Patterns

Idempotent Consumer

Safely process duplicate messages by making consumer side effects converge to the same final result.

intermediate4 min readUpdated unknownReliabilityOperationsTradeoffs
At-Least-Once DeliveryDeduplicationEvent IDsRetry Safety

Concepts Covered

  • Duplicate message delivery
  • At-least-once delivery
  • Event IDs
  • Deduplication stores
  • Natural uniqueness
  • Safe retries
  • Consumer side effects
  • Replay safety

1. Intent

The Idempotent Consumer pattern makes an event consumer safe to run more than once for the same message.

This matters because many queues and event streams use at-least-once delivery. At-least-once delivery means a message should not be lost, but it may be delivered multiple times.

The intent is to make duplicate delivery harmless at the business side-effect layer.

2. The Problem Without This Pattern

Suppose a notification worker receives LikeCreated.

It writes a notification, then crashes before acknowledging the event. The broker retries. Without idempotency, the worker writes another identical notification.

The same issue can:

  • inflate counters
  • duplicate emails
  • send duplicate push notifications
  • charge customers twice
  • replay old state over newer state
  • create duplicate delivery records

The broker did not fail by redelivering. The consumer failed by treating every delivery as new business intent.

3. How The Pattern Works

There are several common implementations.

StrategyHow it worksExample
Processed-event tableStore event_id after processing and skip repeatsprocessed_events(event_id, consumer_name)
Unique side-effect keyUse a unique constraint on the effect itselfone notification per (recipient, actor, post, type)
Version guardApply only events newer than current stateupdate projection only if event_version increases
UpsertMake repeated writes converge to the same valueset delivery state to delivered

A common flow uses a transaction:

BEGIN;

INSERT INTO processed_events (event_id, consumer_name, processed_at)
VALUES ('evt_123', 'notification-worker', now());

INSERT INTO notifications (...);

COMMIT;

If the same event arrives again, the processed_events insert fails, and the consumer skips the side effect.

The dedupe marker and the side effect should commit together when possible. If the marker commits but the side effect fails, the consumer may think work happened when it did not.

4. When To Use It

Use this pattern when:

  • the message broker may redeliver events
  • the publisher may publish duplicates
  • the consumer performs side effects
  • retries are necessary for reliability
  • replay is possible
  • correctness matters more than raw simplicity

Good examples:

  • notification workers
  • counter projection workers
  • message delivery workers
  • analytics aggregators that require accurate counts
  • outbox event consumers

5. When Not To Use It

You may not need heavy deduplication when:

  • the side effect is naturally idempotent
  • duplicate processing is harmless
  • the stream is purely analytical and sampling is acceptable
  • the consumer can rebuild state from scratch regularly
  • the cost of dedupe exceeds the cost of duplicates

Even then, be explicit. "Duplicates are acceptable" should be a product and engineering decision, not an accident.

6. Data And Operational Model

Idempotent consumers often need:

  • stable event IDs
  • event version or timestamp
  • deduplication retention window
  • unique constraints for side effects
  • metrics for duplicate rate
  • replay policy
  • dead-letter behavior for invalid events

Deduplication data cannot always be kept forever. Systems often retain processed IDs for a window that matches replay and retry expectations.

Operators should monitor:

  • duplicate event rate
  • dedupe table growth
  • skipped duplicate count
  • uniqueness violations
  • replay success rate
  • old-event processing attempts

7. Failure Modes

  • Deduplication key is too broad and drops valid work.
  • Deduplication key is too narrow and misses duplicates.
  • Processed-event table grows without retention.
  • Consumer records an event as processed before side effects complete.
  • Old events overwrite newer state.
  • Replay re-triggers side effects because dedupe records expired too early.
  • Different consumers incorrectly share the same dedupe namespace.

8. Tradeoffs

BenefitCost
Makes retries saferRequires dedupe state or careful write design
Prevents duplicate side effectsAdds storage and complexity
Works well with at-least-once brokersRetention window must be chosen
Improves operational confidenceBugs can hide if dedupe keys are wrong
Supports replayCan block valid reprocessing if scoped poorly

This pattern is the practical partner of at-least-once delivery. If the broker may redeliver, the consumer must know how to converge.

Knowledge links

Use these links to understand what to know first, where this idea appears, and what to study next.

Prerequisites

Read these first if this topic feels unfamiliar.

Used In Systems

System studies where this idea appears in context.

Related Concepts

Core ideas that connect to this topic.

Related Patterns

Reusable architecture moves built from these ideas.