Monedula

Scenario 04.3.2 · EOS - offsets in a txn + fencing

Kafka zombie producer fencing with transactional.id

Transactions · Advanced · about 3 min · Apache Kafka 4.3

Consume-process-produce commits input offsets inside the output transaction; a stale-epoch zombie producer is fenced.

Run this scenario in the playground

zombie fenced. t+3.900s

tx-zombie (epoch 0) tries to open a new transaction, but tx holds a newer epoch, so the zombie is FENCED and can never write duplicates. In Kafka the fence lands on its first send or commit, since beginTransaction() is client-side, and transaction.version=2 bumps the epoch at every commit.

Open at this step: step 7, zombie fenced

What you start with

Exactly-once semantics with producer fencing. A transactional producer runs a consume-process-produce transaction - committing the output record and the input offsets together - then a stale-epoch 'zombie' instance sharing the same transactional.id wakes up and tries to begin its own transaction.

Step by step

  1. Cluster ready · EOS app t+0.000s orders has 1 partition, RF=3. Group eos is read_committed with enable.auto.commit=off. tx is the live producer instance (epoch 1); tx-zombie is an old instance of the same transactional.id (epoch 0). Press ▶. Open at this step: step 1, Cluster ready · EOS app
  2. begin txn 1 t+0.100s tx opens transaction 1 (transactional.id tx-app). Open at this step: step 2, begin txn 1
  3. Produced · uncommitted t+1.100s tx wrote two records; the HW advanced but the LSO is frozen, so read_committed eos sees nothing yet. Open at this step: step 3, Produced · uncommitted
  4. txn 1 committed · read, no auto-commit t+1.900s commit advances the LSO and eos reads both records (position 2). But committed stays at 0: enable.auto.commit is off, so offsets move only through the transaction. Open at this step: step 4, txn 1 committed · read, no auto-commit
  5. txn 2 · offsets staged t+2.900s In a second txn tx produced an output record and called sendOffsetsToTransaction(eos): eos offsets {0:2} are staged inside the transaction, not yet committed. Open at this step: step 5, txn 2 · offsets staged
  6. commit · offsets + output atomic t+3.400s commit_transaction commits the output record AND eos input offsets atomically - eos.committed jumps to {0:2}. On an abort neither would commit: exactly-once. Open at this step: step 6, commit · offsets + output atomic
  7. zombie fenced t+3.900s tx-zombie (epoch 0) tries to open a new transaction, but tx holds a newer epoch, so the zombie is FENCED and can never write duplicates. In Kafka the fence lands on its first send or commit, since beginTransaction() is client-side, and transaction.version=2 bumps the epoch at every commit. Open at this step: step 7, zombie fenced

The takeaway

The coordinator fences the zombie because only the latest producer epoch may write - a partitioned-then-recovered task can never corrupt the committed stream.

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.3.2" title="Kafka Simulator: EOS - offsets in a txn + fencing" width="100%" height="520" style="border:1px solid #d0d0d0;border-radius:8px" loading="lazy"></iframe>
<p><a href="https://monedula.dev/kafka-simulator/scenarios/eos-fencing/">Kafka zombie producer fencing with transactional.id</a></p>

Further reading

Apache Kafka documentation

Troubleshooting

More in Transactions

All scenarios