Message Delivery Semantics
At-most-once, at-least-once and "exactly-once". Why the network forces you to choose between losing messages and duplicating them, and how idempotency makes duplicates harmless.
On this page
You send a message to another service and don’t get an acknowledgement. Did the message get lost, or did the acknowledgement? You can’t tell. Every messaging system’s delivery guarantee is a decision about what to do next.
The three guarantees
| Guarantee | Strategy | Risk | Good for |
|---|---|---|---|
| At-most-once | Send once, never retry | Messages can be lost | Metrics, telemetry, live prices where the next update supersedes this one |
| At-least-once | Retry until acknowledged | Messages can be duplicated | Almost everything else: orders, emails, jobs |
| Exactly-once | At-least-once delivery plus deduplication | Complexity | Payments, ledgers, anything where duplicates cost money |
“Exactly-once” is a careful lie
Over an unreliable network (Fallacies of Distributed Computing), true exactly-once delivery is impossible. What systems actually offer is exactly-once processing: deliver at least once, and make sure processing a message twice has the same effect as processing it once. That property is idempotency.
Making operations idempotent
- Idempotency keys. The client generates a unique key per logical operation (
Idempotency-Key: 7f3a…). The server records keys it has processed and returns the original result for a repeat. Payment APIs work this way. - Natural idempotency. “Set status to shipped” is idempotent; “add one to the counter” isn’t. Prefer setting state over applying deltas where you can.
- Deduplication windows. Consumers remember recent message IDs and drop repeats.
- Transactional outbox. Write the state change and the outgoing message in one database transaction, then publish from the outbox. This avoids “saved but never announced” and its opposite.
A small example
A user double-clicks “Place order” on a flaky connection. Without idempotency, two orders. With a key generated when the form loads, the second request returns the first order’s confirmation. The same thinking applies to the retry logic in a WebSocket client: replay from a sequence number, and make applying an already-seen update a no-op.
In practice
The question I now ask of any endpoint that changes state is: what happens if this is called twice? If the answer is “something bad”, it needs an idempotency key or a redesign. It’s one of the cheapest reliability wins there is. The wider picture of what can go wrong is in Types of Failure, and the underlying channel ideas in Message Passing.
- #distributed-systems
- #messaging
- #idempotency
- #queues
- #reliability
↩ Linked from (4)
- Fallacies of Distributed ComputingEvergreen
…le | Packets drop, connections reset, cables get cut | Timeouts, retries with backoff, idempotent operations (Message Delivery Semantics) | | 2. Latency is zero | Every hop costs milliseconds; a chatty design costs secon…
- Message PassingGrowing
…ing and delivery guarantees. Within one machine these are easy. Across a network they're the whole game — see Message Delivery Semantics.
- Two-Phase CommitGrowing
Sagas: a sequence of local transactions, each with a compensating action to undo it if a later step fails. Transactional outbox plus idempotent consumers (Message Delivery Semantics).
- Types of FailureGrowing
…tice the crash, usually a Heartbeat. Omission needs retries, which means duplicates, which means idempotency (Message Delivery Semantics). Timing failures are the sneaky ones, because a slow node and a dead node look the…
↪ Links to (4)
Related by topic
- Evergreen
Architecture Decision Records
Career - Growingmetaphor
Balance Is a Control Loop
Calisthenics Lab - Evergreen
CAP Theorem
Distributed systems - Seed
Deadlock and Starvation
Distributed systems