Scenario 04.2.2 · Transactions & read_committed
Kafka isolation.level: read_committed vs read_uncommitted
A transactional producer commits atomically across partitions; read_committed lags the LSO and skips an aborted batch.
Run this scenario in the playgroundCluster ready · two groups. t+0.000s
orders has 2 partitions, RF=3, min.ISR=2. Group rc is read_committed, ru is read_uncommitted. A transactional producer tx1 is idle. Press ▶.
Open at this step: step 1, Cluster ready · two groupsbegin transaction. t+0.100s
tx1 opens a transaction (transactional.id tx-app-1). Nothing is written yet.
Open at this step: step 2, begin transactionProduced · uncommitted. t+1.000s
tx1 wrote one record to each partition. Both records are replicated (HW advanced), so read_uncommitted (ru) sees them - but the LSO is frozen, so read_committed (rc) is stuck and sees nothing. Neither partition's record is visible to rc until the transaction commits.
Open at this step: step 3, Produced · uncommittedcommit · LSO advances. t+1.900s
commit_transaction advances the LSO to the high watermark on both partitions and rc sees the whole transaction. The simulator does both in one step; in Kafka the markers land on each partition separately, so a reader can briefly see one partition's records before the other's.
Open at this step: step 4, commit · LSO advancesSecond txn · uncommitted. t+2.900s
tx1 began a second transaction and wrote one more record to p0. ru briefly sees it; rc remains at the committed LSO.
Open at this step: step 5, Second txn · uncommittedabort · range skipped. t+3.600s
abort_transaction records the aborted range and jumps the LSO past it. read_committed skips the aborted record entirely - it is never delivered to rc, while ru already saw it.
Open at this step: step 6, abort · range skippedWhat you start with
A transactional producer commits atomically across two partitions, then runs a second transaction it aborts. Two consumer groups read the same topic - one read_committed, one read_uncommitted - so you can compare exactly what each isolation level sees.
Step by step
- Cluster ready · two groups t+0.000s orders has 2 partitions, RF=3, min.ISR=2. Group rc is read_committed, ru is read_uncommitted. A transactional producer tx1 is idle. Press ▶. Open at this step: step 1, Cluster ready · two groups
- begin transaction t+0.100s tx1 opens a transaction (transactional.id tx-app-1). Nothing is written yet. Open at this step: step 2, begin transaction
- Produced · uncommitted t+1.000s tx1 wrote one record to each partition. Both records are replicated (HW advanced), so read_uncommitted (ru) sees them - but the LSO is frozen, so read_committed (rc) is stuck and sees nothing. Neither partition's record is visible to rc until the transaction commits. Open at this step: step 3, Produced · uncommitted
- commit · LSO advances t+1.900s commit_transaction advances the LSO to the high watermark on both partitions and rc sees the whole transaction. The simulator does both in one step; in Kafka the markers land on each partition separately, so a reader can briefly see one partition's records before the other's. Open at this step: step 4, commit · LSO advances
- Second txn · uncommitted t+2.900s tx1 began a second transaction and wrote one more record to p0. ru briefly sees it; rc remains at the committed LSO. Open at this step: step 5, Second txn · uncommitted
- abort · range skipped t+3.600s abort_transaction records the aborted range and jumps the LSO past it. read_committed skips the aborted record entirely - it is never delivered to rc, while ru already saw it. Open at this step: step 6, abort · range skipped
The takeaway
read_uncommitted sees records the moment they are written, while read_committed lags at the LSO and never delivers the aborted batch - isolation.level is what makes transactional output safe to consume.
Configs in this scenario
-
min.The fewest in-sync replicas, leader included, that must have a write for an acks=all produce to succeed (default 1); with fewer in the ISR the leader rejects the write with NOT_ENOUGH_REPLICAS. How min.insync.replicas worksinsync. replicas -
transactional.A stable name that turns on transactions for a producer (and implies idempotence); a new producer that calls initTransactions() with the same id fences the older one.id -
isolation.read_uncommitted (the default) returns every record, including those of open and aborted transactions; read_committed returns only committed transactional records and stops at the last stable offset. How isolation.level workslevel
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.2" title="Kafka Simulator: Transactions & read_committed" width="100%" height="520" style="border:1px solid #d0d0d0;border-radius:8px" loading="lazy"></iframe>
<p><a href="https://monedula.dev/kafka-simulator/scenarios/transactions-read-committed/">Kafka isolation.level: read_committed vs read_uncommitted</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 last stable offset (LSO) with concurrent transactions
- 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