Partition #
Jika topic adalah kategori logis dari aliran data di Apache Kafka, maka Partition (partisi) adalah unit fisik terkecil tempat data tersebut sebenarnya disimpan, direplikasi, dan dikonsumsi. Partisi merupakan mesin utama di balik kemampuan skalabilitas horizontal, paralelisme tinggi, dan toleransi kegagalan (fault tolerance) yang membuat Kafka menjadi standar industri untuk pemrosesan data skala besar. Memahami mekanisme internal partisi secara mendalam akan membantu kita merancang arsitektur sistem yang mampu menangani jutaan pesan per detik tanpa hambatan performa.
Konsep Dasar: Apa itu Partition? #
Secara fisik, sebuah partisi adalah satu berkas log append-only terurut (ordered log file) yang disimpan di dalam disk lokal salah satu server broker Kafka. Ketika produsen mengirimkan event ke suatu partisi, data baru tersebut akan selalu ditambahkan di ujung paling akhir dari berkas log tersebut. Karakteristik ini membuat operasi penulisan ke Kafka menjadi sangat cepat (memiliki kompleksitas waktu $O(1)$) karena tidak memerlukan pencarian indeks acak di disk seperti database tradisional.
Setiap event yang ditulis ke dalam partisi akan mendapatkan nomor urut unik yang disebut Offset. Offset bertipe integer 64-bit yang nilainya terus meningkat secara monoton (0, 1, 2, …). Offset bertindak sebagai alamat fisik dari data tersebut di dalam partisi. Sekali sebuah event mendapatkan offset tertentu, nomor tersebut bersifat mutlak dan tidak akan pernah berubah atau digunakan kembali untuk event lain, bahkan jika data lama sebelum offset tersebut telah dihapus oleh kebijakan retensi.
Jaminan urutan pesan (ordering guarantee) di Kafka bersifat lokal per partisi, bukan global per topik. Artinya, Kafka menjamin bahwa konsumen akan membaca pesan secara urut persis seperti saat dikirimkan hanya jika semua pesan tersebut berada di dalam satu partisi yang sama. Jika pesan tersebar di partisi yang berbeda, urutan pembacaannya tidak lagi dapat dijamin oleh broker.
Mengapa Kita Membutuhkan Partisi? (Paralelisme & Skalabilitas) #
Untuk memahami pentingnya partisi, mari kita bayangkan sebuah topik tanpa partisi (hanya memiliki 1 partisi tunggal). Topik tersebut hanya bisa disimpan pada satu disk server broker saja. Ketika volume data meningkat hingga ratusan terabyte, server tersebut akan kehabisan ruang disk. Selain itu, hanya satu aplikasi konsumen saja yang dapat membaca data tersebut pada satu waktu untuk menjaga urutan pesan, membatasi kecepatan pemrosesan kita.
Dengan memecah topik menjadi beberapa partisi, Kafka memecahkan dua masalah besar ini sekaligus:
1. Skalabilitas Horizontal (Horizontal Scaling) #
Partisi-partisi dari satu topik yang sama tidak harus disimpan pada server broker yang sama. Kafka akan menyebarkan partisi-partisi tersebut ke berbagai server broker yang tersedia di dalam kluster secara otomatis.
Sebagai contoh, jika kita memiliki topik dengan 6 partisi dan kluster dengan 3 broker, maka masing-masing broker akan mengelola 2 partisi. Ketika kapasitas penyimpanan kluster kita mulai penuh, kita cukup menambahkan server broker baru ke dalam kluster dan memindahkan sebagian partisi ke server baru tersebut tanpa perlu mematikan sistem (zero-downtime).
2. Paralelisme Konsumen (Consumer Parallelism) #
Partisi bertindak sebagai unit pembagian beban kerja untuk konsumen. Di dalam Kafka, satu partisi hanya boleh dikonsumsi oleh maksimal satu konsumen di dalam satu kelompok konsumen (Consumer Group) pada satu waktu.
Jika kita memiliki topik dengan 4 partisi, kita dapat menjalankan hingga 4 aplikasi konsumen secara paralel untuk memproses data tersebut secara bersamaan. Jika kita menambah jumlah konsumen menjadi 5, konsumen ke-5 akan menganggur (idle) karena seluruh partisi telah dialokasikan ke 4 konsumen sebelumnya. Oleh karena itu, jumlah partisi menetapkan batas maksimum paralelisme pemrosesan data kita di sisi hilir (downstream).
Cara Menentukan Jumlah Partisi yang Ideal #
Menentukan jumlah partisi saat membuat topik baru adalah keputusan arsitektur yang sangat krusial. Jika jumlah partisi terlalu sedikit, kita akan mengalami kemacetan performa (bottleneck) karena tidak bisa menambah jumlah konsumen untuk mempercepat pemrosesan data. Sebaliknya, jika partisi terlalu banyak, kluster akan memboroskan memori untuk mengelola metadata partisi dan memperlambat waktu pemulihan jika terjadi kegagalan server.
Untuk menghitung jumlah partisi secara ilmiah dan presisi, kita dapat menggunakan rumus throughput target di bawah ini:
$$P = \max\left(\frac{\text{Throughput Target}}{T_p}, \frac{\text{Throughput Target}}{T_c}\right)$$
Mari kita bedah masing-masing variabel dalam rumus tersebut:
- Throughput Target: Total volume data yang ingin kita tangani per detik. Contoh: $100\text{ MB/detik}$ atau $100.000\text{ pesan/detik}$.
- $T_p$ (Throughput Produsen Maksimal): Kecepatan maksimal satu thread aplikasi produsen untuk menulis data ke satu partisi. Secara umum, produsen Kafka tanpa kompresi yang dioptimalkan dengan baik dapat menulis sekitar $50 - 100\text{ MB/detik}$ ke satu partisi.
- $T_c$ (Throughput Konsumen Maksimal): Kecepatan maksimal satu aplikasi konsumen untuk membaca dan memproses data dari satu partisi. Kecepatan ini sangat bergantung pada kompleksitas bisnis di dalam kode konsumen kita (misalnya, apakah harus menulis ke database lain atau memanggil REST API pihak ketiga). Rata-rata konsumen biasa dapat memproses sekitar $10 - 20\text{ MB/detik}$ per partisi.
Contoh Kasus Perhitungan: #
Sebuah perusahaan logistik ingin memproses data koordinat GPS dari armada kurir dengan target throughput sebesar $150\text{ MB/detik}$. Melalui pengujian lokal, didapatkan hasil bahwa satu produsen mampu mengirimkan data sebesar $50\text{ MB/detik}$ per partisi, sedangkan konsumen kita (karena harus melakukan pemrosesan data yang cukup kompleks ke database PostgreSQL) hanya mampu memproses sekitar $15\text{ MB/detik}$ per partisi.
- Throughput produsen membutuhkan: $\frac{150}{50} = 3\text{ partisi}$
- Throughput konsumen membutuhkan: $\frac{150}{15} = 10\text{ partisi}$
Mengambil nilai maksimal dari kedua hasil tersebut, kita membutuhkan minimal 10 partisi untuk topik tersebut agar dapat menangani beban kerja $150\text{ MB/detik}$ secara aman tanpa memicu antrean lag di sisi konsumen.
Pembagian Beban Kerja (Rute Partisi) #
Bagaimana produsen mengarahkan aliran event agar masuk ke partisi yang tepat secara efisien? Mari kita pelajari alur distribusi fisik pesan tersebut:
flowchart TD
subgraph Producer ["Aplikasi Producer"]
EventData["Event: Account_ID = 1024 <br/> Payload = Rp 500.000"]
Partitioner{"Partitioner Engine"}
end
subgraph KafkaCluster ["Kluster Kafka (Topik: 3 Partisi)"]
BrokerA["Broker 1"] ---> Partisi0[("Partisi 0")]
BrokerB["Broker 2"] ---> Partisi1[("Partisi 1")]
BrokerC["Broker 3"] ---> Partisi2[("Partisi 2")]
end
EventData --> Partitioner
Partitioner -->|"MurmurHash2(1024) % 3 <br/> = Partisi 1"| Partisi1
Secara garis besar, ada dua mekanisme bawaan yang digunakan untuk merutekan event ke partisi:
- Dengan Kunci (Keyed Message): Jika event memiliki kunci, produsen akan menghitung hash Murmur2 dari kunci tersebut dan memodulonya dengan jumlah partisi aktif. Semua data dengan kunci yang sama dijamin 100% masuk ke partisi fisik yang sama.
- Tanpa Kunci (Non-Keyed Message): Jika kunci bernilai null, produsen menggunakan strategi Sticky Partitioning. Pesan-pesan akan dikelompokkan ke dalam satu batch jaringan dan dikirimkan secara efisien ke satu partisi acak yang sama hingga batch penuh, sebelum beralih ke partisi lain.
Dampak Key Skewness (Hot Partition) dan Cara Menghindarinya #
Penerapan pengiriman data berbasis kunci (Keyed Message) sering kali menimbulkan masalah serius di lingkungan produksi jika kita tidak memilih kunci dengan bijak. Masalah ini dikenal dengan istilah Key Skewness (ketimpangan kunci) atau Hot Partition (partisi panas).
Ketimpangan kunci terjadi ketika satu nilai kunci tertentu mendominasi volume transaksi bisnis di dunia nyata. Sebagai contoh, jika kita menetapkan partner_id sebagai kunci partisi pada aplikasi pembayaran e-commerce, dan salah satu mitra kita (misalnya merchant raksasa seperti Shopee) menyumbang 80% dari total transaksi seluruh aplikasi kita, maka partisi yang menjadi tujuan hash kunci Shopee akan memproses data 80% lebih banyak dibanding partisi lainnya.
Dampak Kegagalan: Broker yang menyimpan partisi panas tersebut akan mengalami kemacetan karena kehabisan sumber daya I/O disk dan CPU. Konsumen yang ditugaskan membaca partisi tersebut akan mengalami keterlambatan pemrosesan (consumer lag) yang tinggi, sementara konsumen yang membaca partisi lain akan menganggur.
Solusi Mitigasi: Key Salting #
Untuk menghindari partisi panas tanpa kehilangan manfaat urutan pesan, kita dapat menerapkan teknik Key Salting (penggaraman kunci). Kita menambahkan nilai acak (salt) di belakang kunci utama untuk menyebarkan data ke beberapa partisi yang berbeda secara merata:
# ANTI-PATTERN: Menggunakan kunci dengan entropi rendah langsung tanpa pengolahan
# Ini memicu penumpukan data di satu partisi saja jika satu partner mendominasi transaksi.
def kirim_transaksi_skewed(producer, partner_id, data):
# Kunci murni "Shopee" akan selalu masuk ke partisi yang sama persis
producer.send('topik-pembayaran', key=partner_id.encode('utf-8'), value=data)
# Solusi yang BENAR: Menerapkan Key Salting
# Kita menambahkan angka acak terkontrol di belakang kunci untuk membagi beban kerja ke beberapa partisi.
def kirim_transaksi_salted(producer, partner_id, data):
import random
# Jika merchant raksasa terdeteksi, tambahkan garam acak 1 sampai 3
if partner_id == "Shopee":
salt = random.randint(1, 3)
kunci_salted = f"{partner_id}_{salt}"
else:
kunci_salted = partner_id
# Data Shopee kini akan tersebar merata ke 3 partisi hasil hash yang berbeda
# Konsumen hilir harus menghapus suffix "_salt" saat melakukan pemrosesan data
producer.send('topik-pembayaran', key=kunci_salted.encode('utf-8'), value=data)
Konsekuensi Menambah Partisi pada Topik yang Sedang Berjalan #
Dalam operasional sehari-hari, kita mungkin tergoda untuk langsung menambah jumlah partisi pada topik yang sedang aktif ketika mendeteksi adanya lonjakan trafik (traffic spike). Kafka memang mendukung penambahan partisi secara dinamis (on-the-fly). Namun, tindakan ini memiliki dampak arsitektural yang sangat serius yang sering kali diabaikan oleh pengembang:
1. Rusaknya Jaminan Urutan Pesan Berbasis Kunci (Key-to-Partition Mapping) #
Seperti yang telah kita bahas, produsen memetakan kunci ke partisi menggunakan rumus modulo hash:
$$\text{Partisi} = \text{MurmurHash2}(\text{Key}) \pmod{\text{Jumlah Partisi}}$$
Jika kita mengubah nilai pembagi yaitu Jumlah Partisi (misalnya dari 3 partisi menjadi 5 partisi), maka hasil operasi modulo untuk kunci yang sama otomatis akan berubah.
- Sebelum Penambahan (3 Partisi): Event dengan kunci
User_99menghasilkan hash yang menunjuk ke Partisi 1. Semua data historisUser_99tersimpan rapi di Partisi 1. - Setelah Penambahan (5 Partisi): Event baru untuk
User_99dikirim oleh produsen. Karena pembaginya sekarang adalah 5, rumus menghasilkan rute baru ke Partisi 4.
Akibatnya, data lama User_99 berada di Partisi 1, sementara data barunya ditulis di Partisi 4. Aplikasi konsumen kita yang membaca data secara paralel akan memproses data baru tersebut sebelum menyelesaikan data lama, sehingga merusak jaminan urutan data secara total untuk entitas tersebut.
2. Dampak pada Pemrosesan Stateful (Kafka Streams / KTable Join) #
Jika kita menggunakan pustaka pemrosesan aliran data seperti Kafka Streams untuk melakukan agregasi data (misalnya menghitung total saldo berjalan per user) atau melakukan join antar dua aliran data berbeda berdasarkan kunci, penambahan partisi di tengah jalan akan memicu kerusakan state lokal (state store). Data yang seharusnya berkumpul pada satu thread pemrosesan yang mengelola partisi tertentu kini terpecah ke thread lain, memicu hasil kalkulasi data yang tidak akurat.
Strategi Migrasi yang Aman: #
Jika kita benar-benar harus menaikkan kapasitas throughput topik dan tidak bisa mentoleransi rusaknya urutan data:
- Membuat Topik Baru: Solusi terbaik adalah membuat topik baru dengan jumlah partisi yang lebih besar sejak awal (misalnya
prod.finance.payment-v2). - Mengalihkan Trafik: Alihkan produsen untuk menulis ke topik baru, lalu jalankan aplikasi konsumen baru yang membaca dari topik baru tersebut.
- Drain data lama: Biarkan konsumen lama memproses habis seluruh sisa data yang tertinggal di topik lama (
v1) hingga bersih (drain), sebelum dinonaktifkan sepenuhnya.
Replikasi dan Leader-Follower Partisi #
Untuk mencegah kehilangan data ketika terjadi kegagalan server fisik, Kafka mereplikasi setiap partisi ke beberapa server broker yang berbeda. Jumlah replika diatur menggunakan parameter konfigurasi Replication Factor (misalnya replication factor = 3).
Dalam mekanisme replikasi ini, partisi dikelola menggunakan peran Leader-Follower:
- Leader Partition: Di antara semua replika partisi yang ada, hanya satu replika yang ditunjuk sebagai Leader. Seluruh aktivitas baca dan tulis dari produsen dan konsumen secara bawaan wajib diarahkan langsung ke partisi Leader ini.
- Follower Partition: Replika lainnya bertindak sebagai Follower. Mereka tidak melayani permintaan baca/tulis dari klien umum; tugas satu-satunya adalah melakukan replikasi data secara asinkron dari partisi Leader untuk menyamakan status data mereka.
- In-Sync Replicas (ISR): Kelompok replika (termasuk Leader) yang status datanya benar-benar sejajar dan sinkron dengan Leader. Jika Leader mengalami kegagalan, hanya anggota dari ISR ini yang berhak dipilih sebagai Leader baru.
Kesalahan Umum (Anti-pattern) dalam Merancang Partisi #
Ada beberapa kesalahan desain partisi yang wajib dihindari agar operasional kluster Kafka berjalan stabil:
1. Merancang Topik Beban Tinggi dengan Hanya 1 Partisi #
Pengembang yang menginginkan jaminan urutan data secara global sering kali membuat topik dengan hanya memiliki 1 partisi saja untuk semua beban kerja.
Konsekuensi Kegagalan: Membatasi kapasitas throughput topik tersebut setinggi-tingginya hanya pada batas maksimal I/O satu server broker tunggal. Selain itu, kita tidak akan pernah bisa meningkatkan kecepatan pemrosesan di sisi konsumen karena menambahkan lebih dari satu konsumen di dalam consumer group akan sia-sia (konsumen lainnya akan menganggur). Jaminan urutan global harus dikorbankan demi skalabilitas dengan memecah topik ke beberapa partisi dan menggunakan kunci logis yang tepat.
2. Membuat Jumlah Partisi Terlalu Banyak tanpa Perhitungan (Over-Partitioning) #
Beberapa tim pengembang langsung membuat ratusan atau ribuan partisi untuk semua topik dengan asumsi “semakin banyak partisi semakin cepat”.
Konsekuensi Kegagalan: Setiap partisi di Kafka diwakili oleh struktur direktori file di tingkat OS broker. Memiliki terlalu banyak partisi aktif akan memicu:
- Konsumsi file descriptor sistem operasi yang melebihi batas batas aman (open file limits).
- Peningkatan konsumsi memori JVM Heap secara dramatis di server broker untuk mengelola metadata partisi.
- Latensi yang sangat tinggi saat terjadi failover cluster karena KRaft/ZooKeeper harus melakukan pemilihan ulang ribuan Leader partisi secara bersamaan ketika salah satu broker mati.
- Sebagai aturan praktis di produksi, batasi jumlah partisi maksimum di kisaran 100 partisi per broker dan tidak lebih dari 20.000 partisi per kluster.
Ringkasan #
- Definisi Partisi — Partisi adalah berkas berkas log append-only terurut di disk lokal broker yang bertindak sebagai unit fisik terkecil dari skalabilitas horizontal, replikasi data, dan paralelisme di Apache Kafka.
- Offset Data — Setiap data di dalam partisi diidentifikasi secara unik menggunakan nomor urut bernama Offset yang bersifat immutable dan terus meningkat secara monoton.
- Jaminan Urutan — Jaminan urutan pesan di Apache Kafka bersifat lokal per partisi, bukan global tingkat topik secara keseluruhan.
- Paralelisme Konsumen — Jumlah partisi menentukan batas maksimum kapasitas paralelisme aplikasi konsumen kita di dalam satu kelompok konsumen (Consumer Group).
- Rumus Partisi Ideal — Hitung jumlah partisi ideal dengan membandingkan target throughput sistem terhadap kemampuan throughput maksimal satu produsen ($T_p$) dan satu konsumen ($T_c$).
- Mitigasi Partisi Panas — Hindari fenomena Key Skewness (hot partition) dengan memilih kunci yang memiliki sebaran acak tinggi, atau gunakan metode Key Salting untuk merchant/partner yang memiliki volume transaksi sangat dominan.