Problem
Networks and crashes force brokers to choose between losing messages and delivering duplicates. Business effects like charging a card must not happen twice, yet the consumer cannot tell whether a retry is new.
Also known as: effectively-once, idempotent consumer
Achieve the observable effect of processing each message exactly once on top of at-least-once delivery.
Networks and crashes force brokers to choose between losing messages and delivering duplicates. Business effects like charging a card must not happen twice, yet the consumer cannot tell whether a retry is new.
Exactly-once is better described as effectively-once. The message may arrive many times, but its effect is applied once.
The standard recipe is at-least-once delivery plus idempotent handling, with the dedup record and the state change committed atomically.
Think in terms of boundaries. Inside a single database transaction, exactly-once effect is achievable. Across a broker and a database, you need either transactional features in both or an inbox table. Across third-party APIs, you depend on their support for idempotency keys, and otherwise you must reconcile afterward.
On receipt, the handler opens a transaction, inserts the message id into a processed table, applies the change, and commits. A unique-key violation means a duplicate and the handler just acknowledges.
BEGIN;
INSERT INTO processed(msg_id) VALUES (:id); -- fails if seen
UPDATE account SET balance = balance - :amt WHERE id = :acct;
COMMIT;
ack(message)
Some streaming platforms offer transactional writes that make consuming input, updating state and producing output atomic within the platform. That covers pipelines that stay inside the platform, but the guarantee ends at the edge. When a pipeline writes to an external store, make that write an idempotent upsert keyed by a deterministic id derived from the input.
Medium - dedup store
Medium - dedup retention and cleanup
High - subtle failure windows
Dedup record written separately from effect leaves a crash window
Idempotency key not stable across retries
Dedup table pruned while duplicates still arrive