Mesaj Hangi Partition'a Gidecek? Kafka Partitioner Stratejileri
Kafka serisinin üçüncü yazısı: bir mesajın hangi partition'a düşeceğine karar veren partitioner. Hash-based, round-robin, sticky ve custom stratejiler; her seçimin sıralama garantisine ödettiği bedel ve üretim ortamlarındaki gerçek kullanım oranları.
Bu yazı, Kafka serisinin üçüncü parçası. İlk yazıda bir cluster’ın nasıl
kurulduğunu (buradan okunabilir), ikinci yazıda
ise bir mesajın hangi partition’a yazıldığını, offset’in rolünü ve sıralama
garantilerini ele almıştık
(buradan okunabilir). İkinci yazının
temel formülü şuydu: bir mesaj, hash(key) % partition_sayısı ile bir
partition’a düşer.
Peki bu kararı kim veriyor? Daha da önemlisi: bu davranış değiştirilebilir mi? Örneğin “hash kullanma, mesajları sırayla partition’lara dağıt” demek mümkün mü?
Mümkün. Bu kararı veren bileşene partitioner denir ve birden fazla çeşidi vardır. Ancak — bu yazının asıl konusu da bu — her seçim bir şeyi kazandırırken başka bir şeyden vazgeçirir. O yüzden seçenekleri tek tek, bedelleriyle birlikte incelemek gerekiyor.
Partitioner nedir?
Partitioner, producer tarafında çalışan ve “bu mesaj hangi partition’a gitsin?” sorusunu cevaplayan bileşendir. Producer bir mesajı göndermeden hemen önce partitioner devreye girer ve hedef partition numarasını belirler.
Kafka’nın varsayılan davranışı şöyle: mesajın bir key’i varsa, o key’in
hash’i alınır ve partition sayısına göre modu hesaplanır (ikinci yazıdaki
hash(key) % partition_sayısı). Key yoksa (null), bambaşka bir strateji
devreye girer. İşte o “başka strateji”nin ne olduğunu anlamak için partitioner
çeşitlerine bakmak gerekiyor.
Producer tarafında bu davranış, kullanılacak partitioner sınıfını belirten tek bir konfigürasyon ayarıyla değiştirilir. Şimdi seçenekleri sırayla ele alalım.
1. Hash-Based (Keyed) Partitioner — varsayılan
İkinci yazıda ayrıntısıyla anlatılan mekanizma bu. Mesaja bir key verilir
(order_id, user_id gibi), Kafka da o key’in hash’ine göre partition’ı seçer.
order_id = 5 → hash(5) % 3 → P1 (her zaman)
En kritik özellik şu: aynı key her zaman aynı partition’a gider. Böylece aynı siparişe ait tüm event’ler (“oluşturuldu → ödendi → kargolandı”) tek bir partition’da, offset sırasıyla, sıralı durur.
Sıralama gerekiyorsa, doğru cevap neredeyse her zaman budur.
Bu yüzden CDC (Change Data Capture), finans, e-ticaret gibi aynı entity’nin
event’lerinin doğru sırayla işlenmesi gereken her senaryoda bu strateji
tercih edilir. Bu projedeki pipeline’da da (order_id key olarak) tam olarak bu
kullanılıyor.
2. Round-Robin Partitioner
Round-Robin, key’e hiç bakmadan mesajları partition’lara sırayla dağıtır: birinci mesaj P0’a, ikinci P1’e, üçüncü P2’ye, dördüncü yeniden P0’a…
mesaj 1 → P0
mesaj 2 → P1
mesaj 3 → P2
mesaj 4 → P0
İlk bakışta cazip görünüyor: yük partition’lara kusursuz eşitlikte dağılır, hot key riski kalmaz. Ama bunun ağır bir bedeli var.
Round-Robin sıralamayı bozar
Aynı kullanıcının üç event’inin art arda geldiğini düşünelim:
User_A → Sipariş Oluşturuldu → P0
User_A → Ödeme Tamamlandı → P1
User_A → Kargo Hazırlandı → P2
Kafka, sıralama garantisini yalnızca partition içinde verir — bunu ikinci yazıda görmüştük. Bu üç event üç ayrı partition’a düştüğü için consumer’lar onları birbirinden bağımsız ve asenkron okur. “Ödeme Tamamlandı”yı işleyen consumer, “Sipariş Oluşturuldu”yu işleyenden önce davranabilir. Sonuç: ortada henüz sipariş yokken ödeme işlenmeye çalışılır — klasik bir race condition ve veri tutarsızlığı.
Round-Robin, ilişkili event’lerin sırasını kaybettirir. Sıralamanın önemli olduğu hiçbir yerde kullanılmaz.
3. Sticky Partitioner — modern varsayılan (key = null)
Peki amaç yalnızca “key vermeden veriyi eşit dağıtmak” ise? Bunun için
Round-Robin’e gerek yok. Kafka 2.4’ten beri, key null olduğunda devreye giren
varsayılan mekanizma Sticky Partitioner’dır ve eski Round-Robin’i tamamen
emekliye ayırmıştır.
Aradaki fark performansta gizli:
- Round-Robin, her tekil mesajı farklı bir partition’a gönderir. Network paketleri (batch) hiç dolmadan sürekli yola çıkar; bu da yüksek overhead demektir.
- Sticky Partitioner ise mesajları batch’ler halinde biriktirir. Bir batch dolana kadar tüm mesajları aynı partition’a yazar; batch dolup gönderilince bir sonraki partition’a geçer.
Sonuçta yük yine partition’lara dengeli dağılır; ama batch’ler ağzına kadar dolu gittiği için throughput (veri geçiş hızı) belirgin biçimde artar. Metrik toplama, IoT sensör verisi, clickstream gibi sıranın önemsiz olduğu senaryolarda ideal seçenek budur.
Not: Sticky Partitioner da tıpkı Round-Robin gibi sıra garantisi vermez — yalnızca aynı işi çok daha verimli yapar.
4. Custom Partitioner
Bazen hazır stratejilerin hiçbiri iş mantığına oturmaz. O zaman Partitioner
arayüzü elle implement edilir ve özel bir sınıf yazılır.
En klasik senaryo, multi-tenancy ve hot partition problemidir. Bir SaaS şirketi düşünelim: bir tarafta devasa bir “Premium” müşteri, diğer tarafta yüzlerce küçük “Free” müşteri var. İş hash’e bırakılırsa, Premium müşterinin milyonlarca event’i tek bir partition’a yığılıp onu kilitleyebilir — ikinci yazıdaki data skew probleminin ta kendisi.
Custom partitioner ile yük şöyle izole edilir: Premium müşteriden gelen mesajlar ona ayrılmış birkaç partition’a dağıtılır; Free müşterilerin tamamı ise tek bir partition’da toplanır. Şema olarak:
Premium tenant → P0, P1, P2 (yük dağıtılır)
Free tenant'lar → P3 (hepsi tek partition'da toplanır)
Buradaki asıl fikir şu: partition seçimini artık matematik değil, iş mantığı belirliyor. Multi-tenancy, co-location (ilişkili verileri kasıtlı olarak aynı partition’da tutma) ya da co-partitioning gibi özel ihtiyaçlar için bu kapı açılır. Ama bir bedeli var: sıralama, hot key ve rebalancing gibi tüm garantilerin sorumluluğu artık geliştiricinin omuzlarındadır.
Gerçek dünyada hangisi ne kadar kullanılıyor?
Teoride dört seçenek de masada. Üretim (production) ortamlarındaki gerçek dağılım ise hayli dengesiz:
| Partitioner | Kullanım Oranı | En Çok Nerede | Sıralama Garantisi |
|---|---|---|---|
| Hash-Based (Keyed) | ~%75–80 | CDC, finans, e-ticaret, event akışları | Evet (aynı key için) |
| Sticky / Default (key=null) | ~%15–20 | Metrik, IoT, clickstream | Hayır |
| Custom | ~%1–5 | Multi-tenancy, co-location | Senaryoya göre |
| Round-Robin | <%1 | Test / nadir legacy sistemler | Hayır |
Bu dengesizliğin nedenleri:
- Hash-Based ezici çoğunlukta, çünkü gerçek projelerin çoğunda veri
tutarlılığı ve işlem sırası her şeyden önce gelir. Aynı kaydın (aynı primary
key’e sahip satırın)
INSERT → UPDATE → DELETEevent’leri sırayla işlenmelidir; aksi halde hedefteki replika bozulur. - Sticky, anahtarsız veri için standart haline geldi ve eski Round-Robin’i devre dışı bıraktı.
- Custom nadirdir, çünkü ancak varsayılan algoritmanın yetmediği özel durumlarda yazılır — yazması da bakımı da maliyetlidir.
- Round-Robin neredeyse hiç kullanılmaz: hem key’i yok saydığı için sırayı bozar, hem de mesajları partition’lara tek tek dağıttığından batch’ler dolmadan, küçük parçalar halinde gönderilir — bu da istek sayısını artırıp gecikmeyi yükseltir. Anahtarsız veri için zaten daha iyisi (Sticky) mevcut.
Özet: karar matrisi
Yeni bir pipeline tasarlarken yönü genellikle tek bir soru belirler:
İlişkili event’lerin sırası önemli mi?
- Evet → mesaja anlamlı bir key verilir (
order_id,user_id) ve varsayılan Hash-Based partitioner ile ilerlenir. Sıra korunur. - Hayır, tek hedef maksimum throughput ve eşit dağılım → key
nullbırakılır; Sticky Partitioner işi otomatik ve verimli biçimde halleder. - Varsayılanların hiçbiri iş mantığına uymuyor (hot tenant izolasyonu, co-location) → o zaman ve yalnızca o zaman bir Custom Partitioner yazılır.
Round-Robin ise pratikte listeden çıkarılabilir. “Anahtarsız ama eşit dağılım” istenen her yerde onun modern ve daha hızlı halefi olan Sticky Partitioner zaten görevde.
Bir sonraki yazıda, partition’lara bu şekilde yazılan verinin consumer tarafında nasıl ölçekli okunduğuna — consumer group’lara, rebalancing’e ve offset commit stratejilerine — daha yakından bakacağız.
Kaynak: https://deniz-blog.vercel.app/blog/kafka-partitioner-cesitleri/