6.7 KiB
6.7 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-10-05 |
Queues, scheduler, outbox — semantics depth
The queue family's posture is decided (re-derived on both engines; ADR-004 and ADR-005) and the hard driver-coupled properties are POC-pinned (transactional enqueue, exactly-once claim under concurrency). What is not yet designed is the semantics depth — this document's subject, and [OQ-05]'s home. It states the design space, the decided constraints any answer must fit, and the reference material; actual semantics decisions become ADRs when made.
Decided constraints (inherited, not re-opened)
- Mechanisms: durable at-least-once queues; the outbox is a helper
over queues; scheduler is cron/
@everyenqueueing into named queues (ADR-002) — pending OQ-09's collapse decision. - Transactional enqueue: commit-atomic with the caller's business write on both engines (ADR-007); rollback drops the job row with no ghosts.
- Exactly-once claim:
FOR UPDATE SKIP LOCKED(pg, POC-pinned under 4×4 concurrency) / honker's machinery (SQLite). At-least-once work; exactly-once processing needs the ack + visibility model. - Wake-driven consumption: the pg engine's default is LISTEN-driven claim with a re-poll safety net; SQLite's is watcher wake with per-subscription fanout (ADR-006).
- Job options (the contract v1 skeleton,
ADR-008 §1, from the
honker-rs surface):
delay, priority, max_attempts, expiresat enqueue;ack / retry / fail / heartbeaton the job handle. DeeperQueueOpts/retry surface is this document's design space. - Cut-flag context: result storage is ADR-002's cut-flag row; if OQ-05's design shows queue consumers provably need result-query-by-id, that is the channel to revisit it — with a consumer-inventory row, not silent inclusion.
Design space (what OQ-05 must pin)
Retry / backoff
- Trigger model: attempts counted from claim-completion; a job
re-appears after visibility timeout OR explicit
retry. Doesmax_attemptsexhaustion move the job to dead-letter, flag it, or drop it? - Backoff curve between attempts: honker and the pg-boss family have their own shapes (fixed, exponential); which does the contract pin, what is configurable, what's the default?
heartbeatsemantics: renewal vs progress signal (or both) — and what a missed heartbeat means (visibility re-expiry? nothing?).
Visibility timeouts
- Where the renewal lives: explicit (
heartbeat) only, or implicit renewal per op on the job handle? Honker's design is the SQLite-side answer; pg-boss's is the reference. One answer, contract-pinned. - Interaction with long business transactions (claims inside caller txs, ADR-007).
Dead-letter
- Move-to-table (honker's
_honker_dead, pg-boss's dead-letter queue) vs flag-in-place. Inspection/requeue surface shape. - Retention: does a dead-lettered job expire (an
expires-driven sweep — an OQ-05 sub-question) or live until explicitly cleared?
Sweep / maintenance
sweep_expiredis on the queue's surface; cadence ownership is the open question. Honker's scheduler machinery (leader-elected, missed-boundary catch-up) can run it; a consumer's own scheduler row can too; the engine could ship a default. Decision shaped by OQ-09's scheduler collapse — same machinery either way.- Multi-process sweep safety on SQLite vs Postgres: named-lock coordination (the alkblobs fleet-sweeper pattern, ADR-002) vs native advisory locks.
Scheduler surface
- OQ-09's question, stated crisply: is scheduler a first-class
mechanism (own handle, methods, inspection) or queues + a
schedule(cron_expression, queue_name, payload)call? The inventory's evidence is thin (documented-thin); a v1 that is queues- a
schedulecall with leader-election behavior documented is the smaller contract; if a consumer needs inspectable/pausable schedule objects (add/pause/resume/update/list/remove), that's the full honker shape. Decide against the inventory rows, not against the whole honker menu.
- a
Namespaces / schema layout (pg side)
- Schema-scoped DDL (the pg-boss design) vs shared-schema table
naming. Rides the reserved-namespace contract
(ADR-006,
ADR-008 §4):
the reserved channel namespace is pinned (
__alkstore_prefix); queue/stream/lock table layout remains this document's OQ-05 work and must be collision-proof against consumer tables in the same database (alkblobs' ADR-008 — co-tenant-tables precedent — at/workspace/@alkdev/alkblobs/docs/architecture/decisions/).
Reference material
- honker's queue design — the SQLite-side incumbent
(
/workspace/honker, its honker-core machinery; the engine rides it directly, so the SQLite side's depth is largely "inherit + pin"). - pg-boss family —
/workspace/pgboss-rs@ 98f7d9e (queue states, maintenance/dead-letter behavior) and the node original (pg-boss, the upstream of record — compare semantics the port may have dropped). - POC ground —
poc-pg-posture-findings.md(the minimal queue table + SKIP LOCKED claim measured; the property suite green);poc-sqlite-posture-findings.md(the SQLite twin). - Consumers — alkfs OQ-FS-14 (sync outbox), alkblobs ops-surface/gc docs (maintenance cadences), the family-wide "who sweeps" punt (inventory §scheduler).
Design Decisions
| ADR | Decision | Summary |
|---|---|---|
| 002 | Feature scope | queues + outbox in; result storage cut-flag |
| 004 | pg driver | queue machinery re-derived, pg-boss as reference |
| 005 | Ownership | design-reference postures for the queue family |
| 006 | Wake contract | wake-driven consumption, guarantee table |
| 007 | Tx seam | enqueue_tx commit-atomicity |
| 008 | Contract v1 | queue skeleton + EnqueueOpts pinned; depth is OQ-05's extension path |
Open Questions
Open questions are tracked in open-questions.md. Key questions affecting this document:
- OQ-05: the semantics-depth pinning this document frames (open, high priority)
- OQ-09: scheduler as first-class mechanism vs queues + schedule (open)