Ketahanan producer

Konfigurasi acks, retries, dan enable.idempotence pada producer beserta trade-off ketahanannya.

Daftar isi

Ketahanan producer menentukan apakah pesan yang sudah dikirim dapat hilang, terduplikasi, atau tiba dengan urutan yang berubah ketika jaringan dan broker bermasalah. Topik ini membahas tiga tingkat konfirmasi pada konfigurasi acks, mekanisme pengiriman ulang beserta batas waktunya, dan pengiriman idempoten yang menekan duplikat sejak Kafka 0.11. Demo Python pada bagian akhir membandingkan producer dengan acks=1 dan producer idempoten pada topic yang sama.

Tiga tingkat konfirmasi acks#

Konfigurasi acks menetapkan jumlah konfirmasi yang ditunggu producer dari leader partisi sebelum satu pengiriman dianggap selesai. Nilai defaultnya adalah all pada klien modern karena pengiriman idempoten yang aktif secara default menuntutnya. Nilai lain hanya digunakan ketika kecepatan jauh lebih penting daripada keamanan data, atau pada klien lama yang belum mendukung idempotence.

Nilai acksYang ditungguKonsekuensi
0Tidak adaPaling cepat, pesan dapat hilang tanpa diketahui, konfigurasi retries tidak berlaku, dan offset yang dilaporkan selalu -1.
1Konfirmasi leader setelah pesan dicatat di lognyaCepat, tetapi pesan yang sudah dikonfirmasi dapat hilang bila leader gagal sebelum follower menyalinnya.
allKonfirmasi seluruh salinan di ISRPaling tahan hilang, tetapi dapat ditolak bila ISR menyusut di bawah min.insync.replicas.

Skenario kehilangannya berbeda pada setiap tingkat. Dengan acks=0, producer langsung menganggap pesan terkirim begitu nilainya masuk ke penyangga jaringan, sehingga kegagalan broker atau jaringan tidak pernah diketahui dan tidak ada pengiriman ulang. Dengan acks=1, leader mencatat pesan ke log lokalnya lalu menjawab tanpa menunggu follower, sehingga kegagalan leader sebelum salinan diterima membuat pesan itu hilang meskipun producer sudah menerima konfirmasi. Dengan acks=all, pesan tidak hilang selama minimal satu salinan di ISR masih hidup, dan kegagalan yang tampak hanya terjadi ketika ISR memang lebih kecil dari min.insync.replicas yang dipersyaratkan topic.

Nilai all tetap bergantung pada konfigurasi topic yang dibahas pada topik Replikasi. Topic dengan min.insync.replicas=1 memenuhi konfirmasi all hanya dengan leader, sehingga jaminannya turun menjadi setara dengan acks=1, sedangkan topic dengan dua salinan sinkron minimum benar-benar menunggu lebih dari satu salinan sebelum konfirmasi diberikan. Ketahanan akhir sebuah tulisan karenanya ditentukan oleh kombinasi konfigurasi producer dan konfigurasi topic, bukan oleh salah satunya saja.

Pengiriman ulang dan batas waktunya#

Error yang bersifat sementara, misalnya koneksi ke broker yang terputus atau partisi yang sedang berpindah leader, ditangani producer dengan pengiriman ulang otomatis. Pada klien modern, jumlah ulangan default-nya sangat besar, yaitu 2147483647 pada klien Java, sehingga jumlah retries praktis tidak pernah menjadi kendala. Satu pesan terus diulang sampai pengirimannya berhasil, error yang muncul bersifat permanen, atau batas waktu pengiriman habis.

Batas waktu itu adalah delivery.timeout.ms, yaitu batas atas durasi sebelum producer melaporkan sukses atau gagal sejak pesan diserahkan ke send(). Durasi itu mencakup waktu menunggu di penyangga batch, waktu menunggu konfirmasi broker, dan waktu seluruh ulangan, dengan default 120000 milidetik atau dua menit. Nilainya sebaiknya lebih besar atau sama dengan jumlah request.timeout.ms dan linger.ms, karena keduanya adalah bagian dari waktu pengiriman itu sendiri. Dokumentasi resmi menyarankan pengaturan ulangan dilakukan lewat batas waktu ini, bukan lewat jumlah retries yang diperkecil.

Pengiriman ulang membawa dua risiko bila idempotence tidak aktif. Pesan yang diulang dapat tercatat dua kali di log karena broker tidak dapat membedakan ulangan dari pesan baru. Urutan juga dapat berubah bila max.in.flight.requests.per.connection diatur lebih dari 1, karena batch pertama yang gagal diulang sementara batch kedua yang berhasil sudah masuk lebih dulu. Dua risiko inilah yang diselesaikan mekanisme berikutnya.

Idempotence: satu salinan dan urutan terjaga#

Idempotence, yaitu jaminan bahwa satu pesan hanya tercatat satu kali di log, diaktifkan lewat enable.idempotence pada klien Java atau enable_idempotence pada kafka-python. Kedua klien modern menjadikannya default true, sehingga producer baru mendapatkan jaminan tanpa konfigurasi apa pun. Syarat yang menyertainya adalah acks=all, retries lebih besar dari 0, dan max.in.flight.requests.per.connection paling banyak 5, sehingga urutan pesan tetap terjaga pada rentang nilai tersebut.

Mekanismenya berada di broker, bukan di producer. Broker menetapkan sebuah pengenal producer saat sesi dimulai, kemudian producer melampirkan nomor urut pada setiap batch per partisi. Broker hanya menerima nomor urut yang berlanjut, sehingga ulangan batch yang sudah diterima ditolak tanpa mencatatnya kembali. Penolakan itu transparan bagi aplikasi, yang tetap menerima satu konfirmasi dengan offset tetap, sedangkan pengiriman ulang yang gagal di tengah tidak menimbulkan salinan kedua. Untuk urutan, producer menahan batch berikutnya bila batch sebelumnya masih termasuk dalam lima permintaan yang belum selesai.

Konflik konfigurasi ditangani dua cara, bergantung pada apakah idempotence ditetapkan secara eksplisit. Bila idempotence ditetapkan true bersama acks selain all, klien menolak konfigurasinya dengan error konfigurasi. Bila acks=1 diset tanpa menyebut idempotence sama sekali, klien mematikan idempotence secara diam-diam dan hanya meninggalkan peringatan di log, sehingga jaminan producer itu kembali lemah. Demo berikut menggunakan dua producer terpisah agar kedua keadaan tersebut dapat dibandingkan.

Demo dua producer pada topic yang sama#

Skrip berikut membuat topic unik m4-ketahanan-<epoch> dengan satu partisi, lalu menjalankan producer pertama dengan acks=1 dan producer kedua dengan enable_idempotence=True. Parameter kafka-python ditulis dengan garis bawah, sedangkan nilai konfigurasinya sama dengan versi Java yang dibahas di atas. Setiap pengiriman ditunggu dengan get() agar metadata offset yang ditetapkan broker tercetak per pesan, dan keluarannya memuat enam baris karena kedua producer masing-masing mengirim tiga pesan.

python ketahanan-demo.py
from kafka import KafkaProducer, KafkaAdminClient
from kafka.admin import NewTopic
from kafka.errors import TopicAlreadyExistsError
import time

topic = f"m4-ketahanan-{int(time.time())}"  # nama unik per eksekusi

admin = KafkaAdminClient(bootstrap_servers="localhost:9092")
try:
    admin.create_topics([NewTopic(name=topic, num_partitions=1, replication_factor=1)])
except TopicAlreadyExistsError:
    pass
admin.close()

# producer dengan acks=1: klien mematikan idempotence secara otomatis
producer = KafkaProducer(bootstrap_servers="localhost:9092", acks=1)
for i in range(3):
    info = producer.send(topic, value=f"bacaan-acks1-{i}".encode()).get(timeout=10)
    print(f"acks=1 terkirim offset={info.offset}")
producer.close()

# producer idempoten: acks otomatis menjadi all dan urutan terjaga
producer = KafkaProducer(bootstrap_servers="localhost:9092", enable_idempotence=True)
for i in range(3):
    info = producer.send(topic, value=f"bacaan-idem-{i}".encode()).get(timeout=10)
    print(f"idempoten terkirim offset={info.offset}")
producer.close()
text Keluaran
acks=1 terkirim offset=0
acks=1 terkirim offset=1
acks=1 terkirim offset=2
idempoten terkirim offset=3
idempoten terkirim offset=4
idempoten terkirim offset=5

Kedua producer mengisi partisi yang sama, sehingga offset berlanjut dari 0 sampai 5 tanpa ada yang dilewati. Topic dibuat ulang dengan nama yang berbeda pada setiap eksekusi, sehingga offset selalu dimulai dari 0 dan keluarannya tetap sama. Pada broker yang sehat keduanya tampil identik, karena perbedaannya baru terasa ketika kegagalan terjadi. Producer acks=1 menganggap pengiriman selesai di leader saja dan tidak menahan urutan ulangannya, sedangkan producer idempoten menunggu seluruh ISR dan broker menolak ulangan yang sudah diterima. Ketahanan yang lebih tinggi selalu dibayar dengan latensi tambahan untuk menunggu lebih banyak konfirmasi.

Kesalahan umum#

  1. Menyetel acks=1 bersama enable_idempotence=True pada satu producer menimbulkan error konfigurasi karena idempotence menuntut acks=all. Errornya muncul saat producer dibuat, bukan saat pesan pertama dikirim, sehingga aplikasi berhenti lebih awal. Gunakan acks="all" atau hapus acks dan biarkan default idempotence bekerja.
  2. Memperkecil retries demi kecepatan sementara delivery.timeout.ms tidak disentuh. Pengiriman berhenti lebih awal padahal error yang dihadapi masih bisa pulih dengan sendirinya, dan batas waktunya masih tersisa. Biarkan jumlah ulangan default dan kendalikan perilakunya lewat delivery.timeout.ms.
  3. Menganggap acks=all otomatis tahan kegagalan pada topic dengan min.insync.replicas=1. Jaminannya kembali setara dengan acks=1 karena satu salinan sinkron sudah memenuhi konfirmasi, dan salinan tambahan tanpa ambang yang tepat hanya menambah penggunaan penyimpanan. Selaraskan keduanya sejak merancang topic, seperti dibahas pada topik Replikasi.
  4. Memilih nilai acks hanya dari pengukuran latensi. Nilai 0 memang paling cepat, tetapi kegagalannya tidak dilaporkan, sehingga angka latensinya tidak mencerminkan risiko yang ditanggung. Pilih tingkat konfirmasi dari kebutuhan data, lalu ukur latensinya pada nilai yang sudah memadai.

Ringkasan#

  • acks menetapkan tingkat konfirmasi yang ditunggu producer, dari tidak menunggu apa pun (0), leader saja (1), sampai seluruh ISR (all).
  • retries pada klien modern tidak terbatas jumlahnya, sehingga batas pengiriman dikendalikan lewat delivery.timeout.ms yang sebaiknya lebih besar atau sama dengan request.timeout.ms ditambah linger.ms.
  • Pengiriman idempoten yang aktif secara default menjamin satu salinan per pesan dan urutan terjaga, dengan syarat acks=all, retries di atas 0, dan paling banyak 5 permintaan berjalan per koneksi.
  • Menyetel acks di luar all tanpa menyadari klien mematikan idempotence adalah kegagalan ketahanan yang paling sering tidak disengaja.

Referensi#