# Architecture

> How segments, the manifest, leases, the compactor, and the sequencer make a queue out of a bucket.

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`](https://github.com/okuri-oss/okuri/blob/main/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](/docs/operations) 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.
