Module 06 · Lesson 02

Delivery guarantees, honestly stated

At-most-once, at-least-once, and exactly-once — only two of these exist on a network you don't control.

Intermediate 7 min read Updated 2026-11-02

What you will be able to do

  • Contrast at-most-once, at-least-once and exactly-once processing
  • Spot the crash window that forces duplicates
  • Design an idempotency key for a consumer
  • Explain the transactional outbox pattern

Three promises

GuaranteeWhat happensCostUse when
At-most-onceSend and forget; may be lostCheapest, lowest latencyMetrics, telemetry, non-critical pings
At-least-onceRetry until acknowledged; may duplicateDuplicates, needs idempotenceAlmost everything user-visible
Effectively-onceAt-least-once plus deduplication or transactionsExtra state and coordinationMoney, inventory, quotas

The middle row is where serious systems live. The third row is not a magic network property — it is a combination of retries and a consumer that refuses to apply the same event twice.

The crash window

A consumer receives a message, writes to the database, and then crashes before acknowledging. On restart the broker redelivers, because from its point of view nothing was confirmed. The write happened twice. No broker can prevent this, because the acknowledgement and the database commit are two separate systems that cannot be committed atomically.

receive(msg)
begin tx
  insert into ledger(id, amount) values (msg.id, msg.amount)
  insert into processed_ids(id) values (msg.id)   -- same tx
deduct balance(msg.id)
commit
then ack(msg)      -- if we crash before this, redelivery is harmless

Two lines do the work: the deduplication record is written in the same transaction as the business change, so either both exist or neither does. The retry then hits a unique constraint and is skipped. Notice the ack comes last — at-least-once tolerates extra deliveries, not lost work.

"Exactly once" has a scope
A broker can guarantee exactly-once reads and writes inside its own partitions during a transaction. It cannot make your HTTP call to a payment provider exactly-once. That boundary is where engineers get burned, so state it explicitly in an interview.

Idempotency keys

An idempotency key is a stable identifier for the intended effect, generated once at the source and carried through every retry. For an API it might be a client-generated UUID in a header. For an event stream it is the event id. The consumer stores it with a uniqueness constraint and a retention window at least as long as the redelivery window.

  • Do not derive the key from mutable data. A hash of the payload changes when an unrelated field changes, and the retry becomes a new effect.
  • Store the outcome, not just the key. If the first attempt returned a result, a retry should return that same result rather than re-executing.
  • Expire keys deliberately. A one-hour window for a message broker that redelivers within minutes is fine; a window shorter than the retry schedule is a duplicate generator.

The transactional outbox

The dual-write problem is the sibling of the crash window. A service writes to its database and then publishes an event. If the publish fails, the database and the stream disagree forever. If the process dies between the two, downstream never hears about a change that definitely happened.

Write an outbox row in the same transaction
The business row and an event row commit together, or not at all.
Tail the outbox
A relay process reads committed outbox rows in order and publishes them to the broker.
Mark as sent after acknowledgement
Crash before this and the relay republishes — which is fine, because consumers are idempotent.
Prune the outbox
Delete rows after the retention window; an unpruned outbox becomes a table nobody can scan.

Change data capture is the same idea taken one level down: read the database's replication log and emit changes from it. It removes the relay entirely, at the cost of coupling your event schema to table structure.

How to phrase it
"I'd assume at-least-once delivery and make the handler idempotent using the event id inside the same transaction as the write. For publishing, I'd use an outbox so a committed change always becomes an event."

Worth remembering

  • The network can lose an acknowledgement after the work succeeded; the sender cannot tell the difference from a lost message.
  • Most real systems are at-least-once plus idempotent consumers, and that is the correct answer.
  • Exactly-once is scoped to a broker's transactional boundary, not to your side effects in another system.
  • The outbox pattern is how you make a database write and an event publish succeed or fail together.

Checkpoint

Answer all 3 questions to finish the lesson.
1. A consumer commits its database write, then crashes before acknowledging. What does the broker do?
The broker only knows it was never acknowledged. This is the unavoidable duplicate window of at-least-once delivery, and why consumers need idempotency.
2. Where should an idempotency record be written?
Same-transaction means the effect and its deduplication marker share a fate. Writing it separately recreates the crash window it was meant to close.
3. Which problem does the transactional outbox solve?
The outbox turns the publish into a row committed with the business change, so a committed change always has a corresponding event to relay.