advanced 2 min answer

A payments team requires exactly-once processing of a transaction stream. What is actually achievable, and what should be built?

phonepeexactly-onceidempotencytransactionseffects
Show the full answer Hide the answer

What is actually achievable

Exactly-once delivery across a network does not exist. The sender cannot distinguish "not received" from "processed and the acknowledgement was lost", so any system facing that ambiguity must risk duplicates or risk loss.

Exactly-once effect does exist, and it is built from at-least-once delivery plus idempotent consumers.

Teams that pursue the first spend months and build the second anyway, with less understanding of where the duplicates were absorbed.

What should be built

  • A stable, immutable message identifier, assigned when the event occurs and identical across every redelivery. Regenerate it on retry and deduplication is impossible however careful the consumer is.
  • A deduplication record written in the same transaction as the effect. Processing then recording is not idempotent; it merely has a smaller window.
  • A deduplication window sized to the maximum possible redelivery delay, which is longer than teams assume — a consumer replaying from an offset after a bug may reprocess days of messages.
  • Per-key ordering from the partition key, and preserved by the consumer's concurrency model — a consumer handing partition messages to a thread pool has discarded the ordering it paid for, which is the most common way it breaks and is invisible in testing.
  • Reconciliation against the authoritative record, which is the only control that finds what the pipeline does not know it missed.

What the framework's exactly-once mode does and does not give

Transactional processing within the streaming system — read, process, write, offset commit as one atomic unit — which is genuinely useful and is scoped to that system.

It does not extend to external effects. A payment sent to a bank, an email dispatched, a call to a third-party API is outside the transaction, and that is where the duplicates that matter occur.

The framing to use

Assume at-least-once, make consumers idempotent, and reconcile. That combination is achievable, testable and understood — whereas a design resting on a framework's exactly-once guarantee has an unexamined boundary at every external call.