What does setting a Kafka consumer's isolation.level to read_committed do, and why does it matter when producers use transactions?
answer
- default = read_uncommitted (dirty reads)
- read_committed hides aborted records
- won't read past Last Stable Offset (LSO)
- must set it explicitly — consumer side
- plain non-tx records always visible
basics
~10 sread_committed makes the consumer skip records from aborted transactions and not read records from transactions that are still open. It only sees data the producer actually committed, so a rolled-back send never becomes visible.
solid answer
~40 sA Kafka consumer's isolation.level is either read_uncommitted (the default) or read_committed. With read_uncommitted the consumer sees every record, including ones written by transactions that later abort. With read_committed the consumer only delivers records from committed transactions: it filters out aborted records and, crucially, will not read past the Last Stable Offset (LSO) — so records belonging to a transaction that is still in flight stay invisible until that transaction commits or aborts. This is what makes producer-side transactions actually useful end to end: if the produce side rolls back, downstream read_committed consumers never observe the phantom messages. In Spring you set it via consumer property spring.kafka.consumer.isolation-level=read_committed (or ConsumerConfig.ISOLATION_LEVEL_CONFIG). Without it, transactional producers give you atomic writes but consumers can still read data that gets aborted.
code
properties · 4 lines# application.properties — make listeners honor producer transactions
spring.kafka.consumer.isolation-level=read_committed
spring.kafka.consumer.enable-auto-commit=false
spring.kafka.consumer.group-id=orders-processorgo deeper
Know the two values, that read_committed hides aborted records, and that it is not the default.
Explain the LSO and the latency trade-off, and where to set it in Spring.
Tie it to end-to-end EOS: consumer read_committed plus a transactional producer are both required.
Discuss operational impact — long-open transactions stalling read_committed consumers and monitoring LSO lag.
## The problem Kafka transactions let a producer write to multiple partitions (and commit consumer offsets) atomically — all messages become visible together or none do. But a transaction only helps if *readers* respect it. `isolation.level` is the **consumer** setting that decides whether readers honor transaction boundaries. ## The two values - **`read_uncommitted`** (the default): the consumer receives *all* records as soon as they are appended to the log, including records written by a transaction that is later **aborted** (rolled back). You get 'dirty reads'. - **`read_committed`**: the consumer only delivers records from **committed** transactions. It does two things: 1. **Filters aborted records** — the broker knows (via control/marker records) which records belonged to an aborted transaction and does not hand them to the consumer. 2. **Respects the Last Stable Offset (LSO)** — the consumer will not read beyond the LSO, which is the offset of the first still-open transaction. So records from an *in-flight* transaction stay invisible until it commits or aborts. This can add latency: a slow or hung transaction blocks visibility of everything after it. Non-transactional (plain) records are always visible under both settings — `read_committed` only gates *transactional* records. ## How to set it in Spring ```properties spring.kafka.consumer.isolation-level=read_committed ``` or programmatically via `ConsumerConfig.ISOLATION_LEVEL_CONFIG = "read_committed"`. In a `@KafkaListener` / `ConcurrentKafkaListenerContainerFactory` setup this flows through the consumer factory. ## Why it matters for exactly-once Exactly-once semantics (EOS) for a read-process-write pipeline depends on **both** ends: the producer wraps produce + offset-commit in a transaction, *and* downstream consumers run `read_committed` so aborted work is never observed. If you forget the consumer setting, a rolled-back batch can still be read and processed — breaking the guarantee. ## Gotchas - `read_committed` is **not the default** — you must set it explicitly. - It adds visibility latency proportional to how long transactions stay open; keep transactions short. - It does not deduplicate or make *your* consumer transactional; it only hides aborted/in-flight transactional records. - Control records (transaction markers) are consumed internally and never delivered to your application.
- What is the Last Stable Offset and how can read_committed add latency?The LSO is the offset of the first still-open transaction; a read_committed consumer won't read past it. If a producer holds a transaction open (or hangs before commit/abort), consumers stall on everything after the LSO until it resolves, so long-running transactions add visibility latency.
- Does read_committed guarantee exactly-once on its own?No. It only ensures consumers don't see aborted/in-flight transactional records. Exactly-once also needs the producer side to be transactional (transactional.id + atomic produce plus offset commit). Both ends together give the guarantee.
saying these in an interview costs you the question
- Thinking read_committed is the default (it is not — read_uncommitted is).
- Believing read_committed deduplicates or makes the consumer itself transactional.
- Assuming read_committed hides plain non-transactional messages too.
- Not realizing it can add latency by blocking at the LSO.