05 / 05

Why does careful offset committing not by itself provide exactly-once business effects?

Difficulty: 9/10
Commit and replay

Offset commits and external side effects are two separate writes to two systems, so a crash between them causes duplicates or loss unless you make the effect idempotent or atomically tied to the offset

An offset commit is a write to Kafka. A business effect, such as a database update, a payment call, an email or an HTTP request, is a write to some other system. There is no atomic operation spanning both. So whichever order you choose, a crash in the gap breaks something. Effect first, then commit: the effect happened, the commit did not, so the record is redelivered and the effect repeats (at-least-once, duplicates). Commit first, then effect: the commit happened, the effect did not, so the effect is lost (at-most-once). Careful commit ordering therefore only chooses which failure you get; it cannot eliminate both. And duplicates arise even without crashes: rebalances can hand a partition to a new consumer while the old one is still finishing work, a commit can fail after processing succeeded, and retries inside the handler can repeat a non-idempotent call.

What Kafka's exactly-once features cover is narrower than the phrase suggests. Idempotent producers deduplicate retries to a partition. Transactions with sendOffsetsToTransaction make 'write output records to Kafka and commit the input offsets' atomic, and read_committed consumers see only committed output, so a Kafka-to-Kafka consume-transform-produce pipeline (including Kafka Streams with exactly_once_v2) can be effectively exactly-once. The guarantee ends at Kafka's boundary: an external database, API or email is outside the transaction. For those, I get exactly-once business effects by making the effect idempotent (dedupe on a stable event ID, upsert by natural key, or pass an idempotency key to the external API), or by storing the consumer offset in the same local transaction as the effect, so both succeed or fail together, and seeking to the stored offset on assignment. Kafka does not support two-phase commit across arbitrary systems.

javascript
  1. 1

    Option: idempotent effect with a dedupe key. Most flexible and works with any external system that supports it. Costs a dedupe store or a natural unique key, plus a retention policy for seen IDs.

  2. 2

    Option: offsets stored with the effect in one local transaction. Gives exactly-once effects against that one database, at the cost of owning offset management, handling rebalances yourself and losing Kafka's built-in lag visibility for that group.

  3. 3

    Option: Kafka transactions. Correct for read-process-write between Kafka topics (and for Kafka Streams exactly_once_v2), but they do not cover external systems, and they add latency and operational cost.

  4. 4

    Option: transactional outbox. For the producing side, write the state change and the outgoing event in one DB transaction and publish with CDC or a poller, so the DB and Kafka cannot diverge.

  5. 5

    Common mistake: equating 'exactly-once semantics' marketing with end-to-end business exactly-once. The guarantee holds only inside the transactional scope Kafka controls.

  6. 6

    Common mistake: assuming an idempotent producer fixes consumer-side duplicates. It dedupes producer retries, not consumer reprocessing.

  7. 7

    Common mistake: ignoring zombies. A consumer paused by a long GC or network partition can keep working after a rebalance, so effects need a fencing check (generation or version) or an idempotent key.

  8. 8

    Common mistake: using non-idempotent external calls such as increments or emails without an idempotency key, so any redelivery produces a visible duplicate.

  9. 9

    Version note: Kafka Streams exactly_once_v2 (introduced in 2.6, requiring brokers 2.5 or newer) replaced the older EOS-alpha and EOS-beta modes, and Kafka Connect supports exactly-once for source connectors from 3.3. Support is connector-specific, so check your versions and connectors.

Share

Share via WhatsApp, X, Facebook, LinkedIn or copy link. Open Graph preview enabled.