PgQue · Design Brief
Within one queue, events that share a partition key are consumed in order by a single worker at a time; events with different keys are consumed in parallel. Order within a key, parallelism across keys — Kafka's partition model, achieved by routing, with no per-event locks and no new mutable state.
PgQue is an ordered, immutable log, not a job queue. The motivating
workload (a multi-tenant storage service evaluating PgQue to replace pg-boss) emits
millions of file-lifecycle events — FileCreated,
FileDeleted, FileOverwritten. They must be processed
in order per tenant, but order across tenants does not matter.
One in-order consumer can't keep up; naive multi-worker consumption breaks per-tenant order.
Per-key affinity + FIFO. Every event of key K maps to one slot hashtextextended(K,0) % N; within it, non-retried events of K arrive in ev_id order, never to another slot.
Single processor per key. At most one worker holds an unacked event for K at any instant.
Failure boundary. pause: no later K event until the failed one is acked or dead-lettered. skip (v0.1 default): at-least-once, order not held after a failure.
In · v0.1
send()-sourced eventshashtextextended(key,0) % N affinityskip failure policy (default)Deferred / out
pause policy — specified, ships v0.2| ID | Decision | Choice (v0.2) |
|---|---|---|
| D1 | Where the key lives | ev_extra1, send()-sourced queues only |
| D2 | Failure policy | skip default; pause ships v0.2 |
| D3 | Slot count N | Fixed, persisted & enforced per (queue, consumer) |
| D4 | Assignment function | (hashtextextended(key,0) % N + N) % N (stable, sign-safe) |
| D5 | State budget | Happy path & skip: none. pause: a compact blocked-key marker (per failing key, not per event) |
| D6 | Producer signature | send(queue, type, payload, partition_key =>) |
| D7 | Slot & single-owner | slot = consumer "<c>#k/N"; receive lock |
Round 1 re-grounded the mechanism (slots are independent subscriptions, not a coop overlay). Round 2 verified it against the engine: G1's ev_id ordering is real (order by 1, preserved through the filter) and the G2 single-owner lock is the tested #97 double-delivery guard. It also corrected the security trust boundary (the filter hook is admin-only, so the consumer wrappers are SECURITY DEFINER) and moved pause's blocked-set to a durable per-failing-key marker.
Open for round 2: the exact retry_queue predicate that rebuilds a slot's
blocked-key set after a crash (the crux of crash-safe pause), and whether
N× read amplification at target throughput justifies a single-reader/dispatch
optimization in v0.1.