Skip to content

End-to-End Delivery Guarantees

An end-to-end guarantee is only as strong as its weakest link — the producer side (acks and idempotence) and the consumer side (commit placement) each contribute, and you have to reason about them together.

Delivery semantics are not a single switch. Data flows producer → log → consumer, and each hop has its own failure mode:

  • Producer → log. With acks=all and enable.idempotence=true, a record is written durably and retries do not create duplicates in the partition. With acks=0 or acks=1, a broker failure at the wrong moment can lose the write.
  • Log → consumer. Commit after processing gives at-least-once; commit before gives at-most-once. The log itself never loses committed records within retention.

Combine them and you get the real end-to-end behavior:

ProducerConsumer commitEnd-to-end
acks=all + idempotentafter processingat-least-once (no loss, possible dupes)
acks=1after processingat-least-once, but a rare broker failure can still lose a write
acks=all + idempotentbefore processingat-most-once (no dupes, possible loss)
transactional produceroffsets in the transactionexactly-once inside Kafka
flowchart LR
  prod["Producer: acks=all + idempotence"] -->|durable, no dupes| log["Partition log"]
  log -->|committed records retained| cons["Consumer"]
  cons -->|commit after processing| out["at-least-once end-to-end"]
  out -->|idempotent sink or transactions| eos["effectively exactly-once"]
Each hop contributes to the end-to-end guarantee

Most systems run at-least-once end-to-end: acks=all, idempotent producer, commit after processing — then make the downstream effect idempotent so duplicates are harmless. It is simple, robust, and fast. Reach for full exactly-once (transactions) only when the pipeline stays inside Kafka and duplicates are genuinely unacceptable, because it adds coordination cost. The rest of this module builds out that exactly-once path.

Why must you reason about producer and consumer guarantees together?
What combination gives robust at-least-once with no loss?
For most systems, what is the sensible default to design around?
When is full exactly-once (transactions) worth its cost?