Kafka consumer config
isolation.level: read_committed vs read_uncommitted
isolation.level controls whether a consumer sees records from Kafka transactions before they commit. read_uncommitted (the default) returns every record as soon as it is replicated, including records of open and aborted transactions. read_committed returns only committed transactional records and stops at the last stable offset (LSO) until each open transaction commits or aborts.
- Config
isolation.(consumer)level - Default
- read_uncommitted
- Valid values
- read_uncommitted, read_committed
Values
read_poll() returns every record up to the high watermark, whether its transaction later commits, aborts or is still open.uncommitted read_poll() returns records only up to the last stable offset, the first offset of the oldest open transaction, and filters out the records of aborted transactions. Non-transactional records are returned too, in offset order.committed
What it does
A transactional producer writes its records to the log straight away and resolves them later with a commit or abort marker. The partition keeps two bounds: the high watermark, up to which records are replicated, and the last stable offset, below which every transaction is resolved. read_uncommitted reads to the high watermark. read_committed reads to the LSO and uses the list of aborted transactions the broker returns with each fetch to drop their records, so the application sees each transaction all at once or not at all.
The trade-off
read_committed is what makes an exactly-once pipeline exactly once downstream: without it, a consumer reads records that are later aborted. The cost is latency: every record behind an open transaction, transactional or not, waits until that transaction ends, and a producer that hangs holds the LSO back until transaction.timeout.ms makes the coordinator abort it. read_uncommitted has no such wait but gives no transactional guarantee at all.
Advice
- Set read_committed on every consumer downstream of a transactional producer; Kafka Streams with processing.guarantee=exactly_once_v2 sets it for its own consumers.
- Keep transactions short and transaction.timeout.ms modest: one stuck producer stalls every read_committed consumer of the partitions it wrote to.
- On a topic without transactional producers the two settings return the same records, so read_committed costs nothing there.
See it in the simulator
- Kafka isolation.level: read_committed vs read_uncommitted (Transactions & read_committed) Step 3: Produced · uncommitted Open at this step: Kafka isolation.level: read_committed vs read_uncommitted
- Kafka transaction commit vs abort with read_committed (Transactional write: commit vs abort) Step 7: abort · range skipped Open at this step: Kafka transaction commit vs abort with read_committed