Stackbook Logo
messagingestablished · medium operational burden

Exactly-Once Processing

Also known as: effectively-once, idempotent consumer

Intent

Achieve the observable effect of processing each message exactly once on top of at-least-once delivery.

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.

Forces

  • •True exactly-once delivery is impossible over unreliable networks
  • •Retries are necessary for reliability
  • •Side effects span multiple systems
  • •Deduplication state costs storage and time

Solution

Overview

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.

How it works

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.

Pitfalls

  • Checking for the id and then acting in separate steps, which races.
  • Generating a new id on each producer retry, defeating dedup.
  • Assuming broker transactions cover calls to external HTTP APIs.
  • Keeping dedup records for less time than the maximum redelivery delay.

Key takeaways

  • Exactly-once delivery does not exist; exactly-once effect does.
  • Dedup and state change must share a transaction.
  • Use stable ids and idempotency keys.
  • Size dedup retention to maximum redelivery delay.

✓ When to Use

  • •Money, inventory or other non-repeatable effects
  • •Consumers read and write within one transactional store
  • •Upstream retries are unavoidable

✗ When Not to Use

  • •Effects are naturally idempotent, such as setting a value
  • •Approximate counts are acceptable
  • •Side effects go to systems with no idempotency support and no way to deduplicate

Pros

  • +Safe retries
  • +Correct business state under failure
  • +Simple mental model for handlers

Cons

  • −Deduplication store adds cost and latency
  • −Only covers boundaries you control
  • −Dedup window expiry can reopen duplicates

Cost Profile

Infrastructure

Medium - dedup store

Operational

Medium - dedup retention and cleanup

Cognitive

High - subtle failure windows

Failure Modes

  • Dedup record written separately from effect leaves a crash window

  • Idempotency key not stable across retries

  • Dedup table pruned while duplicates still arrive

Real-World Examples

Alternatives

    Related Patterns

    • outbox-pattern
    • dead-letter-queue

    Competency Domains

    data statereliability ops