Monedula

Scenario 04.1.2 · Where duplicates come from

At-least-once delivery in Kafka: where duplicates come from

Transactions · Intermediate · about 3 min · Apache Kafka 4.3

A lost ack - not a lost record - makes an acks=all producer retry, and with producer idempotence off the broker appends the same order twice.

Run this scenario in the playground

Retry appends ORDER-42 again → duplicate. t+2.600s

With idempotence off the resend carries no producer id and sequence, so the broker cannot tell it from a brand-new record and appends ORDER-42 again at offset 1. The log now holds the same order twice and every consumer reads it twice. THIS is where at-least-once duplicates come from: a retry after an uncertain outcome. Turning on idempotence (see 01.3.x) lets the broker recognise the resend and dedup it.

Open at this step: step 4, Retry appends ORDER-42 again → duplicate

What you start with

A close-up on the birth of a single at-least-once duplicate. One acks=all producer with idempotence OFF writes ORDER-42; the broker stores and replicates it, but the acknowledgement is lost in the network, so the producer - unable to tell a lost write from a lost ack - retries.

Step by step

  1. One acks=all producer, idempotence off t+0.300s A single producer on the at-least-once contract: acks=all (so writes are durable) with enable.idempotence=false set explicitly, since the default has been true since Kafka 3.0. This is the exact configuration that turns a retry into a duplicate. Open at this step: step 1, One acks=all producer, idempotence off
  2. ORDER-42 appended and replicated t+1.400s The producer sends ORDER-42. The leader appends it at offset 0, the ISR replicates it, and the broker sends the ack. HW=1 - the record is safely stored. Open at this step: step 2, ORDER-42 appended and replicated
  3. Ack lost → producer retries t+1.700s The ack never reaches the producer. From the producer's side a lost ack is indistinguishable from a lost write, so - exactly as its contract requires - it resends ORDER-42. The leader (broker-1, LEO=2) already has the resend ahead of the followers. Open at this step: step 3, Ack lost → producer retries
  4. Retry appends ORDER-42 again → duplicate t+2.600s With idempotence off the resend carries no producer id and sequence, so the broker cannot tell it from a brand-new record and appends ORDER-42 again at offset 1. The log now holds the same order twice and every consumer reads it twice. THIS is where at-least-once duplicates come from: a retry after an uncertain outcome. Turning on idempotence (see 01.3.x) lets the broker recognise the resend and dedup it. Open at this step: step 4, Retry appends ORDER-42 again → duplicate

The takeaway

Duplicates come from retries after an uncertain outcome, not from lost data; a broker receiving no producer id and sequence can't recognise the resend and appends the order twice - enabling idempotence is what closes the gap.

Configs in this scenario

Embed this scenario

Paste this into a blog post, a course page or your team's docs. The scenario plays inside your page, and the link under it points readers to this walkthrough.

<iframe src="https://monedula.dev/kafka-simulator/embed/?scenario=04.1.2" title="Kafka Simulator: Where duplicates come from" width="100%" height="520" style="border:1px solid #d0d0d0;border-radius:8px" loading="lazy"></iframe>
<p><a href="https://monedula.dev/kafka-simulator/scenarios/where-duplicates-come-from/">At-least-once delivery in Kafka: where duplicates come from</a></p>

Further reading

Apache Kafka documentation

Troubleshooting

More in Transactions

All scenarios