Episode ini membahas Kafka transactions: lifecycle initTransactions, beginTransaction, send, commitTransaction, dan abortTransaction. Kalian juga akan mempelajari pola read-process-write, konfigurasi transactional.id, transaction.timeout.ms, isolation.level, serta trade-off performanya.

Pada episode 8 kalian sudah memahami delivery semantics dan posisi exactly-once dalam spektrum at-most-once dan at-least-once. Sekarang waktunya mempraktikkan bagian yang paling teknis: Kafka transactions — mekanisme yang membuat pengiriman record ke beberapa partition menjadi atomic.
Transaksi Kafka menyelesaikan masalah yang tidak bisa ditangani idempotent producer sendirian. Idempotent producer mencegah duplikasi dalam satu partition, tetapi tidak membuat beberapa tulis ke partition atau topic berbeda menjadi all-or-nothing. Transaksi menutup celah itu dengan memperkenalkan transactional id dan transaction coordinator.
Episode 9 ini akan membawa kalian melalui lima method inti transaction API, pola read-process-write yang menjadi tulang punggung stream processing, konfigurasi yang mengatur perilaku transaksi, serta biaya performa yang harus kalian pertimbangkan sebelum memutuskan memakai transaksi.
Transaksi Kafka dijalankan melalui lima method pada KafkaProducer yang mengikuti urutan ketat:
producer.initTransactions();
producer.beginTransaction();
producer.send(new ProducerRecord<>("orders", "order-001"));
producer.send(new ProducerRecord<>("order-events", "order-001"));
producer.commitTransaction();transactional.id ke coordinator.Setiap transaksi harus diakhiri dengan commit atau abort. Membiarkan transaksi menggantung akan membuat coordinator menunda (timeout) dan membatalkannya secara otomatis.
Jangan pernah membiarkan exception membawa transaksi dalam status tidak jelas:
producer.initTransactions();
try {
producer.beginTransaction();
producer.send(new ProducerRecord<>("orders", "order-001", "created"));
producer.commitTransaction();
} catch (ProducerFencedException | OutOfOrderSequenceException e) {
producer.close();
} catch (KafkaException e) {
producer.abortTransaction();
}Jika transaksi difence (producer lain memakai transactional.id yang sama), instance lama harus ditutup. Untuk error lain, abortTransaction() membersihkan status agar coordinator tidak menahan resource.
Pola paling umum: consumer membaca record, aplikasi memprosesnya, lalu producer menulis hasilnya — semuanya dalam satu transaksi. Hasil proses dan offset konsumsi dikirim bersama, sehingga rollback salah satunya membatalkan keduanya:
consumer.subscribe(List.of("input-topic"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
producer.beginTransaction();
for (ConsumerRecord<String, String> record : records) {
producer.send(new ProducerRecord<>("output-topic", record.key(), "processed:" + record.value()));
producer.sendOffsetsToTransaction(currentOffsets(records), consumer.groupMetadata());
}
producer.commitTransaction();
}Perhatikan sendOffsetsToTransaction: offset dikirim sebagai bagian transaksi, sehingga konsumsi ulang tidak terjadi ketika proses gagal — inilah dasar end-to-end exactly-once di Kafka Streams.
Satu transaksi bisa menulis ke partition dan topic mana pun. Misalnya, service order menulis ke orders dan inventory-updates sekaligus; keduanya terlihat oleh consumer read_committed hanya ketika keduanya berhasil di-commit. Kegagalan di salah satu akan meng-abort semuanya.
Pada stream processing, transaksi menggabungkan tulis state internal (changelog) dengan tulis output. Kafka Streams memakai pola ini secara otomatis dengan processing.guarantee=exactly_once_v2 — kalian tidak perlu menulis kode transaksi manual, library yang menanganinya.
transactional.id harus unik dan stabil per instance aplikasi. Nilai yang stabil memungkinkan coordinator mendeteksi producer lama yang sudah mati dan menolak tulisannya (fencing):
transactional.id=order-processor-1
transaction.timeout.ms=60000
enable.idempotence=true
acks=allJika dua instance memakai transactional.id yang sama secara bersamaan, instance kedua akan mencekal (fencing) yang pertama — mencegah dua proses menulis secara simpang siur. Karena itu pastikan transactional.id unik per instance, misalnya order-processor-1 untuk node pertama dan order-processor-2 untuk node kedua.
Consumer memilih apakah transaksi yang belum selesai boleh dibaca:
Konsistensi end-to-end hanya tercapai bila producer memakai transaksi dan consumer memakai isolation.level=read_committed secara bersamaan.
Transaksi bukan sesuatu yang gratis. Setiap transaksi melibatkan setidaknya satu round-trip tambahan ke transaction coordinator, dan commit membutuhkan penulisan marker di topic internal __transaction_state. Dampaknya:
Jangan membungkus setiap record dalam transaksi sendiri. Praktik terbaik:
transaction.count dan metrik coordinator untuk mendeteksi transaksi yang menggantung.Warning
Transaksi Kafka menjaga atomicity di dalam Kafka. Jika aplikasi juga menulis ke database relasional, Kafka tidak menjamin atomicity dengan tulis database tersebut — untuk itu gunakan pola transactional outbox yang akan dibahas di episode 26.
Di episode 9 ini kalian sudah menjalankan lima method transaction API, memahami pola read-process-write dengan sendOffsetsToTransaction, mengonfigurasi transactional.id dan isolation.level, serta menimbang biaya throughput dan latensi dari transaksi.
Inti yang harus dibawa pulang:
initTransactions, beginTransaction, send, lalu commit atau abort.sendOffsetsToTransaction mengikat offset konsumsi ke transaksi untuk EOS end-to-end.transactional.id harus unik dan stabil; fencing mencegah dua instance menulis bersamaan.isolation.level=read_committed untuk konsistensi penuh.Di episode 10 selanjutnya kita akan membahas log compaction — kebalikan dari retention delete. Kalian akan belajar cara Kafka mempertahankan nilai terbaru per key dengan tombstone, parameter min.compaction.lag.ms, max.compaction.lag.ms, dan min.cleanable.dirty.ratio, serta use case changelog topic, materialized views, dan state store. Transaksi dan compaction adalah dua fondasi penyimpanan state Kafka yang akan terus kalian temui.