Partition Assignment Strategy #

Ketika kita meluncurkan kelompok konsumen (Consumer Group) untuk memproses data dari suatu topik di Apache Kafka, salah satu keputusan terpenting yang harus diambil adalah bagaimana partisi-partisi dari topik tersebut dibagi di antara konsumen yang aktif. Kafka tidak melakukan pembagian ini secara kaku dari sisi broker. Sebaliknya, koordinasi diserahkan kepada pustaka klien menggunakan komponen yang disebut Partition Assignor. Melalui pemilihan strategi pembagian partisi (partition assignment strategy) yang tepat, kita dapat mengontrol keadilan pembagian beban (load balancing), meminimalkan overhead akibat proses pembagian ulang (rebalance), dan menentukan seberapa tangguh aplikasi kita saat menghadapi penambahan atau pengurangan instansi konsumen secara dinamis. Membiarkan konfigurasi default berjalan tanpa analisis mendalam sering kali menjadi bom waktu yang memicu ketimpangan beban kerja (resource imbalance) di lingkungan produksi.


Mengapa Strategi Pembagian Partisi Sangat Penting? #

Dalam kluster Kafka berskala enterprise, sebuah Consumer Group sering kali berlangganan ke beberapa topik sekaligus. Setiap topik memiliki karakteristik jumlah partisi yang berbeda-beda. Di sisi konsumen, kita mungkin memiliki beberapa pod aplikasi (misalnya di Kubernetes) yang berjalan secara paralel.

Jika pembagian partisi tidak adil, kita akan menghadapi situasi di mana:

  • Satu konsumen kewalahan karena mendapatkan alokasi partisi yang memiliki volume lalu lintas data (traffic) sangat padat.
  • Konsumen lain di dalam kelompok yang sama justru menganggur (idle) karena hanya mendapatkan sedikit partisi atau bahkan tidak mendapatkannya sama sekali.
  • Proses rebalance memakan waktu sangat lama karena broker harus menghentikan seluruh aliran data hanya untuk memindahkan kepemilikan partisi yang sebenarnya tidak perlu dipindahkan.

Properti konfigurasi yang bertugas menentukan perilaku ini adalah partition.assignment.strategy. Klien konsumen Kafka memungkinkan kita menetapkan satu atau beberapa strategi sekaligus dalam bentuk daftar prioritas terpisah koma.


Membedah 4 Strategi Utama Partition Assignor #

Mari kita bedah secara mendalam empat strategi bawaan yang paling sering digunakan di Apache Kafka beserta karakteristik matematis dan visualisasinya.

1. Range Assignor (org.apache.kafka.clients.consumer.RangeAssignor) #

Ini adalah strategi default yang digunakan oleh klien konsumen Kafka jika kita tidak menentukan konfigurasi apa pun.

Cara Kerja Range Assignor #

Range Assignor bekerja secara independen untuk setiap topik. Untuk setiap topik yang disubscribe, Range Assignor akan:

  1. Mengurutkan seluruh partisi yang tersedia secara numerik (misal: 0, 1, 2, …).
  2. Mengurutkan seluruh konsumen aktif di dalam grup secara leksikografis (misal: C1, C2).
  3. Membagi jumlah partisi topik dengan jumlah konsumen untuk menentukan berapa banyak partisi yang diperoleh setiap konsumen. Jika ada sisa pembagian, konsumen pertama akan mendapatkan partisi ekstra.

Secara matematis, untuk topik tunggal dengan $n$ partisi dan $m$ konsumen:

  • Setiap konsumen akan mendapatkan $n / m$ partisi.
  • Konsumen pertama sebanyak $n % m$ akan mendapatkan satu partisi tambahan.

Kelemahan Range Assignor: Partition Imbalance #

Masalah besar terjadi jika konsumen berlangganan ke banyak topik secara bersamaan. Karena kalkulasi pembagian dilakukan secara terisolasi per topik, sisa pembagian akan selalu dibebankan kepada konsumen pertama (C1).

Misalkan kita memiliki 2 topik (Topic-A dan Topic-B), masing-masing memiliki 3 partisi (P0, P1, P2). Kita memiliki 2 konsumen (C1 dan C2) di dalam satu grup.

  • Kalkulasi Topic-A: 3 partisi / 2 konsumen = 1.5. C1 mendapatkan 2 partisi (P0, P1), C2 mendapatkan 1 partisi (P2).
  • Kalkulasi Topic-B: 3 partisi / 2 konsumen = 1.5. C1 mendapatkan 2 partisi (P0, P1), C2 mendapatkan 1 partisi (P2).
  • Hasil Akhir Alokasi:
    • C1 memegang 4 partisi: Topic-A (P0, P1) dan Topic-B (P0, P1).
    • C2 hanya memegang 2 partisi: Topic-A (P2) dan Topic-B (P2).

Jika kita memperbanyak jumlah topik menjadi puluhan, C1 akan menanggung beban kerja dua kali lipat lebih berat dibandingkan C2. Ini dapat memicu konsumsi memori yang tinggi pada pod C1 dan berujung pada crash akibat OutOfMemory (OOM).


2. Round Robin Assignor (org.apache.kafka.clients.consumer.RoundRobinAssignor) #

Untuk mengatasi ketimpangan beban yang dihasilkan oleh Range Assignor, kita dapat beralih ke strategi Round Robin.

Cara Kerja Round Robin Assignor #

Berbeda dengan Range Assignor, Round Robin meletakkan seluruh partisi dari seluruh topik yang disubscribe ke dalam satu daftar tunggal, lalu mendistribusikannya secara berputar satu per satu ke setiap konsumen.

Mari kita gunakan contoh yang sama: 2 topik (Topic-A dan Topic-B), masing-masing memiliki 3 partisi. Anggota grup kita adalah C1 dan C2.

  1. Seluruh partisi diurutkan secara kolektif: Topic-A-P0, Topic-A-P1, Topic-A-P2, Topic-B-P0, Topic-B-P1, Topic-B-P2.
  2. Distribusi melingkar dijalankan:
    • Topic-A-P0 -> C1
    • Topic-A-P1 -> C2
    • Topic-A-P2 -> C1
    • Topic-B-P0 -> C2
    • Topic-B-P1 -> C1
    • Topic-B-P2 -> C2
  3. Hasil Akhir Alokasi:
    • C1 memegang 3 partisi: Topic-A-P0, Topic-A-P2, Topic-B-P1.
    • C2 memegang 3 partisi: Topic-A-P1, Topic-B-P0, Topic-B-P2.

Kelemahan Round Robin: Rebalance Overhead #

Meskipun distribusinya sangat merata, Round Robin memiliki kelemahan besar saat terjadi proses rebalance. Karena ia tidak melacak alokasi sebelumnya, perubahan kecil pada keanggotaan grup dapat memicu pemindahan kepemilikan partisi secara acak dan masif. Hal ini memaksa konsumen untuk menutup koneksi socket lama, menghapus cache lokal, dan membuka koneksi baru ke broker leader partisi yang baru dialokasikan.


3. Sticky Assignor (org.apache.kafka.clients.consumer.StickyAssignor) #

Strategi Sticky Assignor dirancang untuk menyelesaikan dilema di antara Range dan Round Robin: memberikan pembagian beban yang merata sekaligus menjaga stabilitas alokasi partisi saat terjadi rebalance.

Tujuan utama Sticky Assignor adalah:

  1. Keseimbangan Utama (Balance): Mendistribusikan partisi seadil mungkin di antara konsumen yang aktif (seperti Round Robin).
  2. Kelengketan Maksimal (Sticky): Saat rebalance terjadi, pastikan partisi yang sebelumnya sudah dipegang oleh konsumen tidak dipindahkan ke konsumen lain, kecuali jika memang mutlak diperlukan untuk menjaga keseimbangan.

Contoh Kasus Kelengketan #

Bayangkan kita memiliki 3 konsumen (C1, C2, C3) membaca 3 partisi (P0, P1, P2). Keadaan awal: C1->P0, C2->P1, C3->P2. Jika C3 mengalami crash:

  • Round Robin: Bisa saja mengacak alokasi menjadi C1->P1, P2 dan C2->P0. Di sini, C1 kehilangan P0 dan C2 kehilangan P1.
  • Sticky Assignor: Menjamin alokasi sebelumnya tetap lengket: C1 tetap memegang P0 dan C2 tetap memegang P1. Satu-satunya partisi yang dialokasikan ulang hanyalah P2 milik C3 yang mati. P2 akan diberikan kepada C1 atau C2.

Dengan menjaga kelengketan ini, kita menghemat sumber daya komputasi dan bandwidth jaringan secara signifikan karena sebagian besar konsumen dapat terus memproses data dari partisi lama mereka tanpa interupsi koneksi.


4. Cooperative Sticky Assignor (org.apache.kafka.clients.consumer.CooperativeStickyAssignor) #

Diperkenalkan sejak Apache Kafka 2.4, ini adalah puncak evolusi dari strategi pembagian partisi di Kafka.

Masalah Eager Rebalance pada Pustaka Lama #

Sebelum versi 2.4, seluruh rebalance di Kafka menggunakan protokol Eager Rebalance. Protokol ini menerapkan pola Stop-The-World. Ketika rebalance terjadi, seluruh konsumen di dalam grup harus melepaskan seluruh partisi mereka secara serentak, berhenti memproses data, bergabung kembali ke grup, dan menunggu hasil alokasi partisi baru selesai dihitung. Hal ini memicu jeda pemrosesan data (lag spikes) yang sangat mengganggu sistem real-time.

Solusi: Cooperative Rebalance (Incremental) #

CooperativeStickyAssignor menggunakan protokol Cooperative Rebalance. Alih-alih mereset seluruh dunia, strategi ini memproses perubahan secara bertahap (incremental):

  1. Ketika konsumen baru bergabung, group leader menghitung partisi mana saja yang perlu dipindahkan untuk mencapai keseimbangan.
  2. Konsumen yang tidak terpengaruh oleh perpindahan partisi tersebut tetap diperbolehkan terus membaca dan memproses data tanpa henti.
  3. Hanya partisi yang akan dipindahkan saja yang dicabut dari pemilik lamanya secara tertib, lalu diserahkan ke pemilik baru pada siklus rebalance kecil berikutnya.

Mekanisme ini menghilangkan jeda pemrosesan stop-the-world secara total untuk sebagian besar anggota grup, menjadikan pipeline data kita jauh lebih mulus.

flowchart TD
    subgraph Eager["Eager Protocol (Range / RoundRobin / Sticky)"]
        direction TB
        E1["Rebalance Dimulai"] --> E2["SEMUA Konsumen Melepas SEMUA Partisi"]
        E2 --> E3["Pemrosesan Berhenti Total (Stop-The-World)"]
        E3 --> E4["Kalkulasi Alokasi Baru Selesai"]
        E4 --> E5["Konsumen Mulai Membaca Kembali"]
    end

    subgraph Cooperative["Cooperative Protocol (Cooperative Sticky)"]
        direction TB
        C1["Rebalance Dimulai"] --> C2["Hitung Partisi yang Perlu Dipindahkan"]
        C2 --> C3["Hanya Cabut Partisi yang Berpindah Pemilik"]
        C3 --> C4["Konsumen Lain Terus Memproses Tanpa Jeda"]
        C4 --> C5["Serahkan Partisi Terpilih ke Pemilik Baru"]
    end

    style Eager stroke:#c62828,stroke-width:2px
    style Cooperative stroke:#2e7d32,stroke-width:2px

Tabel Perbandingan Strategi Partition Assignor #

Tabel di bawah ini merangkum perbandingan komparatif dari keempat strategi pembagian partisi:

Kriteria Range Assignor Round Robin Assignor Sticky Assignor Cooperative Sticky
Distribusi Beban (Multi-Topic) ✗ Sering timpang (imbalance) ✓ Sangat merata ✓ Sangat merata ✓ Sangat merata
Keamanan dari OOM Rendah (karena ketimpangan) Tinggi Tinggi Tinggi
Kelengketan Partisi Rendah Sangat Rendah Tinggi Tinggi
Protokol Rebalance Eager (Stop-The-World) Eager (Stop-The-World) Eager (Stop-The-World) Cooperative (Incremental)
Efek Jeda Pemrosesan Tinggi Tinggi Sedang Sangat Rendah
Default SDK Ya (sejak Kafka awal) Tidak Tidak Ya (pada beberapa framework modern)

Mermaid Flowchart: Memilih Partition Assignor yang Tepat #

Bagan alir keputusan di bawah ini dapat membantu kita menentukan strategi assignor mana yang paling cocok untuk kebutuhan sistem kita:

flowchart TD
    Start["Mulai Evaluasi Strategi Assignor"] --> Q1{"Apakah konsumen hanya berlangganan ke 1 Topik saja?"}
    
    Q1 -- "Ya" --> Q2{"Apakah Anda menggunakan Kafka versi 2.4 atau lebih baru?"}
    Q1 -- "Tidak" --> Q3{"Apakah lalu lintas data sangat sensitif terhadap jeda rebalance?"}
    
    Q2 -- "Ya" --> ChooseCooperative["Rekomendasi: CooperativeStickyAssignor"]
    Q2 -- "Tidak" --> ChooseRange["Gunakan Default: RangeAssignor"]
    
    Q3 -- "Ya" --> ChooseCooperative
    Q3 -- "Tidak" --> Q4{"Apakah pembagian beban yang merata adalah prioritas utama?"}
    
    Q4 -- "Ya" --> ChooseRoundRobin["Gunakan: RoundRobinAssignor"]
    Q4 -- "Tidak" --> ChooseSticky["Gunakan: StickyAssignor"]

    style ChooseCooperative stroke:#2e7d32,stroke-width:2px
    style ChooseRange stroke:#455a64,stroke-width:2px
    style ChooseRoundRobin stroke:#f57c00,stroke-width:2px
    style ChooseSticky stroke:#0288d1,stroke-width:2px

Kode Penerapan dan Konfigurasi Assignor di Klien #

Mari kita lihat perbandingan implementasi kode konfigurasi antara penanganan bawaan yang salah dengan penanganan optimal menggunakan pustaka Java SDK.

Java SDK Anti-Pattern: Penggunaan Default RangeAssignor pada Multi-Topic #

Kode di bawah ini menunjukkan kesalahan umum di mana tim developer membiarkan alokasi default berjalan saat aplikasi harus mengonsumsi banyak topik secara bersamaan.

// ANTI-PATTERN: Membiarkan RangeAssignor mengelola multi-topic subscriber
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "multi-topic-consumer-group");
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");

// ✗ JANGAN: Membiarkan default strategi (RangeAssignor) aktif jika aplikasi kita
// berlangganan ke banyak topik dengan jumlah partisi yang tidak merata.
// Hal ini akan menumpuk beban partisi berlebih pada konsumen pertama di dalam grup.
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

// Berlangganan ke 5 topik yang masing-masing memiliki 3 partisi
consumer.subscribe(Arrays.asList(
    "order-events", 
    "payment-events", 
    "shipping-events", 
    "inventory-events", 
    "notification-events"
));

Java SDK Solusi: Menerapkan CooperativeStickyAssignor #

Untuk beralih ke strategi pemrosesan tingkat lanjut yang toleran terhadap jeda dan adil dalam pembagian beban, kita wajib mendeklarasikan kelas CooperativeStickyAssignor pada parameter konfigurasi konsumen.

// BENAR: Mengaktifkan CooperativeStickyAssignor untuk efisiensi maksimal
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "multi-topic-consumer-group");
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");

// ✓ Mengatur partition assignment strategy ke CooperativeStickyAssignor
// Ini akan mendistribusikan partisi secara merata lintas topik dan mencegah efek stop-the-world
props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, 
    Collections.singletonList(CooperativeStickyAssignor.class.getName())
);

props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

// Berlangganan ke banyak topik dengan aman
consumer.subscribe(Arrays.asList(
    "order-events", 
    "payment-events", 
    "shipping-events", 
    "inventory-events", 
    "notification-events"
));

Konfigurasi Multi-Strategy Fallback #

Menariknya, Kafka mengizinkan kita mendefinisikan beberapa kelas assignor sekaligus. Langkah ini berguna jika kita sedang melakukan migrasi grup konsumen secara bertahap dari satu assignor ke assignor lainnya tanpa mematikan kluster aplikasi.

// BENAR: Menyediakan fallback strategi untuk skenario migrasi bertahap (Rolling Upgrade)
props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, Arrays.asList(
    CooperativeStickyAssignor.class.getName(),
    StickyAssignor.class.getName()
));
  • Cara Kerja: Saat melakukan migrasi rolling upgrade, konsumen baru yang menyala dengan konfigurasi di atas akan bernegosiasi dengan konsumen lama. Karena keduanya memiliki kecocokan pada StickyAssignor (sebagai pilihan kedua bagi konsumen baru), kelompok konsumen akan sementara waktu menggunakan StickyAssignor hingga seluruh pod selesai di-upgrade ke konfigurasi baru yang mendukung CooperativeSticky sepenuhnya.

Ringkasan #

  • Partition Assignor — Pustaka klien yang bertugas menghitung dan mendistribusikan alokasi partisi topik di antara konsumen aktif dalam satu grup.
  • Range Assignor — Strategi default yang membagi partisi per topik secara berurutan. Berisiko memicu ketimpangan beban (partition imbalance) jika berlangganan ke banyak topik sekaligus.
  • Round Robin Assignor — Membagikan seluruh partisi dari seluruh topik secara merata secara melingkar. Namun, memicu overhead rebalance tinggi karena rentan memindahkan alokasi partisi secara acak.
  • Sticky Assignor — Menyeimbangkan pembagian partisi secara adil sekaligus berupaya menjaga agar kepemilikan partisi lama tetap melekat pada konsumen yang sama saat rebalance terjadi.
  • Cooperative Sticky Assignor — Strategi modern yang mengeliminasi jeda pemrosesan global (stop-the-world) menggunakan protokol Cooperative Rebalance secara bertahap (incremental).
  • partition.assignment.strategy — Properti konfigurasi kunci yang digunakan pada sisi konsumen untuk menentukan atau mengubah taktik pembagian partisi yang digunakan.
  • Rolling Upgrade Safe — Pendefinisian daftar strategi assignor terpisah koma memungkinkan kluster melakukan fallback otomatis selama masa transisi pembaruan aplikasi produksi.

← Sebelumnya: Consumer Group   Berikutnya: Rebalance Process →

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