skip to content

Idempotent Consumers and Dedup

Getting effectively-once results by making processing idempotent instead of using Kafka transactions. Interviewers ask because most real sinks are external systems where Kafka's EOS does not apply.

part ofApache Kafkaoverview, primer and where to startread it →
on this pageshow

questions

5

What does it mean to make a Kafka consumer idempotent, and why does that let you live safely with at-least-once delivery?

level: juniorimportance: must knowfreq 75%

answer

  1. process then commit = at-least-once = duplicates on crash
  2. idempotent: apply N times == apply once
  3. upsert/SET not append/INCREMENT
  4. duplicates harmless -> effectively exactly-once
  5. external side effects -> EOS can't reach

basics

~20 s

An idempotent consumer can process the same message more than once with no extra effect. Kafka's at-least-once delivery can redeliver a record after a crash; if processing is idempotent, those duplicates are harmless, so you get effectively exactly-once results.

solid answer

~40 s

Kafka's default delivery is at-least-once: a consumer reads a record, does its work, then commits the offset. If it crashes after working but before committing, the rebalanced consumer re-reads and re-processes that record — a duplicate. An idempotent consumer is one whose processing produces the same final state whether a record is applied once or many times. You achieve this by making the side effect naturally idempotent (e.g. an upsert keyed by an id, a SET rather than an INCREMENT) or by recognizing and dropping records already handled. Because duplicates become harmless, at-least-once delivery is safe and you reach 'effectively exactly-once' end results without Kafka transactions. This matters most when the side effect lands outside Kafka — a database, an HTTP call, a cache — where Kafka EOS cannot reach.

go deeper

for a junior

Know the loop process-then-commit, that a crash before commit causes a redelivery, and that idempotent processing makes that redelivery harmless.

for a middle

Contrast at-least-once vs at-most-once, give concrete idempotent vs non-idempotent operations, and explain 'effectively exactly-once'.

for a senior

Explain why the three steps aren't atomic, when idempotence alone is insufficient (partial work, non-idempotent effects), and the EOS boundary at external sinks.

for a principal

Frame idempotence as the default architectural choice for external side effects and articulate the cost/correctness trade vs Kafka transactions across a system.

## The problem A Kafka consumer's normal loop is: 1. `poll()` records, 2. process them, 3. then commit the offset (the position it has reached in the partition). These three steps are **not atomic**. If the process crashes *after* the side effect (writing a row, calling an API) but *before* the offset commit, then on restart — or after a partition rebalance moves the work to another consumer — Kafka serves the same records again starting from the last committed offset. The work runs a second time. This is **at-least-once delivery**: every record is delivered one or more times, never lost, possibly duplicated. ## At-most-once vs at-least-once - If instead you commit the offset *before* doing the work and then crash, the record is never reprocessed — that's **at-most-once**, which risks losing work. - Most systems prefer **at-least-once** (no data loss) and then deal with duplicates. ## Idempotence An operation is *idempotent* if applying it multiple times has the same effect as applying it once. `x = 5` is idempotent; `x = x + 1` is not. An **idempotent consumer** makes its processing idempotent so that a redelivered (duplicate) record changes nothing the second time. Examples: - an `UPSERT` / `INSERT ... ON CONFLICT DO UPDATE` keyed by a stable business id; - writing to a key/value store with `PUT key=value` (overwrite, not append); - a DELETE; - setting a status to an absolute value rather than incrementing a counter. ## Why it makes at-least-once safe If duplicates have no observable effect, then 'delivered one or more times' yields the same end state as 'delivered exactly once'. You get ***effectively exactly-once*** results — the externally visible outcome of exactly-once — while still running the simple, cheap at-least-once consumer. ## Edge cases - Idempotence only covers the *repeat* of an already-applied effect; it does not by itself protect against **partial work** (you processed half a batch then crashed) — you still need each unit's effect to be individually idempotent and to re-run cleanly. - **Non-idempotent side effects** (send an email, charge a card, append to a log, increment a counter) need an explicit dedup mechanism instead, because re-running them is observable. ## Where it beats Kafka transactions Kafka's **exactly-once semantics (EOS)** only guarantee atomicity for effects that stay *inside* Kafka (consume → transform → produce to other Kafka topics, plus the offset). The moment your side effect is an external database row, an HTTP POST, or a cache write, EOS cannot make that atomic with the offset commit. Idempotent processing is the standard answer there.

  • Why is committing the offset before processing not a good fix for duplicates?
    That gives at-most-once: if you crash after committing but before the work completes, the record is never reprocessed and the work is lost. You trade duplicates for data loss, which is usually worse.
  • Give one operation that is naturally idempotent and one that is not.
    Idempotent: an upsert keyed by an order id, or a DELETE. Not idempotent: incrementing a counter, appending to a list, sending an email, or charging a payment — each repeat is observable.

saying these in an interview costs you the question

  • Saying Kafka delivers exactly-once by default — the default is at-least-once.
  • Claiming committing offsets first eliminates duplicates without noting it causes data loss (at-most-once).
  • Thinking idempotence means the consumer never sees duplicates — it sees them, but handles them harmlessly.
  • Believing idempotent producers alone make the consumer's external side effects exactly-once.

context

open as a page

When would you choose idempotent at-least-once processing over Kafka's exactly-once semantics (EOS) for a consumer?

level: seniorimportance: must knowfreq 65%

basics

~20 s

Choose idempotent consumers when the side effect lands outside Kafka — a database, HTTP call, cache, or other system. Kafka EOS only makes consume-transform-produce atomic within Kafka, so for external sinks idempotence is simpler and the only thing that actually works.

open as a page

Some operations like sending an email or charging a card can't be made naturally idempotent. How do you make a consumer effectively exactly-once for those, conceptually?

level: middleimportance: should knowfreq 55%

basics

~20 s

If the side effect can't be a simple overwrite, you make it idempotent by giving each unit of work a stable unique id and recording 'already done' so a redelivered record is recognized and skipped. The action plus the 'done' record must commit together.

open as a page

A teammate says 'we enabled enable.idempotence on the producer, so our consumers are exactly-once now.' What's wrong with that statement?

level: middleimportance: should knowfreq 60%

basics

~20 s

Producer idempotence only stops the producer's own retries from writing duplicate records into a partition. It does nothing for consumer-side processing. A consumer can still reprocess a record after a crash, so consumer-side idempotence is a completely separate concern.

open as a page

Walk through exactly where the duplicate-creating window is in a consumer that does an idempotent write, and how offset-commit choices interact with it.

level: seniorimportance: should knowfreq 50%

basics

~20 s

The window is between performing the side effect and committing the offset: a crash there causes the record to be reprocessed after rebalance/restart. Idempotent writes make that reprocessing harmless. Offset-commit choice (auto vs manual, before vs after) only shifts how often duplicates happen, not whether idempotence is needed.

open as a page