---
title: "Kafka last stable offset (LSO) with concurrent transactions"
description: "Two producers hold open transactions on one partition. The younger one commits first, but the Kafka LSO does not move until the older one commits too."
url: https://monedula.dev/kafka-simulator/scenarios/concurrent-txn-lso/
---

# Kafka last stable offset (LSO) with concurrent transactions

Scenario 04.2.3 · Concurrent transactions & LSO. 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 it: https://monedula.dev/kafka-simulator/playground/?scenario=04.2.3

## 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](https://monedula.dev/kafka-simulator/playground/?scenario=04.2.3&t=0)
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](https://monedula.dev/kafka-simulator/playground/?scenario=04.2.3&t=1)
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](https://monedula.dev/kafka-simulator/playground/?scenario=04.2.3&t=2)
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](https://monedula.dev/kafka-simulator/playground/?scenario=04.2.3&t=3)
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](https://monedula.dev/kafka-simulator/playground/?scenario=04.2.3&t=4)

## 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.

## Further reading

- [Apache Kafka documentation](https://kafka.apache.org/43/design/design/#message-delivery-semantics)
