Duration: 30–45 mins
Optional — only if Day 2 is ahead of schedule. Read KafkaProducer’s section on transactions first.
Set transactional.id on a producer’s Properties (any stable
unique string) and call initTransactions() once, right after
construction
Wrap a batch of send calls in beginTransaction() /
commitTransaction(). Consume with isolation.level=read_committed
on the consumer side and confirm the batch appears atomically — all
records visible together, not one at a time as they’re sent
Force an abort: call send a few times, then abortTransaction()
instead of committing. Confirm a read_committed consumer sees
none of those records — while a consumer left on the default
read_uncommitted sees them anyway. This is the crux of what
“transactional” buys you: it’s opt-in on the read side.
Simulate a crash mid-transaction: start a transaction, send a couple
of records, then kill the JVM (System.exit(1)) before calling
commit or abort. Restart a producer with the same
transactional.id and call initTransactions() — this is what
fences off and rolls back the previous incomplete transaction. Confirm
the abandoned records never become visible to a read_committed
consumer.
Discuss: this exercise only covered producer-side transactions
(“send exactly once”). True end-to-end exactly-once — consume,
process, and produce as one atomic unit — needs the consume offsets
committed as part of the same transaction
(producer.sendOffsetsToTransaction). That combination is exactly
what Kafka Streams does for you automatically, which is one of the
main reasons applications reach for it once their pipeline gets past
“one input topic, one output topic.”