Overview #
Saat kita membangun pipa data real-time berskala besar, kita sering melihat Apache Kafka sebagai kotak hitam (black box) yang hanya menerima dan mengirimkan pesan dengan kecepatan sangat tinggi. Namun di bawah tenda, Kafka adalah mahakarya rekayasa perangkat lunak terdistribusi yang sangat terstruktur, efisien, dan dioptimalkan secara mendalam pada tingkat kernel sistem operasi. Di bagian arsitektur internal ini, kita akan menyelami cara kerja bagian dalam server broker Kafka. Kita akan membedah bagaimana permintaan klien masuk melalui lapisan jaringan Socket Server, disalurkan ke antrean permintaan asinkron, diolah oleh kolam Request Handler Thread, dirutekan ke Log Subsystem untuk penulisan disk fisik, dan dikelola oleh berbagai Coordinator. Memahami arsitektur internal ini sangat penting untuk membantu kita melakukan diagnosa masalah performa, menengahi kemacetan jaringan, serta merancang kluster Kafka yang tahan banting untuk kebutuhan sistem produksi kita.
Kapan Butuh Arsitektur Internal? #
Kita perlu mendalami arsitektur internal ini jika:
✓ Kita merancang kluster Kafka skala produksi dan perlu melakukan penyetelan (tuning) latensi dan throughput.
✓ Kita mengalami masalah latensi tinggi atau penumpukan permintaan (request backlog) pada broker.
✓ Kita ingin memahami bagaimana data direplikasi secara asinkron tanpa mengorbankan konsistensi.
✓ Kita perlu mendiagnosis kegagalan koordinasi konsumen (consumer group rebalances) atau kehilangan data.
Tidak perlu mendalami detail internal ini jika:
✗ Kita hanya menggunakan Kafka sebagai pengguna akhir (end-user) dengan volume data sangat kecil yang tidak memerlukan tuning konfigurasi.
✗ Kita menggunakan layanan Kafka yang sepenuhnya dikelola (fully managed SaaS) seperti Confluent Cloud di mana seluruh parameter infrastruktur disembunyikan.
Lapisan Jaringan (Socket Server, Acceptor, Processor) #
Bagian terdepan dari server broker Kafka yang berinteraksi langsung dengan klien (produsen dan konsumen) adalah Socket Server. Lapisan jaringan ini dirancang menggunakan pola desain Reactor non-blocking berbasis Java NIO (New I/O) Selector yang sangat efisien untuk menangani puluhan ribu koneksi TCP secara simultan dengan jumlah thread yang relatif kecil.
Dalam Socket Server, tugas penanganan jaringan dibagi secara tegas menjadi dua jenis thread:
1. Acceptor Thread #
Setiap broker memiliki satu Acceptor Thread untuk setiap listener (port jaringan) yang dikonfigurasi. Tugas tunggal dari thread ini adalah mendengarkan koneksi TCP baru yang masuk dari klien. Saat koneksi TCP baru terjalin, Acceptor Thread tidak membaca data dari koneksi tersebut; Acceptor Thread hanya menerima (accept) koneksi dan langsung menyerahkan objek soket koneksi tersebut kepada salah satu Processor Thread secara bergiliran (round-robin). Pemisahan ini memastikan bahwa broker dapat menerima koneksi baru secara konstan tanpa terhambat oleh proses pembacaan data yang lambat dari koneksi yang sudah ada.
2. Processor Thread #
Kafka menjalankan sekelompok Processor Thread (jumlahnya dikonfigurasi melalui parameter num.network.threads yang secara default bernilai 3). Setiap Processor Thread memiliki sebuah Selector NIO internal yang memantau sekelompok soket klien yang dialokasikan kepadanya.
- Membaca Permintaan: Processor Thread bertanggung jawab membaca aliran byte biner dari soket klien, menyusunnya menjadi objek permintaan (Request), dan meletakkan objek tersebut ke dalam Request Queue (antrean permintaan global yang dibagikan ke seluruh thread pengolah).
- Menulis Balasan: Processor Thread juga terus memantau Response Queue lokal miliknya. Ketika thread pengolah selesai memproses permintaan dan meletakkan objek jawaban (Response) di Response Queue, Processor Thread yang bersangkutan akan mengambil jawaban tersebut dan menuliskan aliran byte balasannya kembali ke soket TCP klien yang sesuai.
Di tingkat sistem operasi (OS) Linux, Java NIO Selector ini memetakan panggilannya ke mekanisme system call tingkat rendah seperti epoll (atau kqueue di macOS). Berbeda dengan model klasik thread-per-connection yang memboroskan memori RAM dan siklus CPU untuk context switching, model epoll ini memungkinkan satu Processor Thread untuk memantau ribuan soket secara non-blocking dengan konsumsi CPU yang sangat efisien.
Penyetelan parameter num.network.threads sangat bergantung pada jumlah koneksi aktif dan volume lalu lintas jaringan kita. Sebagai aturan praktis, kita menyetel parameter ini sebanding dengan jumlah core CPU fisik yang didedikasikan untuk menangani aktivitas jaringan (biasanya 50% dari total core CPU). Jika metrik pemantauan menunjukkan bahwa Processor Thread terlalu sibuk (nilai idle ratio mendekati 0%), kita wajib menaikkan nilainya agar tidak terjadi hambatan pada I/O jaringan.
Request Handler dan Pool Pemrosesan #
Setelah permintaan klien diletakkan di dalam Request Queue oleh Processor Thread, permintaan tersebut akan diolah oleh Kafka Request Handler Pool. Ini adalah kolam thread pekerja (worker threads) yang bertugas melakukan komputasi bisnis sesungguhnya di dalam broker.
Jumlah thread pekerja di dalam kolam ini dikonfigurasi melalui parameter num.io.threads (secara default bernilai 8). Thread-thread pekerja ini berjalan secara asinkron dan terus-menerus mengambil permintaan dari Request Queue menggunakan skema antrean FIFO (First-In-First-Out).
Ketika sebuah thread pekerja (KafkaRequestHandler) mengambil permintaan, ia melakukan langkah-langkah berikut:
- Validasi dan Otorisasi: Memastikan klien memiliki hak akses (ACLs) untuk melakukan operasi yang diminta pada topik tujuan.
- Routing API: Memeriksa jenis permintaan berdasarkan API Key (misalnya,
PRODUCEuntuk penulisan data,FETCHuntuk pembacaan data, atauMETADATAuntuk meminta informasi kluster). - Eksekusi Operasi: Berinteraksi dengan komponen internal yang relevan (seperti Log Subsystem untuk membaca/menulis file disk atau Coordinator untuk mengelola status grup).
- Penyusunan Balasan: Setelah operasi selesai, thread pekerja membungkus hasilnya menjadi objek
Responsedan meletakkannya ke dalam Response Queue spesifik milik Processor Thread yang awalnya membawa permintaan tersebut.
Mekanisme Backpressure (Tekanan Balik) #
Arsitektur antrean global Request Queue memiliki batas kapasitas maksimum yang dikontrol oleh konfigurasi queued.max.requests (secara default bernilai 500). Apa yang terjadi jika thread pekerja I/O terhambat (misalnya karena disk sedang lambat) dan Request Queue terisi penuh hingga mencapai 500 permintaan?
Di sinilah mekanisme Backpressure (tekanan balik) bekerja secara otomatis. Ketika Request Queue penuh, seluruh Processor Threads akan berhenti mengambil permintaan dari soket klien. Soket-soket TCP klien tidak akan dibaca. Akibatnya, buffer TCP di sisi kernel sistem operasi broker akan penuh, memicu penyusutan jendela penerimaan TCP (TCP window size) ke arah klien. Produsen dan konsumen aplikasi kita secara otomatis akan merasakan perlambatan penulisan jaringan dan menahan laju pengiriman data mereka secara natural. Mekanisme ini mencegah broker kehabisan memori RAM (out-of-memory) akibat penumpukan permintaan di memori.
Log Subsystem dan Disk Persistence #
Ketika thread pekerja memproses permintaan tipe PRODUCE (tulis pesan) atau FETCH (baca pesan), ia akan berinteraksi langsung dengan Log Subsystem. Komponen ini bertanggung jawab penuh atas penyimpanan fisik data ke dalam disk.
Log Subsystem mengelola seluruh partisi yang dialokasikan pada broker tersebut. Setiap partisi direpresentasikan secara fisik sebagai direktori di dalam sistem file server. Di dalam direktori ini, data disimpan dalam bentuk segmen-segmen log biner terurut.
Alur penulisan data internal pada Log Subsystem bekerja sebagai berikut:
- Penulisan ke Page Cache: Saat menerima pesan baru, Log Subsystem tidak langsung melakukan penulisan fisik ke piringan disk keras. Data ditulis ke deskriptor file yang dikelola oleh memori RAM sistem operasi (OS Page Cache). Proses ini sangat cepat karena hanya melibatkan operasi penulisan memori.
- Penyusunan Indeks Mmap: Secara bersamaan, Log Subsystem memperbarui file indeks
.indexdan.timeindexyang dipetakan ke memori menggunakan teknologi Memory-Mapped Files (mmapdi Java). Indeks ini memungkinkan pencarian offset atau waktu yang sangat cepat tanpa overhead pembacaan disk yang besar. - Pembilasan Asinkron: Sistem operasi secara asinkron akan membilas (flush) halaman memori kotor (dirty pages) di Page Cache ke disk fisik menggunakan thread kernel latar belakang.
Tuning Kernel Sistem Operasi #
Karena Kafka sangat mengandalkan sistem operasi untuk melakukan penulisan log, kita wajib melakukan tuning pada parameter memori virtual Linux kernel. Konfigurasi penting pada /etc/sysctl.conf meliputi:
vm.dirty_background_ratio(biasanya disetel ke5): Persentase memori sistem yang berisi data kotor sebelum thread kernel latar belakang (pdflush/flush) mulai menulis data tersebut ke disk. Penyetelan nilai yang rendah mencegah lonjakan disk I/O yang ekstrem.vm.dirty_ratio(biasanya disetel ke10): Persentase memori sistem maksimum di mana proses penulisan akan terblokir dan dipaksa menulis data kotor ke disk secara langsung.
Selain itu, pemilihan jenis sistem berkas (filesystem) juga krusial. Sistem berkas XFS sangat direkomendasikan untuk Kafka dibandingkan EXT4 karena XFS memiliki penanganan alokasi blok disk berukuran besar secara lebih efisien dan mendukung operasi I/O paralel yang lebih baik. Mount options seperti noatime (menolak pembaruan stempel waktu akses file) juga wajib digunakan untuk memangkas overhead penulisan disk.
Coordinator dan Pengelolaan Metadata #
Selain mengelola aliran data log, broker Kafka juga harus mengelola status internal sistem terdistribusi, seperti keanggotaan konsumen dalam kelompok (consumer groups) dan koordinasi transaksi. Tugas ini diemban oleh komponen internal khusus yang disebut Coordinator:
1. Group Coordinator #
Setiap broker Kafka menjalankan modul Group Coordinator. Tugas utamanya adalah mengelola siklus hidup Consumer Group. Ketika sebuah konsumen bergabung atau keluar dari grup, Group Coordinator akan memicu proses penyeimbangan ulang (rebalance). Coordinator ini juga bertanggung jawab menerima dan menyimpan koordinat pembacaan konsumen (offset commits) ke dalam topik internal khusus bernama __consumer_offsets.
Bagaimana pembagian kerja Group Coordinator dilakukan? Kafka tidak menunjuk satu broker tunggal untuk mengelola seluruh konsumen di kluster. Sebaliknya, pembagian dilakukan berdasarkan rumus hashing sederhana:
$$\text{Target Partition} = \text{hash}(\text{group.id}) \pmod{\text{num.partitions}(\text{__consumer_offsets})}$$
Broker yang menjadi Leader untuk partisi target tersebut akan bertindak sebagai Group Coordinator khusus untuk grup tersebut. Pendekatan ini menyebarkan beban manajemen konsumen secara merata ke seluruh broker di dalam kluster.
2. Transaction Coordinator #
Untuk mendukung fitur transaksi Exactly-Once Semantics (EOS), Kafka menjalankan Transaction Coordinator. Komponen ini mengelola status transaksi produser di dalam topik internal __transaction_state. Coordinator memastikan bahwa event-event yang dikirim dalam satu transaksi akan ditandai sebagai COMMITTED atau ABORTED secara atomik, sehingga konsumen hanya akan membaca data transaksi yang sukses.
3. Metadata Cache #
Setiap broker memelihara salinan lokal dari metadata kluster di dalam memori cache-nya. Metadata ini berisi informasi tentang seluruh broker aktif, topik, partisi, serta broker mana yang menjadi Leader untuk masing-masing partisi. Ketika klien mengirimkan permintaan METADATA, broker dapat merespon secara instan dari cache memori lokal ini tanpa perlu melakukan koordinasi jaringan tambahan.
Perbandingan Arsitektur Aliran Data Internal #
Untuk memahami bagaimana berbagai lapisan internal ini bekerja sama, mari kita pelajari diagram aliran data lengkap di dalam broker Kafka saat menerima dan memproses permintaan klien:
flowchart TD
subgraph ClientSection ["Klien Aplikasi"]
Client["Klien Kafka <br/> (Producer / Consumer)"]
end
subgraph NetworkLayer ["Lapisan Jaringan (Socket Server)"]
Acceptor["Acceptor Thread <br/> (Menerima Koneksi Baru)"]
ProcessorPool["Processor Threads <br/> (num.network.threads)"]
ReqQueue["Request Queue <br/> (Antrean Permintaan Global)"]
RespQueue["Response Queues <br/> (Antrean Balasan per Processor)"]
end
subgraph HandlerLayer ["Lapisan Pemrosesan (Handler Pool)"]
HandlerPool["Kafka Request Handlers <br/> (num.io.threads)"]
end
subgraph StorageLayer ["Lapisan Penyimpanan & Metadata"]
LogSub["Log Subsystem <br/> (Disk I/O)"]
Coord["Coordinator <br/> (Group / Transaction)"]
end
Client -->|"1. Request TCP"| Acceptor
Acceptor -->|"2. Alokasikan Soket"| ProcessorPool
ProcessorPool -->|"3. Tulis Request"| ReqQueue
ReqQueue -->|"4. Ambil Request (FIFO)"| HandlerPool
HandlerPool -->|"5a. Baca/Tulis Disk"| LogSub
HandlerPool -->|"5b. Update State"| Coord
HandlerPool -->|"6. Tulis Balasan"| RespQueue
RespQueue -->|"7. Ambil Balasan"| ProcessorPool
ProcessorPool -->|"8. Kirim Response TCP"| Client
style Acceptor fill:#ffdddd,stroke:#ff8888
style ProcessorPool fill:#ffdddd,stroke:#ff8888
style HandlerPool fill:#ddffdd,stroke:#88ff88
style LogSub fill:#ddddff,stroke:#8888ff
Perbandingan Jenis Permintaan Utama (Request Types) #
Masing-masing jenis permintaan klien membebani sumber daya server broker secara berbeda. Tabel di bawah ini membandingkan karakteristik operasional dari tiga jenis permintaan utama protokol Kafka:
| Karakteristik | PRODUCE (Tulis Data) | FETCH (Baca Data) | METADATA (Minta Informasi) |
|---|---|---|---|
| Sumber Permintaan | Produser aplikasi klien. | Konsumen aplikasi klien & Broker Follower. | Semua klien (saat inisialisasi & pembaruan). |
| API Key Protokol | API Key 0 |
API Key 1 |
API Key 3 |
| Alur Proses Utama | Ditulis ke OS Page Cache partisi Leader, lalu direplikasi ke follower. | Dibaca dari OS Page Cache (cepat) atau Disk fisik (lambat). | Dibaca langsung dari Metadata Cache lokal di memori RAM broker. |
| Hambatan Utama | Kecepatan CPU (kompresi) dan latensi tulis Page Cache/Disk. | Bandwidth kartu jaringan (network egress) dan Disk read I/O. | Latensi memori RAM (sangat ringan). |
| Penyetelan Konfigurasi | linger.ms, batch.size, kompresi (snappy/zstd). |
fetch.min.bytes, fetch.max.wait.ms, sendfile. |
metadata.max.age.ms di sisi klien. |
Decision Tree — Optimasi Parameter Tuning #
Dalam operasional produksi, kita sering kali harus mendiagnosis hambatan kinerja (bottleneck) pada kluster Kafka. Diagram pohon keputusan di bawah ini memandu kita dalam mengidentifikasi parameter konfigurasi internal mana yang harus disetel berdasarkan indikasi metrik performa:
flowchart TD
Start{"Mulai Diagnosa Bottleneck"} --> CheckCPU{"Apakah Penggunaan CPU Tinggi?"}
CheckCPU -- "Ya" --> CheckGC{"Apakah GC Pauses Sangat Lama?"}
CheckGC -- "Ya" --> TuneJVM["Optimalkan JVM Heap & GC Flags"]
CheckGC -- "Tidak" --> CheckThreads{"Apakah Request Handler Idle < 10%?"}
CheckThreads -- "Ya (Handler Backlog)" --> ScaleIO["Naikkan num.io.threads atau Upgrade Disk"]
CheckThreads -- "Tidak" --> CheckNet{"Apakah Network Processor Idle < 10%?"}
CheckNet -- "Ya (Network Backlog)" --> ScaleNet["Naikkan num.network.threads"]
CheckNet -- "Tidak" --> Normal["Sistem Bekerja Optimal"]
CheckCPU -- "Tidak" --> CheckIO{"Apakah I/O Wait Disk Tinggi?"}
CheckIO -- "Ya" --> UpgradeStorage["Gunakan SSD / RAID atau Tingkatkan num.io.threads"]
CheckIO -- "Tidak" --> Normal
style Start fill:#f9f,stroke:#333
style Normal fill:#9f9,stroke:#333
style TuneJVM fill:#ff9,stroke:#333
style ScaleIO fill:#ff9,stroke:#333
style ScaleNet fill:#ff9,stroke:#333
style UpgradeStorage fill:#ff9,stroke:#333
Ringkasan #
- Socket Server — Lapisan terdepan broker menggunakan Java NIO Selector dengan pola desain Reactor asinkron untuk menangani puluhan ribu koneksi TCP secara efisien.
- Acceptor vs Processor — Satu Acceptor Thread menerima koneksi TCP baru secara cepat dan mendistribusikannya secara round-robin ke beberapa Processor Thread untuk dibaca/tulis datanya.
- Request & Response Queue — Antrean memori asinkron yang memisahkan operasi jaringan (NIO Processor) dengan operasi pemrosesan komputasi/disk (Request Handler Thread).
- num.network.threads — Parameter penyetelan jumlah Processor Thread yang bertugas membaca dan menulis aliran byte data soket jaringan.
- num.io.threads — Parameter penyetelan jumlah thread pekerja di dalam kolam pengolah permintaan (RequestHandlerPool) yang melakukan baca/tulis ke sistem penyimpanan disk.
- Log Subsystem — Komponen internal yang mengelola segmen-segmen log fisik, pemetaan memori file indeks (
mmap), serta interaksi langsung dengan Page Cache sistem operasi.- Coordinators — Modul internal khusus broker seperti Group Coordinator (manajemen offset & rebalance konsumen) dan Transaction Coordinator (manajemen transaksi atomik).
- Optimasi Page Cache — Kafka meminimalkan read disk dengan memastikan konsumen membaca data yang masih segar di memori RAM Page Cache sistem operasi.
← Sebelumnya: Distributed Commit Log Berikutnya: Leader & Follower →