Scenario 04.2.3 · Concurrent transactions & LSO
Kafka last stable offset (LSO) with concurrent transactions
Two producers hold open transactions on one partition; committing the younger one cannot stabilize its records while the older stays open.
Run this scenario in the playgroundCluster ready · two txn producers. t+0.000s
orders has 1 partition, RF=3. Producers A (tx-a) and B (tx-b) are independent transactional apps writing the same partition. Group rc is read_committed, ru is read_uncommitted. Press ▶.
Open at this step: step 1, Cluster ready · two txn producersA open · LSO held at 0. t+0.900s
A produced offset 0 inside an open transaction. The record is replicated (HW advanced), so read_uncommitted (ru) sees it - but the LSO is held at 0, so read_committed (rc) is stuck.
Open at this step: step 2, A open · LSO held at 0A + B both open. t+1.900s
B opened a second transaction and wrote offset 1. Both txns are open, so the partition’s openTxns list has two entries; the LSO is the MIN first-offset = 0. ru now sees both records; rc still sees nothing.
Open at this step: step 3, A + B both openB committed · LSO still held by A. t+2.900s
B committed, so its entry is removed from openTxns - but A is still open at offset 0, so the LSO stays at 0. B’s offset-1 record is committed yet NOT stable: read_committed still cannot read it. This is the whole point of the concurrent-txn LSO rule.
Open at this step: step 4, B committed · LSO still held by AA committed · LSO releases. t+3.600s
A committed too. With no open txn left, the LSO jumps to the high watermark (2). read_committed atomically catches up and reads both records at once.
Open at this step: step 5, A committed · LSO releasesWhat you start with
Two transactional producers hold open transactions on the same partition at once. The Last Stable Offset (LSO) is the minimum first-offset across all open transactions, so the order in which they resolve - not just whether they commit - governs when records become visible.
Step by step
- Cluster ready · two txn producers t+0.000s orders has 1 partition, RF=3. Producers A (tx-a) and B (tx-b) are independent transactional apps writing the same partition. Group rc is read_committed, ru is read_uncommitted. Press ▶. Open at this step: step 1, Cluster ready · two txn producers
- A open · LSO held at 0 t+0.900s A produced offset 0 inside an open transaction. The record is replicated (HW advanced), so read_uncommitted (ru) sees it - but the LSO is held at 0, so read_committed (rc) is stuck. Open at this step: step 2, A open · LSO held at 0
- A + B both open t+1.900s B opened a second transaction and wrote offset 1. Both txns are open, so the partition’s openTxns list has two entries; the LSO is the MIN first-offset = 0. ru now sees both records; rc still sees nothing. Open at this step: step 3, A + B both open
- B committed · LSO still held by A t+2.900s B committed, so its entry is removed from openTxns - but A is still open at offset 0, so the LSO stays at 0. B’s offset-1 record is committed yet NOT stable: read_committed still cannot read it. This is the whole point of the concurrent-txn LSO rule. Open at this step: step 4, B committed · LSO still held by A
- A committed · LSO releases t+3.600s A committed too. With no open txn left, the LSO jumps to the high watermark (2). read_committed atomically catches up and reads both records at once. Open at this step: step 5, A committed · LSO releases
The takeaway
Committing the younger transaction can't stabilize its records while an older one stays open; only when the oldest open transaction resolves does the LSO jump to the high watermark - a single long-running transaction stalls visibility for everything behind it.
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.2.3" title="Kafka Simulator: Concurrent transactions & LSO" width="100%" height="520" style="border:1px solid #d0d0d0;border-radius:8px" loading="lazy"></iframe>
<p><a href="https://monedula.dev/kafka-simulator/scenarios/concurrent-txn-lso/">Kafka last stable offset (LSO) with concurrent transactions</a></p> Further reading
More in Transactions
- Kafka at-most-once vs at-least-once vs exactly-once
- At-least-once delivery in Kafka: where duplicates come from
- Kafka transaction commit vs abort with read_committed
- Kafka isolation.level: read_committed vs read_uncommitted
- Kafka transaction.timeout.ms: the coordinator aborts
- Kafka read_committed during a leader election
- Kafka consume-process-produce loop with exactly-once
- Kafka zombie producer fencing with transactional.id
- Kafka processing.guarantee=exactly_once_v2 end to end