Monedula

Scenario 04.2.3 · Concurrent transactions & LSO

Kafka last stable offset (LSO) with concurrent transactions

Transactions · Advanced · about 3 min · Apache Kafka 4.3

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 playground

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

What 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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 &amp; 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

Apache Kafka documentation

More in Transactions

All scenarios