okuri

Architecture

Everything durable lives in the bucket. Compute nodes hold no durable state at all: kill a node mid-receive and another node picks up the lease when it expires. There is no cluster membership, no leader election for the data path, and no local disk to lose.

This page is a summary. The full technical reference is docs/DESIGN.md in the repository.

The objects

Segments. Producers batch messages into immutable segment objects. A segment is written once and never modified; batching (the “linger”) is what amortizes per-request object-store pricing on the write path.

The manifest. A per-queue manifest object, advanced by compare-and-swap, is the queue’s single authority: which segments exist, what is acked, where the purge watermark sits, and the queue’s configuration (visibility_timeout_ms, retention_ms, max_receive_count).

Leases. Small objects that fence in-flight deliveries. Every lease transition is a pure function of observed state and time, applied via compare-and-swap, so nodes with disagreeing clocks can cost a redelivery but never lose or double-ack a message.

Tombstones. Acks land as tombstones that the compactor later folds into the manifest and garbage-collects.

The statelessness contract

Any node serves any request. Nothing in the API requires sticky routing, session affinity, or a client returning to the node it started with. Receipt handles are signed with a fleet-wide key rather than tied to a node, which is what lets a load balancer send the ack anywhere.

The flip side is that a fleet is not free: more nodes means more independent pollers against the same objects. The operations page shows the measured cost of running 1, 2, and 4 nodes.

The compactor

An async compactor runs alongside serving: it merges narrow segments, applies ack tombstones, garbage-collects acked data, and enforces retention. It is crash-safe and resumable; a compactor killed mid-run leaves nothing worse than work still to do.

Sequencer mode

By default every node writes independently and contention is resolved by CAS retries. Optionally, a fleet can enable sequencer mode: one node takes a CAS-elected role (a lease with a TTL, renewed on an interval) and batches writes through itself to cut contention and cost. The role is purely an optimization: if the holder dies, another node claims the lapsed lease within milliseconds, and no data is lost because the bucket remains the only source of truth.

Namespaces

Every object a node writes lives under its configured namespace prefix, and the namespace is bound into every receipt-handle signature. Two deployments pointed at one bucket under different namespaces share the bucket and nothing else.

Why this design

The premise is that the hard parts of a queue (durability, fencing, atomicity) are exactly what object stores already sell: eleven nines of durability and compare-and-swap on writes. okuri adds queue semantics on top of those primitives instead of rebuilding them in a stateful cluster, and everything else follows: stateless nodes, trivial scale-out, and nothing to operate but a binary and a bucket.