EP · Overview · Dive 1
The durability spectrum.
Durability is not a checkbox; it is a position on a spectrum, set deliberately per queue. Read from one measurement, the spectrum has a single mechanism behind it — and one more axis that the throughput number hides: whether the durable state ever leaves the box.
The measurement
graft.design.mdFour engines recorded the same 20,000 jobs on one shared vCPU, with Postgres durable (synchronous_commit=on) and Redis durable (AOF everysec). The result spans three orders of magnitude, and the spread has exactly one explanation: throughput tracks how many durable records ride each fsync. The fsync is fixed cost; everything else is how many jobs you amortize it across.
engine store durability single batch
Memory in-memory map none 336,961 —
Champ K=10000 local fsync snapshot bounded ≤ 10,000 rec 103,970 —
Champ K=1000 local fsync snapshot bounded ≤ 1,000 rec 33,989 —
BullMQ Redis AOF everysec bounded 1s 6,388 16,180
Oban Postgres sync commit strict per-commit 718 13,235
Memory rides ∞ records/fsync · Champ rides K · Oban-batch rides a txn · Oban-single rides one
A factor of 469 separates strict per-commit durability (Oban, 718 jobs/s) from no durability (Memory, 336,961 jobs/s). Within Oban, the single-insert path commits once per job; insert_all writes 1,000 jobs per transaction and reaches 13,235 — a factor of 18, purely from spreading one fsync across the batch. Champ reaches the same class continuously, because its checkpoint amortizes the fsync over K records without the caller ever holding a batch.
The lesson is mechanical, not magical: to make a strong durability guarantee affordable, raise the durable-records-per-fsync — with K (Champ), a transaction (Oban-batch), or a group-commit. That single lever is what the whole two-tier design pulls.
Two axes, one decision
graft.design.mdThe shootout measures one axis: the loss window, from “none” to “strict per-commit.” A second axis is independent of it — does the durable state leave the machine? Oban is strict but single-node unless Postgres streaming replication is bolted on; Champ’s snapshot is bounded-loss and ships asynchronously. A tier can be strict-but-single-node, or bounded-but-replicated; the two choices do not move together.
Placing a queue therefore means choosing both — a loss window and a replication posture. A telemetry queue wants neither (in-memory, 336,961 jobs/s, holding nothing). A money queue wants both: per-commit and off-box. The rest of Echo Persistence is about reaching that far corner — per-commit and replicated — without paying the per-commit floor, which Dive 2 builds.
References & sources
- Echo records
- graft.design.mdthe shootout and the spectrum
- External
- Valkey persistence (AOF)the 1s loss window
- oban-bg/obanstrict per-commit; insert_all batch
- taskforcesh/bullmqthe Redis-backed tier
- Designing Data-Intensive Applications, Kleppmann 2017group commit · Ch. 3, 5