Acknowledgement #
Dalam arsitektur sistem terdistribusi, pengiriman data yang andal memerlukan konfirmasi penerimaan yang jelas antara pengirim (producer) dan penerima (broker). Di Apache Kafka, jaminan ini dikendalikan oleh konfigurasi yang disebut Acknowledgement (ACKs). Parameter ini menetapkan seberapa yakin produser harus merasa bahwa pesan yang dikirimnya telah benar-benar tersimpan dengan aman di kluster broker sebelum ia dapat melanjutkan pengiriman pesan berikutnya. Memilih nilai ACKs yang tepat adalah keputusan arsitektural yang kritis, karena ia menentukan titik kompromi (trade-off) paling fundamental dalam sistem terdistribusi: kecepatan (latency/throughput) melawan ketahanan data (durability).
Membedah Tiga Level Konfigurasi acks
#
Produser Kafka menyediakan konfigurasi acks di tingkat klien yang mendukung tiga opsi nilai: 0, 1, dan all (atau -1). Masing-masing opsi menetapkan kriteria keberhasilan yang sangat berbeda di sisi broker.
1. acks=0 (Fire-and-Forget)
#
Ketika kita menetapkan acks=0, produser akan menganggap pesan berhasil terkirim segera setelah data ditulis ke jaringan socket transport, bahkan sebelum broker menerima data tersebut. Produser tidak menunggu tanggapan atau konfirmasi apa pun dari broker.
- Cara Kerja: Produser mengirimkan record ke broker dan langsung melanjutkan ke record berikutnya tanpa memedulikan apakah broker sedang mati, disk penuh, atau terjadi gangguan jaringan.
- Keunggulan: Memiliki latensi terendah dan throughput tertinggi karena tidak ada waktu tunggu jaringan untuk paket balasan (RTT - Round Trip Time dieliminasi dari siklus penulisan).
- Kelemahan & Risiko: Risiko kehilangan data sangat tinggi. Jika terjadi kegagalan pada broker penerima, produser tidak akan pernah tahu dan data yang hilang tidak akan pernah dikirim ulang (retry tidak akan dipicu karena produser berasumsi pengiriman selalu sukses).
- Use Case: Pengumpulan data sensor IoT frekuensi tinggi, pelacakan koordinat GPS armada secara real-time, atau log telemetri di mana hilangnya beberapa pesan tidak merusak integritas bisnis sistem secara keseluruhan.
2. acks=1 (Leader Acknowledgement)
#
Nilai acks=1 adalah titik tengah yang menyeimbangkan antara kecepatan dan keamanan. Dalam mode ini, produser akan menunggu konfirmasi sukses hanya dari broker yang bertindak sebagai Leader dari partisi tujuan.
- Cara Kerja: Leader broker menerima pesan dari produser, menulis pesan tersebut ke berkas commit log lokal miliknya sendiri, lalu langsung mengirimkan ACK sukses ke produser. Replikasi ke broker-broker pengikut (followers) terjadi setelahnya secara asinkron.
- Keunggulan: Latensi relatif rendah karena produser hanya perlu menunggu satu broker lokal menulis data ke disk/page cache sebelum melanjutkan.
- Kelemahan & Risiko: Ada celah risiko kehilangan data (data loss window). Jika leader broker mengalami kegagalan fisik (crash/hardware failure) mendadak sesaat setelah ia mengirim ACK ke produser, tetapi sebelum para follower broker sempat mengambil (fetch) pesan baru tersebut, maka data tersebut akan hilang. Ketika salah satu follower dipilih menjadi leader baru, data tersebut tidak akan pernah ditemukan.
- Use Case: Log aktivitas web pengguna, metrik aplikasi bisnis, atau pelacakan analytics di mana keandalan tinggi dibutuhkan namun sedikit kehilangan data akibat kegagalan total server masih dapat ditoleransi.
3. acks=all atau acks=-1 (Full Quorum Acknowledgement)
#
Mode acks=all (atau acks=-1) menawarkan tingkat durabilitas data tertinggi. Produser akan menunggu hingga pesan ditulis tidak hanya oleh leader, tetapi juga oleh seluruh replika yang aktif dalam kelompok In-Sync Replicas (ISR).
- Cara Kerja: Leader broker menerima pesan dari produser, menulisnya ke log lokal, lalu menunggu follower yang terdaftar dalam ISR untuk mengambil dan menulis data tersebut ke log mereka sendiri. Setelah kuorum ISR terpenuhi, leader baru mengirimkan ACK sukses ke produser.
- Keunggulan: Jaminan durabilitas mutlak. Selama minimal ada satu replika cadangan yang aktif dalam ISR, data dijamin tidak akan hilang meskipun leader broker hancur secara fisik.
- Kelemahan & Risiko: Latensi penulisan paling tinggi karena produser harus menunggu beberapa putaran komunikasi jaringan dan operasi I/O dari beberapa broker sekaligus sebelum mendapatkan konfirmasi.
- Use Case: Transaksi keuangan, sistem pemrosesan pesanan e-commerce, pencatatan mutasi saldo bank, log audit kepatuhan, atau skenario apa pun yang menerapkan kebijakan tanpa toleransi kehilangan data (zero data-loss policy).
Alur Visual Proses Acknowledgement #
Untuk memahami perbedaan latensi dan titik aman masing-masing konfigurasi, mari kita bandingkan siklus hidup pesan dari produser hingga broker melalui diagram sekuensial berikut:
flowchart TD
subgraph Skenario_Acks0 ["Skenario acks=0 (Fire-and-Forget)"]
direction TB
P0["Producer"] -->|"Kirim Pesan (Tanpa Menunggu)"| L0["Leader Broker"]
P0 -. "Langsung kirim pesan berikutnya" .-> P0
end
subgraph Skenario_Acks1 ["Skenario acks=1 (Leader Only)"]
direction TB
P1["Producer"] -->|"Kirim Pesan"| L1["Leader Broker"]
L1 -->|"Tulis ke Log Lokal"| L1
L1 -->|"Kirim ACK (Sukses)"| P1
L1 -.->|"Replikasi (Asinkron)"| F1["Follower Broker"]
end
subgraph Skenario_AcksAll ["Skenario acks=all (ISR Quorum)"]
direction TB
P2["Producer"] -->|"Kirim Pesan"| L2["Leader Broker"]
L2 -->|"Tulis ke Log Lokal"| L2
F2["Follower Broker"] -->|"Tarik Data (Fetch)"| L2
F2 -->|"Tulis ke Log Follower"| F2
F2 -->|"Kirim LEO Update"| L2
L2 -->|"Kirim ACK setelah Quorum ISR"| P2
end
Anatomi Internal Broker: Bagaimana acks=all Diproses?
#
Ketika produser mengirimkan permintaan tulis (produce request) dengan konfigurasi acks=all, broker tidak memprosesnya secara sederhana di satu thread saja. Di balik layar, Kafka menggunakan struktur data asinkron berbasis peristiwa (event-driven asynchronous data structure) yang memanfaatkan komponen bernama DelayedProduce.
Alur Kerja Delayed Operation purgatory #
Untuk memahami bagaimana Kafka mengelola penundaan konfirmasi tanpa memblokir thread I/O utama, kita harus melihat mekanisme purgatory di broker:
- Penerimaan Request: Klien produser mengirim pesan ke broker leader. Thread I/O broker (
KafkaRequestHandler) membaca pesan dari socket dan menulis data tersebut ke file log lokal dari leader partisi. - Pengecekan ISR: Broker leader memeriksa apakah status replikasi partisi saat ini sudah memenuhi kriteria. Jika seluruh follower yang terdaftar dalam ISR sudah memiliki offset yang sama (atau lebih tinggi) dari pesan baru ini, leader akan langsung membalas dengan sukses. Namun, hal ini jarang terjadi secara instan.
- Penyimpanan di Purgatory: Jika follower belum melakukan sinkronisasi, leader akan membungkus permintaan tersebut ke dalam sebuah objek
DelayedProducedan menyimpannya di dalam Operation Purgatory (ruang tunggu khusus untuk operasi tertunda). - Replica Fetcher: Broker follower terus mengirimkan permintaan tarik data (fetch request) ke leader secara berkala. Ketika follower berhasil menyalin pesan baru, ia akan memperbarui nilai Log End Offset (LEO) miliknya.
- Evaluasi Ulang: Begitu leader mendeteksi bahwa LEO dari semua follower dalam ISR telah melampaui offset pesan tersebut, operasi
DelayedProducedi purgatory dinyatakan selesai (completed). - Pengiriman ACK: Thread pemroses mengambil kembali operasi yang telah selesai dari purgatory dan mengirimkan paket respon ACK sukses ke produser.
Mekanisme purgatory ini sangat krusial karena ia memastikan thread handler I/O utama dari broker tidak pernah tertahan (blocked) menunggu aktivitas jaringan follower. Broker tetap bebas melayani request masuk lainnya dari klien-klien lain.
Aliansi Kritis: acks=all dan min.insync.replicas
#
Menetapkan konfigurasi acks=all di sisi produser barulah setengah langkah untuk mengamankan data kita. Jaminan durabilitas penuh baru akan terbentuk jika dikolaborasikan dengan konfigurasi di sisi broker/topik yang disebut min.insync.replicas.
Jebakan Konfigurasi ISR Tunggal #
Bayangkan kita memiliki sebuah topik dengan replication.factor=3 dan produser mengirim data dengan acks=all. Secara default, nilai konfigurasi min.insync.replicas di Kafka adalah 1.
Mari kita lihat apa yang terjadi saat bencana melanda:
- Dua broker follower mengalami kegagalan jaringan dan keluar dari keanggotaan ISR. Sisa anggota ISR kini hanya tinggal 1 broker (yaitu sang leader itu sendiri).
- Produser mengirim pesan dengan
acks=all. - Leader menerima pesan, menulisnya secara lokal, dan melihat bahwa seluruh anggota ISR saat ini (yang kebetulan hanya dirinya sendiri) telah menulis data.
- Karena kriteria “seluruh anggota ISR” terpenuhi, leader langsung mengirimkan ACK sukses ke produser.
- Sesaat kemudian, leader broker tersebut mati mendadak sebelum dua follower pulih.
Dalam skenario ini, meskipun kita menggunakan acks=all, data tetap hilang! Hal ini terjadi karena kriteria ISR menyusut hingga hanya menyisakan satu broker saja, sehingga perilaku acks=all diturunkan secara otomatis menjadi setara dengan acks=1.
Konfigurasi Standar Industri untuk Keamanan Maksimal #
Untuk mencegah skenario di atas, kita harus menyetel batas minimum jumlah replika aktif yang harus ada di dalam ISR agar operasi penulisan diperbolehkan. Konfigurasi yang aman adalah sebagai berikut:
# Properti Topik (atau Kluster)
min.insync.replicas=2
# Properti Produser
acks=all
Dengan konfigurasi ini, jika jumlah replika aktif di dalam ISR turun di bawah 2 (misalnya, dua dari tiga broker mati), maka leader broker akan langsung menolak pesan baru yang dikirim oleh produser dan membalasnya dengan error NotEnoughReplicasException atau NotEnoughReplicasAfterAppendException.
Sistem kita akan mendeteksi penolakan ini dan menangguhkannya untuk mencegah penulisan data yang tidak dapat direplikasi dengan aman. Ini adalah contoh nyata di mana kita lebih memilih ketidaktersediaan sistem tulis (write unavailability) demi menjaga konsistensi dan durabilitas data.
Studi Kasus Kegagalan: Outage Payment Gateway dengan acks=1
#
Mari kita bedah sebuah insiden dunia nyata yang sering terjadi akibat salah penyetelan parameter ACKs pada arsitektur e-commerce berskala besar.
Skenario Sistem #
- Topik:
payment-transactions - Replication Factor:
3 - Konfigurasi Awal:
acks=1(Leader Acknowledgement) - Kejadian:
- Klien mengirim transaksi pembayaran senilai Rp10.000.000.
- Broker 1 (Leader) menerima transaksi tersebut, menulisnya ke log lokal, dan mengirimkan ACK sukses ke produser.
- Aplikasi payment gateway menerima ACK tersebut, mencatat status transaksi sebagai “PAID”, dan menampilkan halaman sukses ke pelanggan.
- Tepat 20 milidetik kemudian, sebelum Broker 2 dan Broker 3 (Follower) sempat menarik data transaksi tersebut, Broker 1 mengalami kegagalan perangkat keras (kernel panic akibat kegagalan RAM).
- Pengawas kluster (KRaft Controller) mendeteksi kematian Broker 1 dan memilih Broker 2 sebagai Leader baru untuk partisi tersebut.
- Karena Broker 2 tidak sempat menerima replika transaksi tersebut sebelum Broker 1 mati, transaksi senilai Rp10.000.000 tersebut hilang secara permanen dari kluster Kafka.
Dampak Bisnis #
- Ketidaksesuaian Data: Pelanggan telah kehilangan uang mereka karena sistem eksternal menyatakan pembayaran sukses, namun sistem pemrosesan pesanan (order processor) yang membaca dari Kafka tidak pernah melihat event pembayaran tersebut sehingga pesanan tidak dikirimkan.
- Kerugian Finansial & Reputasi: Tim operasional harus melakukan rekonsiliasi manual yang memakan waktu berhari-hari untuk mencocokkan log database eksternal dengan log Kafka guna menemukan transaksi yang hilang.
Solusi Rekayasa #
Tim arsitek mengubah konfigurasi menjadi:
- Produser:
acks=all - Topik:
min.insync.replicas=2
Kini, jika kejadian yang sama terulang, transaksi tidak akan dilaporkan sukses ke pelanggan kecuali minimal ada 2 broker yang memegang salinan datanya. Jika salah satu mati, broker yang tersisa dijamin memiliki data yang identik.
Analisis Komparasi Kualitatif #
Untuk membantu kita memilih konfigurasi yang paling relevan dengan kebutuhan bisnis, tabel berikut merangkum karakteristik kinerja dari masing-masing level acks:
| Parameter | acks=0 |
acks=1 |
acks=all (dengan min.insync.replicas=2) |
|---|---|---|---|
| Jaminan Durabilitas | Sangat Rendah | Sedang | Sangat Tinggi |
| Resiko Kehilangan Data | Sangat Tinggi (Bila Broker/Jaringan Fail) | Rendah-Sedang (Bila Leader Crash Sebelum Replikasi) | Hampir Nol (Selama Kuorum Broker Terjaga) |
| Latensi Pengiriman | Sangat Rendah (< 1ms) | Rendah (2-5ms) | Tinggi (10-50ms) |
| Throughput Maksimal | Maksimal | Tinggi | Sedang-Tinggi (Bergantung Kecepatan Follower) |
| Perilaku Saat Rerata Fail | Abaikan error | Picu Retry otomatis | Picu Retry otomatis / Blokir bila ISR < Min |
Penanganan Kegagalan & Kode Error pada Sisi Klien #
Ketika produser menggunakan parameter acks=1 atau acks=all, ia harus siap menangani berbagai respon kesalahan yang dilemparkan oleh broker jika terjadi kegagalan penulisan. Berikut adalah beberapa kode pengecualian (exceptions) utama yang harus dikelola oleh kode aplikasi kita:
1. NotEnoughReplicasException
#
Pengecualian ini dilemparkan oleh broker leader ketika ia menerima permintaan tulis dengan acks=all, namun jumlah replika aktif saat ini di dalam ISR kurang dari nilai parameter min.insync.replicas.
- Sebab: Broker follower mengalami crash, lag yang parah, atau masalah koneksi jaringan antar-broker, menyisakan anggota ISR di bawah batas aman.
- Solusi Aplikasi: Produser harus menangkap error ini, menghentikan sementara pengiriman data ke partisi tersebut, dan memicu mekanisme peringatan (alerting) karena kesehatan kluster sedang dalam kondisi kritis.
2. NotEnoughReplicasAfterAppendException
#
Hampir serupa dengan error sebelumnya, namun terjadi setelah leader berhasil menulis pesan ke log lokalnya sebelum menyadari bahwa replika tidak dapat disinkronisasikan ke jumlah minimum follower dalam ISR.
- Sebab: Kematian mendadak dari follower tepat saat penulisan lokal leader sedang berlangsung.
- Solusi Aplikasi: Error ini memerlukan pengiriman ulang (retry) yang idempoten untuk memastikan data tidak terduplikasi ketika kluster pulih.
Contoh Implementasi Penanganan Error di Java #
Berikut adalah contoh bagaimana kita menulis kode produser yang tangguh di Java untuk menangani kesalahan-kesalahan arsitektural di atas menggunakan callback asinkron:
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.errors.NotEnoughReplicasException;
import org.apache.kafka.common.errors.TimeoutException;
public class HighDurabilityProducer {
private final KafkaProducer<String, String> producer;
public HighDurabilityProducer(Properties configs) {
this.producer = new KafkaProducer<>(configs);
}
public void sendTransaction(String key, String transactionData) {
ProducerRecord<String, String> record = new ProducerRecord<>("payment-transactions", key, transactionData);
producer.send(record, (RecordMetadata metadata, Exception exception) -> {
if (exception != null) {
// Memeriksa jenis error secara spesifik
if (exception instanceof NotEnoughReplicasException) {
System.err.printf("[DANGER] Gagal menulis transaksi %s. Jumlah replika ISR kurang dari batas minimum!\n", key);
logToAlternativeStorage(key, transactionData); // Amankan data ke media penyimpanan lokal (fallback)
} else if (exception instanceof TimeoutException) {
System.err.printf("[WARNING] Timeout saat menunggu ACK untuk transaksi %s. Picu retry ulang.\n", key);
} else {
System.err.printf("[ERROR] Gagal mengirim pesan. Alasan: %s\n", exception.getMessage());
}
} else {
System.out.printf("[SUCCESS] Transaksi %s berhasil dicatat pada partisi %d, offset %d\n",
key, metadata.partition(), metadata.offset());
}
});
}
private void logToAlternativeStorage(String key, String data) {
// Implementasikan penyimpanan lokal sementara (misalnya, SQLite lokal atau disk log)
// agar data tidak hilang ketika kluster Kafka menolak penulisan.
}
}
Contoh Properti Konfigurasi Klien Lengkap #
Berikut adalah dua contoh profil konfigurasi produser dalam Java Properties yang dioptimalkan untuk dua skenario ekstrem yang berbeda.
1. Profil Transaksi Keuangan (Durabilitas Mutlak, Tanpa Toleransi Kehilangan Data) #
Gunakan profil ini untuk modul-modul pembayaran, pencatatan transaksi ledger, pemrosesan pesanan barang, atau sinkronisasi database komparatif.
# Menuntut pengakuan dari seluruh kuorum replika aktif
acks=all
# Mengaktifkan produser idempoten untuk mencegah duplikasi data akibat retry
enable.idempotence=true
# Batas percobaan pengiriman ulang tak terbatas (retry terus hingga berhasil)
retries=2147483647
# Menjaga urutan pengiriman paralel tetap aman
max.in.flight.requests.per.connection=5
# Batas waktu tunggu pengakuan dari broker (30 detik)
request.timeout.ms=30000
2. Profil Telemetri IoT (Throughput Maksimal, Toleransi Kehilangan Data Tinggi) #
Gunakan profil ini untuk penyerapan metrik infrastruktur server, logs debug yang tidak kritis, pelacakan pergerakan mouse pengguna (clickstream), atau pembacaan sensor cuaca berkala.
# Mode fire-and-forget untuk kecepatan maksimal
acks=0
# Mematikan idempotensi karena tidak menunggu ACK (mengurangi overhead memori)
enable.idempotence=false
# Mengurangi retry karena jika gagal, pesan berikutnya lebih berharga daripada pesan lama
retries=0
# Mengoptimalkan kompresi untuk throughput besar
compression.type=lz4
# Mengumpulkan pesan sebanyak mungkin sebelum dikirim
linger.ms=20
batch.size=65536
Ringkasan #
- Parameter
acks: Berfungsi mengontrol kriteria sukses pengiriman pesan dari produser ke kluster Kafka berdasarkan konfirmasi broker.- Pilihan Nilai:
acks=0: Kecepatan maksimal, tanpa verifikasi, risiko kehilangan data tertinggi.acks=1: Kecepatan sedang, verifikasi satu broker (leader), risiko kehilangan data jika leader crash sebelum replikasi.acks=all: Keamanan maksimal, verifikasi seluruh kuorum ISR, latensi paling tinggi.- Mekanisme Purgatory: Broker menggunakan operasi tertunda (
DelayedProduce) untuk menunggu replikasi follower secara asinkron tanpa memblokir thread pemroses utama.- Sinergi Penting: Konfigurasi
acks=alltidak berdaya melindungi data jikamin.insync.replicastidak diset secara disiplin (disarankan minimal2pada replikasi faktor3).- Kompromi Bisnis: Pilih level
acksberdasarkan sensitivitas data bisnis kita; jangan memaksakanacks=alluntuk log aplikasi biasa, dan jangan gunakanacks=1untuk transaksi keuangan.