Acks, Retries, & Linger.ms #

Ketika kita membangun aplikasi klien produser untuk mengirimkan pesan ke Apache Kafka, performa bawaan (default configuration) yang disediakan oleh pustaka klien tidak selalu sesuai dengan kebutuhan sistem kita. Kafka dirancang untuk melayani berbagai jenis kebutuhan sistem yang sangat bertolak belakang: dari sistem pencatatan sensor IoT yang membutuhkan throughput jutaan pesan per detik tanpa memedulikan kehilangan sebagian kecil data, hingga sistem transaksi keuangan perbankan yang menuntut keandalan mutlak tanpa boleh kehilangan satu pun pesan, meskipun harus mengorbankan sedikit kecepatan. Dua pilar utama yang menentukan perilaku ini berada di tangan penyetelan parameter produser kita. Memahami cara melakukan tuning pada parameter ketahanan data seperti acks dan retries, serta parameter optimasi throughput seperti linger.ms dan batch.size adalah keterampilan wajib untuk merancang arsitektur data yang tangguh dan efisien.


Membedah Konfigurasi Ketahanan Data: acks #

Parameter acks (acknowledgement) mengontrol jumlah broker dalam kluster yang harus menerima pesan dan mengirimkan konfirmasi kembali ke produser sebelum pengiriman pesan tersebut dianggap sukses. Penyetelan parameter ini merupakan kompromi (trade-off) langsung antara tingkat ketahanan data (durability) dengan kecepatan pengiriman (latency).

Terdapat tiga nilai konfigurasi yang dapat kita berikan pada parameter acks:

1. acks=0 (Tanpa Konfirmasi) #

Produser dianggap telah sukses mengirimkan pesan segera setelah ia menuliskannya ke socket jaringan TCP. Produser tidak menunggu konfirmasi apa pun dari broker Kafka.

  • Kelebihan: Latensi transmisi sangat rendah dan throughput maksimal karena produser tidak terhambat oleh proses I/O broker.
  • Kekurangan: Risiko kehilangan data sangat tinggi. Jika broker tujuan mati, atau disk penuh tepat setelah pesan masuk ke socket, produser tidak akan pernah tahu bahwa pesan tersebut telah hilang dan akan tetap melanjutkan pengiriman berikutnya.
  • Use Case: Pengumpulan log server yang tidak kritis, pelacakan metrik real-time.

2. acks=1 (Hanya Konfirmasi Leader) #

Produser akan menunggu konfirmasi tertulis sukses hanya dari broker yang bertindak sebagai Leader partisi tujuan. Leader akan menulis data ke log komit lokalnya (menjamin penyimpanan ke disk) sebelum mengirimkan ACK sukses kembali ke produser.

  • Kelebihan: Memberikan keseimbangan moderat antara throughput yang cepat dengan jaminan ketahanan data yang wajar.
  • Kekurangan: Masih terdapat celah kehilangan data. Jika leader mati sesaat setelah mengirimkan ACK sukses ke produser, namun sebelum broker pengikut (Followers) sempat menduplikasi data tersebut via replikasi, data tersebut akan hilang secara permanen ketika salah satu follower yang tertinggal dipromosikan menjadi leader baru.
  • Use Case: Sistem tracking perilaku klik pengguna (clickstream), pelaporan statistik non-finansial.

3. acks=all atau acks=-1 (Konfirmasi Seluruh ISR) #

Produser baru akan menganggap pengiriman pesan sukses setelah leader partisi menerima konfirmasi dari seluruh replika yang tergabung dalam In-Sync Replicas (ISR).

  • Kelebihan: Keandalan data mutlak. Selama minimal satu broker ISR cadangan tetap hidup, pesan dijamin tidak akan hilang dari kluster.
  • Kekurangan: Latensi pengiriman tertinggi karena produser terikat oleh waktu sinkronisasi replikasi jaringan antar broker.
  • Use Case: Transaksi pembayaran finansial, pembuatan faktur invoice, catatan audit keamanan.

[!WARNING] Properti acks=all tidak memberikan jaminan keamanan data penuh jika parameter min.insync.replicas di sisi broker topik disetel ke nilai 1. Jika min.insync.replicas=1, maka meskipun produser meminta acks=all, broker leader akan langsung mengirimkan ACK sukses meskipun tidak ada follower aktif lainnya yang mereplikasi data tersebut (karena leader menilai dirinya sendiri sudah cukup memenuhi syarat minimum 1 ISR). Untuk keandalan finansial, selalu setel min.insync.replicas=2 pada broker dengan Replication Factor bernilai 3.


Mengatasi Gangguan Jaringan: retries dan Timeout Parameters #

Koneksi jaringan antar aplikasi produser dengan broker Kafka tidak selalu stabil. Adanya gangguan jaringan sesaat (network glitches), proses pemilihan leader baru (leader election), atau proses restart broker secara bergantian (rolling restart) dapat memicu kegagalan pengiriman pesan. Di sinilah peran parameter ketahanan error aktif bekerja.

1. Properti retries #

Parameter ini menentukan berapa kali produser akan mencoba mengirimkan kembali (retry) pesan yang gagal akibat masalah sementara (retriable errors seperti masalah timeout jaringan atau broker leader sedang berpindah).

  • Konfigurasi Modern: Pada klien produser Kafka versi terbaru, default dari retries telah disetel ke nilai maksimum integer (Integer.MAX_VALUE). Produser akan terus mencoba mengirim ulang pesan hingga batas durasi waktu keseluruhan habis.

2. Properti retry.backoff.ms #

Secara default, jika sebuah pengiriman gagal, produser akan segera melakukan retry. Namun, membombardir broker yang sedang sibuk atau mati dengan permintaan terus-menerus tanpa jeda dapat memperparah kondisi broker tersebut.

  • Cara Kerja: Parameter retry.backoff.ms (default: 100 ms) memberikan jeda istirahat (waktu tunggu) bagi produser sebelum melakukan upaya retry berikutnya, memberikan waktu bagi kluster broker untuk pulih.

3. Properti delivery.timeout.ms #

Ini adalah batas waktu total terpenting di sisi produser. Properti delivery.timeout.ms (default: 120.000 ms atau 2 menit) membatasi durasi total dari pemanggilan .send() hingga produser menerima ACK sukses atau menyerah dan melempar exception error.

  • Rumus Batas Waktu: Nilai delivery.timeout.ms harus selalu lebih besar atau sama dengan jumlah dari parameter request.timeout.ms dan linger.ms:

$$\text{delivery.timeout.ms} \ge \text{request.timeout.ms} + \text{linger.ms}$$

Jika pesan tidak kunjung menerima ACK sukses dalam rentang waktu default 2 menit (karena jaringan mati total), pesan tersebut akan dibuang dari antrean Record Accumulator dan aplikasi kita akan menerima kegagalan pengiriman.


Tuning Throughput vs Latensi: linger.ms dan batch.size #

Siklus pengiriman pesan secara asinkron di produser Kafka sangat bertumpu pada efisiensi pengelompokan pesan di dalam memori buffer Record Accumulator. Kita dapat menaikkan performa throughput aplikasi kita dengan mengatur dua konfigurasi berikut:

1. Properti batch.size #

Menentukan kapasitas ukuran memori maksimum (dalam satuan byte) yang dialokasikan untuk menampung pesan-pesan per partisi di dalam Record Accumulator. Defaultnya adalah 16.384 byte (16 KB).

  • Optimasi: Jika pesan-pesan kita berukuran besar atau throughput aplikasi kita sangat padat, menaikkan batch.size menjadi 64 KB atau 128 KB sangat disarankan agar buffer tidak terlalu cepat penuh dan memicu pengiriman paket jaringan kecil secara berulang.

2. Properti linger.ms #

Menentukan berapa lama (dalam milidetik) produser akan menunda pengiriman batch di memori sebelum diambil oleh Sender Thread. Secara default, properti ini disetel ke 0 ms (produser akan mengirimkan pesan sesegera mungkin tanpa menunggu pesan lain masuk ke batch yang sama).

  • Optimasi: Dengan memberikan sedikit waktu tunggu, misalnya linger.ms=20 (20 milidetik), kita memberi kesempatan bagi utas aplikasi utama untuk menumpuk lebih banyak pesan ke dalam batch yang sama sebelum Sender Thread mengirimkannya ke socket. Hal ini melipatgandakan throughput dengan menambah sedikit overhead latensi 20 milidetik.

Diagram Mermaid: Hubungan Linger.ms terhadap Transmisi Batch #

Berikut adalah visualisasi bagaimana parameter linger.ms dan batch.size berkolaborasi di dalam Record Accumulator untuk mengontrol keputusan Sender Thread sebelum mengirim data ke broker:

flowchart TD
    subgraph Accumulator ["Record Accumulator (Penyangga Memori)"]
        direction TB
        Rec1["Pesan 1 (T=0ms)"] --> B1["Batch Partisi 0"]
        Rec2["Pesan 2 (T=2ms)"] --> B1
        Rec3["Pesan 3 (T=5ms)"] --> B1
    end
    
    subgraph Decision ["Sender Thread Decision Loop"]
        direction TB
        Cond1{"Ukuran Batch >= batch.size?"}
        Cond2{"Waktu Tertahan >= linger.ms?"}
    end
    
    B1 --> Cond1
    Cond1 -- "Ya (Batch Penuh, e.g., 16KB)" --> SendNow["Kirim Segera ke Jaringan"]
    Cond1 -- "Tidak" --> Cond2
    
    Cond2 -- "Ya (e.g., linger.ms=20ms habis)" --> SendNow
    Cond2 -- "Tidak" --> Wait["Tahan di Buffer (Akumulasi Pesan Baru)"]
    
    SendNow --> Broker["Broker Kafka"]
    
    style Accumulator stroke:#e5e7eb
    style Decision stroke:#e5e7eb
    style B1 stroke:#0288d1,stroke-width:2px
    style SendNow stroke:#2e7d32,stroke-width:2px
    style Wait stroke:#f57c00,stroke-width:2px

Interaksi Max In-Flight Requests dengan Urutan Pesan (Ordering) #

Salah satu parameter terpenting yang sering kali terabaikan dalam merancang stabilitas produser adalah max.in.flight.requests.per.connection (default: 5). Parameter ini menentukan berapa banyak permintaan pengiriman (produce requests) tak bertuan (belum memperoleh ACK sukses dari broker) yang boleh dikirimkan produser melalui satu koneksi socket TCP secara bersamaan.

Mengapa Properti ini Memengaruhi Urutan Data? #

Jika properti ini disetel ke nilai yang lebih besar dari 1 (misalnya menggunakan nilai default 5) dan kita membiarkan enable.idempotence=false, kita berisiko mengalami masalah pengurutan pesan yang terbalik (out-of-order messages) apabila terjadi kegagalan jaringan sementara.

Mari kita simulasikan skenario kegagalan pengiriman berikut:

  1. Produser mengirim Batch A ke broker. Karena belum ada ACK, Batch A berstatus in-flight.
  2. Tanpa menunggu ACK Batch A, produser mengirim Batch B secara asinkron. Sekarang ada 2 request in-flight.
  3. Batch A gagal diterima broker karena terjadinya gangguan jaringan sementara (misalnya broker leader sedang sibuk melakukan pemilihan leader baru).
  4. Batch B sukses diterima broker dan berhasil ditulis ke log komit partisi.
  5. Produser menerima sinyal kegagalan untuk Batch A. Karena parameter retries aktif, produser mencoba mengirim ulang Batch A.
  6. Upaya pengiriman ulang Batch A sukses ditulis ke broker.
  • Hasil Akhir yang Merusak: Di dalam log komit broker, Batch B tertulis lebih dahulu daripada Batch A. Urutan pesan terbalik secara permanen!

Solusi Mengatasi Masalah Urutan Pesan #

  • Cara Lama (Sebelum Idempotency): Kita terpaksa menyetel max.in.flight.requests.per.connection=1. Konfigurasi ini memaksa produser bertindak secara sinkronis — menanti ACK Batch A sebelum Batch B boleh dikirim. Namun, ini membatasi throughput pengiriman karena bandwidth jaringan tidak pernah termanfaatkan secara penuh.
  • Cara Modern (Rekomendasi): Kita mempertahankan nilai max.in.flight.requests.per.connection antara 1 hingga 5, dan wajib menyetel enable.idempotence=true. Idempotensi menggunakan nomor urut (sequence number) untuk melacak urutan batch di sisi broker. Broker akan menolak menulis Batch B jika mendeteksi Batch A yang berurutan sebelumnya belum masuk, sehingga memaksa broker menyusun kembali batch yang berantakan secara benar sebelum ditulis ke disk.

Kompresi Data pada Produser: Gzip, Snappy, Lz4, vs Zstd #

Secara default, pesan dikirimkan dari produser ke broker dalam bentuk data mentah tidak terkompresi. Jika aplikasi kita memproses jutaan pesan berukuran sedang hingga besar setiap hari, mengaktifkan kompresi data via properti compression.type di sisi produser adalah langkah optimasi yang sangat signifikan.

Keuntungan Kompresi End-to-End Kafka #

Siklus kompresi di Kafka sangat efisien karena beroperasi secara end-to-end:

  1. Produser melakukan kompresi pada batch data.
  2. Data dikirimkan dalam bentuk biner terkompresi melalui jaringan, menghemat bandwidth.
  3. Broker menyimpan byte terkompresi secara langsung ke disk tanpa melakukan dekompresi (menghemat memori dan CPU broker).
  4. Konsumen menerima byte terkompresi dan melakukan dekompresi di sisi aplikasi mereka.

Perbandingan Algoritma Kompresi #

Tipe Kompresi Rasio Kompresi Kecepatan Kompresi Beban CPU Klien Karakteristik Utama
gzip Sangat Tinggi Lambat Tinggi Rasio terbaik, cocok untuk kompresi arsip data log dingin (cold data).
snappy Sedang Sangat Cepat Sangat Rendah Dikembangkan oleh Google, didesain untuk kecepatan I/O tinggi dengan beban CPU minimal.
lz4 Sedang Sangat Cepat Sangat Rendah Mirip dengan Snappy, sangat optimal untuk lalu lintas data real-time cepat.
zstd Tinggi Cepat Sedang Dikembangkan oleh Facebook, memberikan rasio mendekati Gzip dengan kecepatan mendekati Snappy.

Bagi sebagian besar aplikasi produksi umum, memilih snappy atau lz4 adalah keputusan teraman untuk meminimalkan beban CPU pada aplikasi produser kita, sementara zstd adalah pilihan ideal jika bandwidth jaringan kita sangat terbatas namun kita masih memiliki daya komputasi CPU cadangan yang memadai.


Implementasi Konfigurasi Java: High-Throughput vs High-Durability #

Sebagai arsitek sistem, kita harus mampu mengonfigurasi produser agar sesuai dengan karakteristik data bisnis kita. Berikut adalah kode perbandingan antara konfigurasi produser yang salah (anti-pattern) dengan konfigurasi optimal untuk dua use case yang berbeda:

// ANTI-PATTERN: Konfigurasi default atau campuran tidak terarah
// Prosedur transaksi keuangan yang tidak aman karena acks=1 dan retries dimatikan
public class NaiveFinancialProducer {
    public Properties getProperties() {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        
        // ✗ Sangat berbahaya untuk transaksi keuangan: data bisa hilang jika leader restart
        props.put("acks", "1"); 
        
        // ✗ Mematikan retry otomatis memaksa aplikasi menangani error transient secara manual
        props.put("retries", "0"); 
        
        // ✗ linger.ms terlalu tinggi tanpa alasan jelas di sistem transaksional sensitif latensi
        props.put("linger.ms", "5000"); 
        return props;
    }
}

// BENAR: Konfigurasi Skenario 1 - High Durability (Financial Grade, No Data Loss)
public class HighDurabilityProducer {
    public Properties getProperties() {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        
        // ✓ Keandalan Mutlak: Menunggu konfirmasi dari seluruh ISR
        props.put(ProducerConfig.ACKS_CONFIG, "all"); 
        
        // ✓ Lakukan retry otomatis hingga batas waktu habis demi menghindari kehilangan data transient
        props.put(ProducerConfig.RETRIES_CONFIG, Integer.MAX_VALUE);
        
        // ✓ Aktifkan idempotensi produser untuk mencegah duplikasi akibat retry jaringan
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true"); 
        
        // ✓ Batasi in-flight request ke 5 untuk menjamin urutan data dan kompatibilitas idempotensi
        props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, "5");
        
        // ✓ Batas waktu keseluruhan penyerahan record sebelum error dilempar ke aplikasi
        props.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, "120000"); // 2 menit
        return props;
    }
}

// BENAR: Konfigurasi Skenario 2 - High Throughput (Massive Logging/IoT Clickstream)
public class HighThroughputProducer {
    public Properties getProperties() {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        
        // ✓ Cukup tunggu konfirmasi leader untuk menghemat latensi transmisi RTT
        props.put(ProducerConfig.ACKS_CONFIG, "1"); 
        
        // ✓ Berikan jeda 20ms agar utas aplikasi dapat menumpuk pesan ke satu batch
        props.put(ProducerConfig.LINGER_MS_CONFIG, "20"); 
        
        // ✓ Naikkan kapasitas batch memori menjadi 64KB (dari default 16KB)
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, String.valueOf(64 * 1024)); 
        
        // ✓ Aktifkan kompresi data biner (misal: Snappy atau Zstd) untuk menghemat bandwidth
        props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy"); 
        
        // ✓ Alokasikan memori penyangga buffer pool yang lebih luas untuk menghindari backpressure
        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, String.valueOf(64 * 1024 * 1024)); // 64 MB
        return props;
    }
}

Rekomendasi Penyetelan untuk Berbagai Use Case #

Untuk mempermudah pengambilan keputusan saat merancang sistem di lingkungan produksi, berikut adalah matriks acuan parameter konfigurasi berdasarkan use case:

Use Case acks linger.ms compression.type enable.idempotence min.insync.replicas (broker)
E-Commerce Payment all 05 lz4 true 2
IoT Sensor Metrics 1 50100 snappy false 1
Log Aggregation 1 2050 zstd false 1
Audit Security Logs all 10 zstd true 2

Ringkasan #

  • ACKS Levels: Penyetelan properti acks mengontrol tingkat konfirmasi broker (0, 1, all) yang menentukan keseimbangan (trade-off) latensi vs keandalan data.
  • min.insync.replicas: Nilai acks=all di produser membutuhkan konfigurasi min.insync.replicas >= 2 di sisi broker untuk menjamin replikasi data ke pengikut (followers).
  • Linger & Batch: Menggabungkan parameter linger.ms (misal 20ms) dengan batch.size (misal 64KB) dapat memangkas latensi jaringan dan melipatgandakan throughput.
  • Idempotence: Selalu aktifkan enable.idempotence=true saat menggunakan retry tinggi untuk membuang salinan data duplikat secara aman di sisi broker.
  • Delivery Timeout: Parameter delivery.timeout.ms membatasi durasi toleransi produser melakukan upaya coba ulang sebelum melempar exception ke utas aplikasi utama.

← Sebelumnya: Key vs No-Key Message   Berikutnya: Idempotent Producer →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact