Leader & Follower #

Dalam sistem terdistribusi berskala besar, kegagalan perangkat keras bukanlah pertanyaan “apakah”, melainkan “kapan”. Sebuah server broker fisik dapat mengalami mati daya, kegagalan hard drive, atau isolasi jaringan kapan saja tanpa peringatan. Untuk memastikan kelangsungan aliran data bisnis kita tanpa hambatan, Apache Kafka merancang arsitektur replikasi partisi yang kuat berbasis model Leader & Follower. Setiap partisi topik di dalam kluster Kafka dijalankan secara redundan di beberapa broker. Salah satu replika ditunjuk sebagai Leader, sementara replika lainnya bertindak sebagai Follower. Memahami bagaimana kedua peran ini berinteraksi, bagaimana penanda Log End Offset (LEO) dan High Watermark (HW) membatasi visibilitas data konsumen, serta bagaimana protokol konsensus menangani peralihan kekuasaan secara otomatis saat terjadi kegagalan (failover) adalah kunci utama bagi kita untuk merancang sistem pemrosesan data dengan jaminan keandalan mutlak (zero data loss).

Peran Replika: Leader vs Follower #

Replikasi di Kafka dikonfigurasi pada tingkat topik melalui parameter replication.factor. Jika kita menyetel faktor replikasi sebesar 3, artinya setiap partisi dari topik tersebut akan memiliki 3 salinan fisik yang tersebar di 3 broker berbeda. Dari ketiga salinan tersebut, sistem akan membagi peran sebagai berikut:

1. Leader Replica #

Untuk setiap partisi, salah satu broker ditunjuk sebagai Leader. Broker ini bertindak sebagai pintu gerbang tunggal untuk semua aktivitas interaksi data.

  • Menerima Penulisan: Seluruh permintaan PRODUCE dari produsen aplikasi kita wajib dialamatkan dan ditulis langsung ke Leader.
  • Melayani Pembacaan: Secara bawaan (default), seluruh permintaan FETCH dari konsumen juga dilayani oleh Leader.

2. Follower Replica #

Broker-broker lain yang menyimpan salinan partisi yang sama bertindak sebagai Follower. Peran mereka sangat berbeda dengan konsep secondary database pada database relasional tradisional:

  • Penyalinan Pasif: Follower tidak melayani permintaan penulisan dari produsen. Tugas tunggal mereka adalah bertindak seperti konsumen internal. Mereka menjalankan thread latar belakang bernama ReplicaFetcherThread untuk secara konstan mengirimkan permintaan Fetch biner ke broker Leader guna menarik (pull) pesan-pesan terbaru dan menyalinnya ke log lokal mereka sendiri secara berurutan.
  • Warm Standby: Follower bersiaga penuh sebagai cadangan. Jika broker Leader mengalami crash, salah satu Follower sehat yang paling sinkron akan segera dipromosikan oleh kluster untuk menjadi Leader baru.

Fitur Modern: Follower Fetching (Read from Closest Replica) #

Mulai dari Kafka 2.4, Kafka memperkenalkan fitur optimasi jaringan yang disebut Follower Fetching. Pada kluster multi-zona (Multi-AZ), jika konsumen kita berada di Availability Zone (AZ) yang berbeda dengan broker Leader, mentransfer data melintasi batas zona akan memicu biaya transfer data lintas zona yang sangat mahal dari penyedia cloud.

Dengan mengaktifkan konfigurasi replica.selector.class di sisi broker dan menyetel konfigurasi klien konsumen ke lokasi rack/zone yang sama, konsumen diizinkan untuk membaca data langsung dari Follower terdekat yang berada di AZ yang sama. Hal ini secara dramatis memotong latensi jaringan dan menghemat biaya operasional infrastruktur kita.


Indikator Replikasi: Log End Offset (LEO) vs High Watermark (HW) #

Untuk mengoordinasikan proses sinkronisasi data antar-broker tanpa menggunakan penguncian transaksi (locking) yang lambat, Kafka menggunakan dua penunjuk numerik (offset pointers) yang sangat krusial:

1. Log End Offset (LEO) #

Log End Offset (LEO) adalah koordinat offset berikutnya yang akan ditulis ke dalam sebuah log fisik replika. LEO menandakan panjang total log pada replika tersebut pada satu waktu.

  • Setiap kali produsen berhasil menulis pesan baru ke Leader, nilai LEO pada Leader akan bertambah.
  • Setiap kali Follower berhasil menyalin pesan baru dari Leader ke disk lokalnya, nilai LEO pada Follower tersebut juga akan bertambah.
  • Nilai LEO Leader selalu menjadi yang tertinggi atau setara dengan LEO Follower yang paling sinkron.

2. High Watermark (HW) #

High Watermark (HW) adalah offset tertinggi di mana seluruh replika yang tergabung di dalam kelompok In-Sync Replicas (ISR) telah sukses menyalin pesan tersebut ke log masing-masing.

  • Nilai HW dihitung secara matematis oleh Leader sebagai nilai LEO terkecil di antara seluruh replika ISR yang aktif.
  • Batas Visibilitas Konsumen: Ini adalah aturan paling kritis di Kafka: Konsumen hanya diperbolehkan membaca pesan hingga batas High Watermark. Pesan yang berada di antara HW dan LEO Leader (pesan yang sudah ditulis ke Leader tetapi belum sepenuhnya disalin oleh seluruh follower ISR) dianggap belum berkomit (uncommitted) dan disembunyikan dari konsumen.

Mengapa pesan di atas HW disembunyikan dari konsumen?

Mari kita ambil skenario buruk: Produsen mengirim pesan dengan offset 5 ke Leader (Broker 1). LEO Broker 1 menjadi 6. Namun, sebelum Broker 2 (Follower) sempat menyalin pesan tersebut, Broker 1 mati mendadak. Jika konsumen diizinkan membaca offset 5 langsung saat tertulis di Leader, konsumen akan memproses data tersebut.

Kemudian, ketika Broker 2 dipromosikan menjadi Leader baru, pesan offset 5 tidak ada di log-nya. Ketika produsen mengirim pesan baru, pesan tersebut akan ditulis di offset 5 dengan konten yang berbeda. Konsumen kini akan melihat inkonsistensi data yang fatal (dirty read). Dengan membatasi pembacaan hanya sampai HW, Kafka menjamin bahwa pesan yang sudah dibaca oleh konsumen tidak akan pernah hilang atau berubah meskipun terjadi crash pada broker.


Diagram Sinkronisasi: Alur Perjalanan LEO dan HW #

Mari kita perhatikan visualisasi perbedaan posisi LEO dan HW di dalam log fisik Leader dan Follower ketika terjadi proses replikasi asinkron:

flowchart TD
    subgraph LeaderReplica ["Broker 1: Leader (Partition 0)"]
        direction TB
        L0["Msg 0 (Offset 0)"]
        L1["Msg 1 (Offset 1)"]
        L2["Msg 2 (Offset 2)"]
        L3["Msg 3 (Offset 3)"]
        
        style L0 fill:#ddffdd,stroke:#88ff88
        style L1 fill:#ddffdd,stroke:#88ff88
        style L2 fill:#ddffdd,stroke:#88ff88
        style L3 fill:#ffdddd,stroke:#ff8888
    end

    subgraph FollowerReplica ["Broker 2: Follower (Partition 0)"]
        direction TB
        F0["Msg 0 (Offset 0)"]
        F1["Msg 1 (Offset 1)"]
        F2["Msg 2 (Offset 2)"]
        
        style F0 fill:#ddffdd,stroke:#88ff88
        style F1 fill:#ddffdd,stroke:#88ff88
        style F2 fill:#ddffdd,stroke:#88ff88
    end

    Consumer["Klien Consumer"] -->|"Batas Baca (High Watermark = Offset 2)"| L2
    Producer["Klien Producer"] -->|"Tulis Data Baru (LEO = Offset 4)"| L3
    FollowerReplica -->|"Tarik Data (Fetch LEO = Offset 3)"| LeaderReplica

Pada diagram di atas, pesan pada Offset 3 (Msg 3) sudah ditulis di Leader (LEO = 4), tetapi karena Follower belum selesai menyalinnya (LEO Follower = 3), nilai High Watermark dikunci pada Offset 2. Konsumen belum bisa membaca Msg 3 hingga Follower sukses menyalinnya pada siklus fetch berikutnya.


Siklus Hidup Replikasi (Replica Fetcher Protocol) #

Proses sinkronisasi LEO dan pergeseran HW terjadi secara asinkron melalui siklus permintaan-tanggapan (request-response loop) yang dilakukan secara terus-menerus oleh thread Follower:

  1. Follower Mengirim Fetch Request: Thread ReplicaFetcherThread pada Follower mengirimkan permintaan biner ReplicaFetchRequest ke Leader. Di dalam permintaan ini, Follower menyertakan offset terakhir yang ia miliki di log lokalnya (misalnya, FetchOffset = 3).
  2. Leader Memperbarui LEO Follower: Saat Leader menerima permintaan tersebut, ia menyadari bahwa Follower tersebut telah sukses menyalin semua pesan hingga offset 2 (karena ia meminta mulai offset 3). Leader segera memperbarui catatan LEO internal untuk Follower tersebut menjadi 3 di dalam memori metadatanya.
  3. Leader Membaca Data & Mengirim Balasan: Leader membaca pesan baru mulai dari offset 3 dari Page Cache-nya, menyusunnya ke dalam paket data, dan mengirimkannya kembali ke Follower bersama dengan koordinat High Watermark (HW) Leader saat ini.
  4. Follower Menulis ke Disk & Memperbarui HW: Follower menerima paket data biner tersebut, menulisnya ke log lokal, menaikkan LEO lokalnya, dan memperbarui nilai HW lokal miliknya sesuai dengan nilai HW yang diinformasikan oleh Leader.

Siklus ini berulang dalam hitungan milidetik secara konstan, memastikan perbedaan data antara Leader dan Follower tetap mendekati nol.


Proses Failover Otomatis dan Peran Leader Epoch #

Ketika broker yang bertindak sebagai Leader partisi tiba-tiba mengalami crash, sistem koordinasi kluster (KRaft Controller Quorum) akan mengambil tindakan cepat untuk menunjuk pemimpin baru.

1. Deteksi Kegagalan #

Controller memantau detak jantung (heartbeat) dari seluruh broker aktif. Jika broker Leader gagal mengirimkan detak jantung dalam batas waktu timeout, Controller akan menandai broker tersebut mati dan memulai proses pemilihan pemimpin partisi (partition leader election).

2. Pemilihan Leader Baru dari ISR #

Controller memeriksa daftar In-Sync Replicas (ISR) untuk partisi tersebut. Hanya follower yang berada di dalam daftar ISR yang berhak dipromosikan menjadi Leader baru. Hal ini dilakukan karena follower ISR dijamin memiliki data yang paling lengkap dan sinkron hingga batas High Watermark.

3. Masalah Klasik Reorientasi Log (Log Truncation) #

Di masa lalu, ketika follower dipromosikan menjadi Leader baru, follower tersebut menggunakan High Watermark sebagai titik referensi tunggal untuk melakukan rekonsiliasi log dengan follower lainnya. Namun, hal ini sering memicu hilangnya data secara diam-diam (silent data loss) atau data ganda yang tidak konsisten akibat ketidaksinkronan posisi HW saat terjadi crash beruntun.

4. Solusi Modern: Leader Epoch #

Untuk mengatasi masalah ini secara mutlak, Kafka memperkenalkan konsep Leader Epoch. Setiap kali terjadi pemilihan pemimpin partisi baru, Controller akan menaikkan nomor generasi kepemimpinan (Leader Epoch) sebesar 1 angka. Setiap broker menyimpan daftar sejarah kepemimpinan ini dalam file teks rahasia bernama leader-epoch-checkpoint di direktori partisi mereka.

Saat broker Follower terhubung kembali ke Leader yang baru terpilih, ia tidak lagi langsung memotong log-nya berdasarkan High Watermark lokal. Follower akan mengirimkan permintaan metadata khusus untuk menanyakan LEO terakhir milik Leader pada masa jabatan (epoch) sebelumnya. Informasi Leader Epoch ini memastikan proses pemotongan log (log truncation) terjadi secara presisi hanya pada pesan-pesan yang benar-benar tidak berkomit, mencegah kehilangan data bersejarah secara tidak sengaja.


Kesalahan Umum (Anti-pattern) dalam Replikasi #

Berikut adalah kesalahan konfigurasi replikasi partisi yang sering dijumpai beserta solusi perbaikannya:

1. Mengabaikan Callback Failover di Kode Aplikasi Klien #

Ketika terjadi leader failover, pengiriman pesan dari produsen akan mengalami kegagalan sementara selama beberapa milidetik karena lokasi Leader sedang berpindah. Pengembang sering kali tidak menangani pengecualian ini di sisi klien, membiarkan aplikasi crash atau kehilangan data.

Konsekuensi Kegagalan: Aplikasi kita akan membuang pesan penting saat terjadi failover, memicu kebocoran data transaksi bisnis.

// =========================================================================
// ANTI-PATTERN: Mengabaikan exception failover leader dan membiarkan aplikasi crash
// Saat broker leader mati, pengiriman tanpa recovery akan langsung memutus koneksi klien.
// =========================================================================
try {
    producer.send(record).get();
} catch (Exception e) {
    System.err.println("Fatal: Gagal mengirim pesan: " + e.getMessage());
    // Aplikasi crash atau data hilang tanpa retry
    System.exit(1);
}

// =========================================================================
// SOLUSI YANG BENAR: Menggunakan callback asinkron dan mengandalkan auto-retry internal SDK
// SDK Kafka secara otomatis mendeteksi NotLeaderOrFollowerException dan memperbarui metadata
// untuk mencari lokasi Leader baru secara dinamis.
// =========================================================================
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ProducerFailoverHandler {
    private static final Logger log = LoggerFactory.getLogger(ProducerFailoverHandler.class);

    public void sendWithRetryHandling(org.apache.kafka.clients.producer.KafkaProducer<String, String> producer, 
                                      org.apache.kafka.clients.producer.ProducerRecord<String, String> record) {
        
        producer.send(record, new Callback() {
            @Override
            public void onCompletion(RecordMetadata metadata, Exception exception) {
                if (exception != null) {
                    if (exception instanceof org.apache.kafka.common.errors.NotLeaderOrFollowerException) {
                        // ✓ Ditangani secara otomatis oleh SDK melalui metadata refresh,
                        // namun jika retry habis, kita bisa melakukan fallback manual ke database cadangan
                        log.error("Leader failover terdeteksi untuk partisi: " + exception.getMessage());
                        fallbackToStorage(record);
                    } else {
                        log.error("Pengiriman gagal secara permanen: " + exception.getMessage());
                    }
                } else {
                    log.info("Pesan berhasil direplikasi pada partisi: " + metadata.partition() 
                        + ", offset: " + metadata.offset());
                }
            }
        });
    }

    private void fallbackToStorage(org.apache.kafka.clients.producer.ProducerRecord<String, String> record) {
        // Simpan data sementara ke database lokal / sistem file cadangan untuk dikirim ulang nanti
        log.warn("Menyimpan record ke penyimpanan cadangan: " + record.key());
    }
}

2. Menyetel replica.lag.time.max.ms Terlalu Rendah #

Menyetel parameter deteksi keterlambatan follower replica.lag.time.max.ms ke nilai yang sangat sensitif (misalnya 500 milidetik) dengan harapan deteksi kegagalan berlangsung cepat.

Konsekuensi Kegagalan: Pada kondisi jaringan yang sibuk atau saat terjadi lonjakan beban kerja (spikes), follower sehat akan sering kali terlambat beberapa milidetik dari Leader. Jika limit diatur terlalu sensitif, follower sehat tersebut akan terus-menerus ditendang keluar dari daftar ISR oleh Leader, memicu rentetan pembaruan metadata kluster ke Controller yang memboroskan sumber daya CPU dan jaringan. Naikkan nilainya ke angka yang wajar (misalnya 10000 ms hingga 30000 ms) untuk meredam fluktuasi jaringan.


Ringkasan #

  • Leader & Follower — Desain replikasi partisi di Kafka di mana Leader melayani pembacaan dan penulisan klien, sementara Follower aktif menyalin data secara asinkron dari Leader.
  • Log End Offset (LEO) — Koordinat offset berikutnya yang akan ditulis di akhir log fisik replika, menandakan panjang total log saat itu.
  • High Watermark (HW) — Offset tertinggi yang telah berhasil disalin oleh seluruh replika di dalam kelompok ISR. Konsumen hanya bisa membaca data hingga batas HW ini.
  • Follower Fetching — Optimasi rute jaringan yang memungkinkan konsumen membaca data langsung dari Follower terdekat yang berada di Availability Zone yang sama.
  • ReplicaFetcherThread — Thread latar belakang yang berjalan di broker Follower untuk mengirimkan permintaan biner FETCH secara konstan ke broker Leader.
  • Leader Epoch — Nomor generasi kepemimpinan partisi yang menuntun broker follower melakukan pemotongan log (log truncation) secara presisi saat terjadi failover.
  • Toleransi Kegagalan — Jaminan data tidak hilang dicapai dengan mendistribusikan replika secara Rack-Aware, menyetel replication factor minimal 3, dan acks=all di produsen.

← Sebelumnya: Overview   Berikutnya: In-Sync Replica →

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