Partition Strategy #
Di dalam arsitektur penyimpanan terdistribusi Apache Kafka, Partition (partisi) adalah unit terkecil penentu skalabilitas fisik, pemisahan beban, dan paralelisme sistem. Kafka membagi topik menjadi beberapa partisi agar data dapat disebar ke beberapa server broker secara bersamaan. Namun, menentukan berapa jumlah partisi yang ideal dan bagaimana mendistribusikan pesan ke partisi-partisi tersebut adalah sebuah keputusan desain arsitektural yang kritis.
Kesalahan dalam strategi alokasi partisi dapat menyebabkan ketimpangan beban server (Key Skewness), hambatan antrean data (Head-of-Line blocking), hingga penurunan drastis throughput sistem. Melalui artikel ini, kita akan merumuskan formula perhitungan jumlah partisi yang tepat berdasarkan target kapasitas throughput produsen dan konsumen, membedah alur kerja pembagi partisi bawaan (Sticky Partitioner vs Hashed Key Partitioner), menyusun implementasi Custom Partitioner untuk isolasi lalu lintas data kritis, serta mempelajari teknik mitigasi hot partitions melalui metode Key Salting.
Formula Menghitung Jumlah Partisi Ideal #
Satu mitos yang sering beredar di kalangan developer adalah “semakin banyak partisi, semakin baik kinerja Kafka”. Hal ini tidak sepenuhnya benar. Menetapkan jumlah partisi secara berlebihan (misalnya langsung membuat 500 partisi untuk topik kecil) akan memicu beban overhead yang tinggi bagi kluster:
- File Handle Limits: Setiap partisi diwakili oleh direktori fisik berisi file log dan indeks. Ribuan partisi berarti ribuan file deskriptor terbuka yang membebani sistem operasi broker.
- Controller Overhead: Ketika terjadi kegagalan broker, Controller harus memimpin proses pemilihan pemimpin (leader election) untuk seluruh partisi yang kehilangan pemimpinnya. Semakin banyak partisi, waktu pemulihan kluster akan semakin lama.
- Client Buffer Memory: Produsen Kafka mengalokasikan memori buffer (Record Accumulator) per partisi. Semakin banyak partisi, konsumsi RAM di sisi aplikasi klien produsen kita akan membengkak secara eksponensial.
Untuk menghitung jumlah partisi secara ilmiah, kita harus menggunakan formula target throughput berikut:
$$P = \max \left( \frac{T_P}{t_p}, \frac{T_C}{t_c} \right)$$
Di mana:
- $P$ — Jumlah partisi ideal yang dibutuhkan.
- $T_P$ — Target throughput penulisan total produsen yang diinginkan (dalam MB/detik).
- $t_p$ — Kecepatan penulisan maksimum yang bisa dicapai oleh satu produsen pada satu partisi tunggal (biasanya berkisar 10 MB/detik, tergantung jaringan dan hardware).
- $T_C$ — Target throughput pembacaan total konsumen yang diinginkan (dalam MB/detik).
- $t_c$ — Kecepatan pemrosesan bisnis maksimum yang bisa dicapai oleh satu konsumen pada satu partisi tunggal (biasanya berkisar 2 MB/detik hingga 5 MB/detik, sangat bergantung pada performa database eksternal aplikasi).
Contoh Skenario Kasus: #
Kita ingin merancang sistem pemrosesan transaksi pembayaran dengan spesifikasi:
- Target volume data masuk ($T_P$) = 50 MB/detik.
- Target volume pemrosesan data keluar ($T_C$) = 50 MB/detik.
- Kecepatan tulis satu partisi ($t_p$) = 10 MB/detik.
- Kecepatan baca dan pemrosesan satu konsumen ($t_c$) = 2.5 MB/detik (karena konsumen harus melakukan query ke database MySQL internal).
Maka perhitungan partisi idealnya adalah:
$$P = \max \left( \frac{50}{10}, \frac{50}{2.5} \right) = \max (5, 20) = 20 \text{ partisi}$$
Penyetelan ini menjamin kita memiliki jumlah partisi yang cukup (20 partisi) sehingga kita dapat menjalankan hingga 20 konsumen aktif secara paralel di dalam satu Consumer Group untuk mengejar target throughput pemrosesan data keluar. Selalu tambahkan ruang toleransi cadangan (headroom) sebesar 20% hingga 30% pada nilai akhir hasil kalkulasi untuk mengantisipasi lonjakan beban di masa mendatang.
Membedah Mekanisme Partitioner Bawaan #
Saat produsen mengirim data ke Kafka, modul Partitioner bertugas mengarahkan pesan ke nomor partisi tujuan. Mekanisme pembagian rute ini terbagi menjadi dua skenario tergantung pada keberadaan kunci (Key) pada pesan:
1. Hashed Key Partitioner (Pesan Menggunakan Kunci) #
Ketika produsen mengirim pesan dengan menyertakan kunci (key is not null), Kafka akan menggunakan algoritma hashing bawaan Murmur2 untuk menghasilkan representasi integer dari kunci tersebut, lalu melakukan operasi modulo terhadap total jumlah partisi topik aktif:
$$\text{Partition ID} = \text{abs}(\text{Murmur2}(\text{Key})) \pmod{\text{numPartitions}}$$
- Jaminan Konsistensi Rute: Keuntungan utama metode ini adalah menjamin seluruh pesan yang memiliki kunci yang sama akan selalu berakhir di partisi fisik yang sama. Hal ini mutlak dibutuhkan untuk jaminan pengurutan pesan kronologis.
- Bahaya Menambah Partisi: Jika kita menambah jumlah partisi topik di tengah jalan (misalnya dari 10 partisi menjadi 15 partisi), nilai
numPartitionsdi rumus modulo berubah. Akibatnya, pesan baru dengan kunci yang sama akan dirutekan ke nomor partisi yang berbeda, menghancurkan integritas pengurutan pesan bersejarah kita. Jangan pernah menambah partisi pada topik aktif yang datanya mengandalkan pengurutan kunci tanpa perencanaan migrasi data.
2. Sticky Partitioner (Pesan Tanpa Kunci) #
Sebelum versi 2.4, Kafka menggunakan algoritma Round-Robin klasik untuk data tanpa kunci. Pada model Round-Robin, produsen membagi pesan satu per satu ke setiap partisi secara berurutan. Ini melahirkan masalah besar: batch memori Accumulator untuk setiap partisi tidak pernah penuh terisi secara efisien, memicu pengiriman ribuan paket TCP berukuran sangat kecil. Hal ini memboroskan siklus CPU dan bandwidth jaringan.
Sebagai solusinya, Kafka memperkenalkan Sticky Partitioner pada versi 2.4:
- Cara Kerja: Sticky Partitioner akan mengunci satu partisi tujuan secara acak dan mengarahkan seluruh pesan tanpa kunci ke partisi tersebut sampai memori batch pengiriman (Record Accumulator) terpenuhi (mencapai
batch.sizeatau menyentuh timeoutlinger.ms). Setelah batch dikirim ke broker, partitioner baru akan memilih partisi berikutnya secara acak untuk mengumpulkan batch baru. - Keunggulan: Metode ini melipatgandakan performa jaringan produsen dengan memangkas overhead TCP. Dengan mengelompokkan pesan ke dalam satu partisi pada satu waktu, kapasitas kompresi data batch bekerja maksimal, menghasilkan throughput yang jauh lebih tinggi dan latensi CPU yang minim dibandingkan Round-Robin.
Diagram Perbandingan Aliran Partitioner #
Mari kita bandingkan bagaimana pesan-pesan didistribusikan dari produsen ke partisi broker menggunakan metode Sticky Partitioner asinkron vs Hashed Key Partitioner:
flowchart TD
subgraph InputEvents ["Aliran Event Klien"]
E1["Msg 1 (Key: 'UserA')"]
E2["Msg 2 (Key: null)"]
E3["Msg 3 (Key: 'UserA')"]
E4["Msg 4 (Key: null)"]
end
subgraph PartitionerEngine ["Partitioner Engine"]
direction TB
KeyCheck{"Apakah Event Memiliki Key?"}
KeyCheck -- "Ya" --> HashCalc["Murmur2(Key) % Partitions"]
KeyCheck -- "Tidak" --> StickyCalc["Sticky Batching (Isi Satu Partisi Dulu)"]
end
subgraph BrokerPartitions ["Target Partisi Broker"]
direction LR
P0[("Partisi 0")]
P1[("Partisi 1")]
end
E1 --> KeyCheck
E2 --> KeyCheck
E3 --> KeyCheck
E4 --> KeyCheck
HashCalc -->|"UserA selalu ke P0"| P0
StickyCalc -->|"Batch 1 (Msg 2 & 4) ke P1"| P1
style P0 fill:#ddffdd,stroke:#88ff88
style P1 fill:#ddffdd,stroke:#88ff88
Bahaya Key Skewness (Hot Partitions) dan Mitigasinya #
Meskipun menggunakan Hashed Key Partitioner terdengar ideal untuk menjaga pengurutan data, metode ini rentan memicu fenomena Key Skewness (ketimpangan beban) atau sering disebut Hot Partitions.
Mengapa Hot Partitions Terjadi? #
Ketimpangan beban terjadi jika distribusi kunci di dalam lalu lintas bisnis kita tidak merata.
- Misalnya, kita membagi partisi transaksi berdasarkan kunci
country_code(ID = Indonesia, SG = Singapura, US = Amerika Serikat). Karena 95% transaksi bisnis kita terjadi di Indonesia, maka partisi yang memegang hashIDakan terbebani secara ekstrem (CPU dan disk space broker penuh), sementara partisi lain untukSGdanUSkosong melompong. - Broker yang menyimpan partisi panas (hot partition) akan mengalami degradasi performa, memicu kelambatan bagi seluruh kluster.
Dampak Ketimpangan di Sisi Konsumen #
Masalah tidak berhenti di sisi broker. Dalam sebuah Consumer Group, setiap konsumen dialokasikan memegang partisi tertentu secara eksklusif.
- Jika terjadi Key Skewness, maka konsumen yang ditugaskan memegang hot partition (Partisi Indonesia) akan terbebani secara ekstrem. Konsumen tersebut harus mengolah 95% dari total lalu lintas data bisnis, memicu peningkatan consumer lag yang parah pada partisi tersebut.
- Sementara itu, konsumen lain yang memegang partisi Singapura dan Amerika Serikat akan menganggur (idle), membuang-buang sumber daya komputasi wadah kontainer server kita. Ini meniadakan manfaat penyeimbangan beban dari fitur Consumer Group.
Teknik Mitigasi: Key Salting #
Untuk memecah ketimpangan beban ini, kita dapat menerapkan teknik Key Salting (penaburan garam pada kunci). Kita menambahkan angka acak secara berkala di akhir kunci untuk mendistribusikan data ke beberapa partisi yang berbeda.
- Formula Salting:
SaltedKey = Key + "_" + random(1, S)di mana $S$ adalah rentang garam. - Contoh Penerapan: Jika kunci asli kita adalah
IDdan $S = 3$, maka kunci yang dihasilkan secara acak menjadiID_1,ID_2, atauID_3. Data transaksi Indonesia kini akan tersebar merata di 3 partisi yang berbeda, mengurangi beban broker dan menyeimbangkan alokasi kerja konsumen. - Trade-off: Kita kehilangan jaminan pengurutan data global untuk seluruh transaksi Indonesia. Namun, kita tetap mendapatkan jaminan pengurutan lokal di dalam masing-masing partisi garam (misalnya, data
ID_1tetap terurut di partisinya sendiri).
Anti-pattern vs Solusi: Isolasi Traffic dengan Custom Partitioner #
Salah satu kesalahan arsitektur terbesar adalah mencampurkan lalu lintas pesan yang membutuhkan prioritas tinggi (seperti transaksi pembayaran pelanggan premium VIP) dengan pesan biasa bervolume tinggi (seperti metrik analitik aplikasi biasa) di dalam partisi yang sama tanpa isolasi.
Konsekuensi Kegagalan: Jika partisi mengalami antrean panjang akibat antrean data analitik yang lambat diproses (consumer lag), transaksi pelanggan VIP akan ikut terhambat di antrean yang sama (Head-of-Line blocking). Ini merusak kepuasan pelanggan bisnis kita.
Solusi: Custom Partitioner Terisolasi #
Kita dapat menulis logika Custom Partitioner terprogram di sisi produsen untuk mengarahkan pesan ke partisi khusus berdasarkan kriteria bisnis (misalnya, jika metadata pesan menandakan akun VIP, arahkan ke partisi prioritas khusus).
Mari kita lihat perbedaan implementasi antara kode pembagian rute default yang rentan dan pembagian rute kustom terisolasi menggunakan Java:
// =========================================================================
// ANTI-PATTERN: Mengandalkan Default Partitioner untuk Semua Skenario
// Data penting VIP tercampur di partisi yang sama dengan data non-VIP.
// =========================================================================
public void sendUntunedData(KafkaProducer<String, String> producer, String customerId, String payload) {
// ✗ JANGAN: Mengirim data VIP tanpa rute khusus. Pesan akan di-hash
// secara default dan rentan terjebak lag di partisi yang sama dengan data biasa.
ProducerRecord<String, String> record = new ProducerRecord<>("transactions", customerId, payload);
producer.send(record);
}
// =========================================================================
// SOLUSI YANG BENAR: Implementasi Custom Partitioner Terisolasi
// Logika pembagian rute memisahkan partisi VIP (Partisi 0) dengan partisi biasa (Partisi 1 ke atas).
// =========================================================================
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.Cluster;
import java.util.Map;
import java.util.Properties;
public class VipPartitionSelector implements Partitioner {
private static final int VIP_PARTITION_ID = 0; // Partisi 0 khusus VIP
@Override
public void configure(Map<String, ?> configs) {
// Konfigurasi tambahan jika dibutuhkan
}
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
int numPartitions = cluster.partitionsForTopic(topic).size();
String customerKey = (String) key;
// ✓ BENAR: Isolasi data pelanggan VIP ke partisi khusus
if (customerKey != null && customerKey.startsWith("VIP_")) {
// Selalu arahkan pelanggan VIP ke Partisi 0
return VIP_PARTITION_ID;
}
// Untuk pelanggan reguler, distribusikan secara hashed ke partisi 1 ke atas
int regulerPartitionsCount = numPartitions - 1;
if (regulerPartitionsCount <= 0) {
return 0; // Fallback jika total partisi hanya 1
}
// Hashing modulo untuk sisa partisi reguler (1, 2, ..., numPartitions-1)
int hashedPartition = (Math.abs(org.apache.kafka.common.utils.Utils.murmur2(keyBytes)) % regulerPartitionsCount) + 1;
return hashedPartition;
}
@Override
public void close() {
// Pembersihan sumber daya
}
// Demonstrasi integrasi properti produser
public static Properties getProducerProperties(String bootstrapServers) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
// ✓ BENAR: Daftarkan custom partitioner ke konfigurasi producer
props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, VipPartitionSelector.class.getName());
return props;
}
}
Dengan menyematkan kelas kustom VipPartitionSelector ini pada properti produser kita, kita menjamin kelancaran penulisan data transaksi pelanggan VIP tanpa terpengaruh oleh kelambatan pemrosesan data konsumen reguler.
Ringkasan #
- Skalabilitas Partisi — Partisi adalah unit terkecil penentu paralelisme di Kafka. Tentukan kapasitas partisi berdasarkan target throughput penulisan dan pemrosesan.
- Formula Partisi — Jumlah partisi dihitung menggunakan rumus matematis: $P = \max(T_P / t_p, T_C / t_c)$ ditambah ruang toleransi cadangan sebesar 20%.
- Hashed Key Partitioner — Algoritma modulo Murmur2 yang menjamin konsistensi rute pesan berkunci ke partisi yang sama untuk pengurutan data kronologis.
- Sticky Partitioner — Algoritma performa tinggi untuk pesan tanpa kunci (null key) yang mengelompokkan pesan dalam satu partisi guna memaksimalkan batching jaringan.
- Key Skewness — Fenomena ketimpangan beban broker (hot partition) akibat kunci pesan terdistribusi secara tidak merata dalam lalu lintas bisnis.
- Dampak Konsumen — Key skewness memicu lag parah di satu konsumen group, sementara konsumen lain idle, meniadakan efisiensi pembagian beban kerja.
- Key Salting — Teknik menambahkan string/angka acak di akhir kunci pesan untuk memecah data panas ke beberapa partisi secara merata.
- Custom Partitioner — Menulis kelas pembagi rute kustom secara terprogram untuk mengisolasi aliran data prioritas tinggi (VIP) dari Head-of-Line blocking data reguler.