Backend Development › Queues & Async Processing
Delivery Guarantees
At-most-once, at-least-once and exactly-once delivery.
Also known as: delivery guarantees, delivery semantics, message delivery
Messaging systems offer three fundamental delivery guarantees, and each is a trade between reliability and complexity:
- At-most-once — a message is delivered zero or one time. Fast and simple, but messages can be lost (if a consumer crashes after receiving but before finishing, it’s gone). Acceptable only when losing a message is harmless.
- At-least-once — a message is delivered one or more times. If a consumer fails before acknowledging, the message is redelivered. Nothing is lost, but duplicates happen, so consumers must be idempotent. This is the common default.
- Exactly-once — a message is processed exactly once. The ideal, but hard and often expensive: it requires coordination (transactional processing, deduplication, or idempotent sinks). True end-to-end exactly-once is rare; what’s often provided is exactly-once within a system, with caveats at the edges.
at-most-once: may lose → simplest, risk of loss
at-least-once: may duplicate → common; needs idempotent consumers
exactly-once: neither, if attainable → costly; often effectively-once
The classic mistakes:
- Assuming at-least-once is exactly-once. The most common messaging bug: a consumer that isn’t idempotent double-processes on redelivery (double email, double charge). Duplicates are a feature of the guarantee; design for them.
- Believing “exactly-once” is free or global. It usually requires specific configurations and only covers certain paths; end-to-end exactly-once across systems is a design achievement, not a checkbox.
- Choosing at-most-once for important data. It’s simple but lossy; use it only where loss is genuinely fine (some metrics, best-effort notifications).
- Ignoring the ack/commit timing. The guarantee depends on when you ack/commit relative to processing. Acking before processing turns at-least-once into at-most-once (lossy).
- No deduplication where needed. If you need to avoid duplicates, implement idempotent handlers or a dedup store keyed by message id (see idempotency key).
- Forgetting ordering interacts with them. Retries and rebalances can reorder messages; guarantees about delivery don’t imply order (see message ordering).
How to choose: default to at-least-once with idempotent consumers — robust and simple enough, and it’s what most brokers give you. Accept at-most-once only for loss-tolerant messages. Reach for exactly-once (or “effectively-once” via idempotency) when duplicates are unacceptable and you can pay the complexity. The practical rule: make consumers idempotent, and the guarantee becomes far less of a worry. See at-least-once, exactly-once.