Bir e-ticaret sisteminde "sipariş oluşturuldu" olayını beş farklı yer duymak ister: fatura servisi, stok servisi, kargo entegrasyonu, öneri motoru ve veri ambarı. Klasik bir kuyrukta bu mesajı bir tüketici alır ve mesaj gider; ikinci ekip aynı olayı istediğinde ya kuyruğu çoğaltırsın ya da üreticiyi değiştirirsin. Apache Kafka bu problemi tersinden çözer: mesajı kimse "tüketip yok etmez", olay diske sıralı biçimde yazılır ve isteyen herkes kendi hızında, kendi konumundan okur.
Bu rehberde Apache Kafka'nın gerçekte ne olduğunu, neden bir kuyruktan çok bir "dağıtık kayıt defteri" gibi davrandığını anlatacağım. Topic, partition ve offset üçlüsünü örneklerle çözeceğiz, ZooKeeper'a ihtiyaç duymayan KRaft modunda Docker ile tek düğümlü bir Kafka kuracağız, konsol araçlarıyla ilk mesajımızı yazıp okuyacağız. Sonra tüketici gruplarının ölçeklemeyi nasıl belirlediğine, saklama politikasının diskini nasıl doldurduğuna ve Kafka'yı seçmenin ne zaman fazla ağır bir karar olduğuna bakacağız.
Kafka'yı Klasik Kuyruklardan Ayıran Şey#
Kafka'yı ilk kez kuran çoğu kişi ona bir mesaj kuyruğu gibi davranır ve kısa sürede tıkanır. Aradaki temel fark şudur: RabbitMQ gibi bir broker'da mesaj tüketildiğinde kuyruktan silinir, Kafka'da ise mesaj yalnızca sona eklenen (append-only) bir log dosyasına yazılır ve saklama süresi dolana kadar orada durur. Tüketici mesajı "almaz", log içinde kaçıncı kayda kadar geldiğini gösteren bir sayaç tutar. Bu sayaca offset denir ve Kafka'nın tüm mantığı bu tek sayının etrafında kuruludur.
Bunun pratikteki sonucu güçlüdür. Aynı veriyi okuyan on farklı ekip birbirini hiç etkilemez, çünkü her biri kendi offset'ini tutar. Bir hata bulup son üç saatlik veriyi yeniden işlemek istersen offset'i geri alırsın ve olaylar sırayla tekrar akar; kuyruk tabanlı bir sistemde bu ancak mesajları yeniden üretmekle mümkün olur. Buna karşılık Kafka, tek tek mesaj bazında öncelik verme, mesajı gecikmeli teslim etme veya karmaşık yönlendirme kuralları kurma konusunda zayıftır — bunlar bir broker'ın işidir. Kuyruk tarafındaki bu esnekliğe ihtiyacın varsa RabbitMQ kurulumu yazısındaki exchange–binding modeli sana daha uygun gelecektir.
| Konu | Kafka | Klasik broker (AMQP) |
|---|---|---|
| Mesajın ömrü | Saklama süresi boyunca diskte kalır | Tüketilince silinir |
| Okuma konumu | Tüketici offset tutar | Broker teslim eder |
| Aynı veriyi çok tüketici | Doğal, birbirini etkilemez | Fanout kurmak gerekir |
| Yeniden işleme | Offset'i geri al, akar | Mesajı yeniden üret |
| Mesaj başına öncelik | Yok | Var |
| Tipik hacim | Saniyede yüz binlerce olay | Saniyede binlerce iş |
Topic, Partition ve Offset: Üç Kavramla Her Şey#
Topic bir veri akışının adıdır: siparisler, kullanici-olaylari, odeme-loglari gibi. Bir topic tek bir dosya değildir; içi partition adı verilen paralel loglara bölünmüştür. Her partition kendi içinde kesin sıralıdır ve her kaydın o partition'a özel artan bir offset numarası vardır. Kafka'nın ölçeklenmesi tamamen buradan gelir: bir topic'in 6 partition'ı varsa, o topic'i 6 tüketici paralel okuyabilir.
Sıralama garantisinin sınırını burada net biçimde öğrenmek gerekir, çünkü üretimdeki en pahalı hatalardan biri budur. Kafka topic genelinde değil, yalnızca partition içinde sırayı garanti eder. Aynı siparişe ait "oluşturuldu", "ödendi", "kargolandı" olaylarının sırasının bozulmasını istemiyorsan, bu üç mesajı aynı partition'a düşürmelisin. Bunun yolu da mesaj anahtarıdır (key): Kafka anahtarın hash'ini alır ve aynı anahtar hep aynı partition'a gider. Anahtar olarak sipariş numarasını verirsen sıra korunur; anahtarı boş bırakırsan mesajlar partition'lara dağıtılır ve sıra garantisi kaybolur.
topic: siparisler (3 partition)
partition 0 | 0 1 2 3 4 5 → (key: SIP-1001, SIP-1004 ...)
partition 1 | 0 1 2 3 → (key: SIP-1002 ...)
partition 2 | 0 1 2 3 4 → (key: SIP-1003 ...)
↑
offset numaraları her partition içinde bağımsızdır
Partition sayısını baştan biraz cömert seçmek iyi bir alışkanlıktır, çünkü sonradan artırmak mümkündür ama artırdığın anda anahtar–partition eşlemesi değişir ve eski anahtarlar farklı partition'lara düşmeye başlar; yani sıralama garantisi geçiş anında kırılır. Küçük bir sistemde topic başına 3–6 partition makul bir başlangıçtır.
Docker ile Tek Düğümlü Kafka Kurulumu#
Modern Kafka artık ZooKeeper istemiyor; koordinasyonu kendi içinde KRaft modu ile yapıyor. Bu, geliştirme ortamında tek konteynerle çalışan bir Kafka kurabilmen anlamına geliyor. Aşağıdaki compose.yml dosyası tek düğümlü, veriyi kalıcı bir volume'de tutan bir kurulum başlatır:
services:
kafka:
image: apache/kafka:latest
container_name: kafka
ports:
- "9092:9092"
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
# Dışarıdan bağlanan istemcilere broker'ın kendini nasıl tanıtacağı:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
# Tek düğümde replikasyon yapılamaz, iç topic'ler için 1 verilmeli:
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
volumes:
- kafka-data:/var/lib/kafka/data
volumes:
kafka-data:
Ayağa kaldırmak için:
docker compose up -d
# Broker'ın hazır olduğunu doğrula (birkaç saniye sürebilir)
docker compose logs -f kafka | grep -i "Kafka Server started"
Buradaki en kritik satır KAFKA_ADVERTISED_LISTENERS'tır ve yeni başlayanların yüzde doksanı burada takılır. Broker, bağlanan istemciye "beni şu adresten bul" der; bu adres yanlışsa istemci ilk el sıkışmayı yapar, sonra reklam edilen adrese bağlanmaya çalışır ve zaman aşımına düşer. Kafka'ya başka bir konteynerden erişeceksen localhost değil servis adını (kafka:9092) reklam etmelisin. Compose kullanımına yabancıysan Docker Compose kullanımı yazısı servis-ağ ilişkisini net biçimde açıklıyor; verinin konteyner silinince uçmaması için de Docker volume veri yönetimi yazısına göz atmanı öneririm.
İlk Topic'i Oluştur, Mesaj Yaz ve Oku#
Kafka'nın kendi komut satırı araçları konteynerin içinde /opt/kafka/bin altındadır. Önce bir topic oluşturalım:
# 3 partition, tek düğüm olduğu için replikasyon 1
docker exec -it kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 \
--create --topic siparisler --partitions 3 --replication-factor 1
# Topic listesini ve detayını gör
docker exec -it kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 --describe --topic siparisler
--describe çıktısı sana her partition'ın lideri ve replikalarını gösterir:
Topic: siparisler PartitionCount: 3 ReplicationFactor: 1
Topic: siparisler Partition: 0 Leader: 1 Replicas: 1 Isr: 1
Topic: siparisler Partition: 1 Leader: 1 Replicas: 1 Isr: 1
Topic: siparisler Partition: 2 Leader: 1 Replicas: 1 Isr: 1
Şimdi anahtarlı mesaj üretelim. --property ile anahtar ayracını tanımlarsan konsol üreticisi anahtar:değer biçimini kabul eder:
docker exec -it kafka /opt/kafka/bin/kafka-console-producer.sh \
--bootstrap-server localhost:9092 --topic siparisler \
--property "parse.key=true" --property "key.separator=:"
# Ardından satır satır yaz:
# SIP-1001:{"durum":"olusturuldu","tutar":249.90}
# SIP-1001:{"durum":"odendi","tutar":249.90}
Başka bir terminalde en baştan okuyalım:
docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 --topic siparisler \
--from-beginning --property print.key=true --property print.partition=true
--from-beginning bayrağı offset'i sıfırdan başlatır. Bu bayrağı kaldırıp komutu tekrar çalıştırırsan hiçbir şey görmezsin, çünkü varsayılan davranış "şu andan sonrasını dinle"dir. "Mesaj gelmiyor" diye saatlerce debug edilen durumların büyük kısmı budur.
Tüketici Grupları ve Gerçek Ölçekleme#
Tek başına bir tüketici bir topic'in tüm partition'larını okur. Yükü paylaştırmak istediğinde aynı group.id değerini taşıyan birden fazla tüketici başlatırsın; Kafka partition'ları bu tüketiciler arasında paylaştırır. Kural nettir ve akılda tutulması gerekir: bir partition aynı anda grup içindeki yalnızca bir tüketiciye atanır. Yani 3 partition'lı bir topic'te 5 tüketici çalıştırırsan ikisi boşta bekler. Paralelliğinin tavanı partition sayısıdır.
# Aynı gruptan iki tüketici başlat (iki ayrı terminalde)
docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 --topic siparisler --group fatura-servisi
# Grubun durumunu ve gecikmesini (lag) izle
docker exec -it kafka /opt/kafka/bin/kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 --describe --group fatura-servisi
Bu son komutun çıktısındaki LAG sütunu, üretimde her gün bakman gereken tek sayıdır: üreticinin yazdığı son offset ile tüketicinin işlediği offset arasındaki fark. Lag sabit bir bandın içinde salınıyorsa sistem sağlıklıdır; sürekli büyüyorsa tüketicilerin üretim hızına yetişemiyor demektir ve ya tüketici sayısını (partition sayısına kadar) artırman ya da işlemeyi hızlandırman gerekir.
| Sorun | Belirti | Çözüm |
|---|---|---|
| Tüketici yetişemiyor | LAG sürekli artıyor | Tüketici sayısını artır, partition sayısını yükselt |
| Boşta tüketici var | Tüketici sayısı > partition | Partition sayısını artır |
| Sıra bozuluyor | Anahtar boş gönderiliyor | Mesaj anahtarını (key) doldur |
| Sürekli yeniden dengeleme | İşleme süresi çok uzun | max.poll.interval.ms artır, işi kısalt |
Saklama Politikası, Disk ve Dayanıklılık#
Kafka mesajı silmez; saklama politikası siler. Varsayılan olarak veriler bir süre (klasik olarak 7 gün) tutulur ve sonra segment segment temizlenir. İki eksen vardır: süreye göre (retention.ms) ve boyuta göre (retention.bytes). Hangisi önce dolarsa silme o zaman başlar. Ayrıca cleanup.policy değeri delete yerine compact yapılırsa Kafka her anahtarın yalnızca en son değerini tutar — bu, "şu kullanıcının güncel adresi" gibi durum tablolarını topic üzerinde taşımak için kullanılan güçlü bir moddur.
# Topic'in saklama süresini 3 güne, boyut tavanını partition başına 5 GB'a çek
docker exec -it kafka /opt/kafka/bin/kafka-configs.sh \
--bootstrap-server localhost:9092 --alter --entity-type topics \
--entity-name siparisler \
--add-config retention.ms=259200000,retention.bytes=5368709120
Diski dolduran Kafka kurulumlarının neredeyse tamamı bu iki ayarı hiç değiştirmemiş olanlardır: yüksek hacimli bir topic, varsayılan sürede sessizce yüzlerce gigabayta ulaşır ve bir sabah broker disk dolu diye yazmayı reddeder. Kurulum yaparken partition sayısı × günlük hacim × saklama günü hesabını kabaca yapıp diskini ona göre seçmelisin. Böyle bir kurulum için VDS sınıfında SSD diskli bir makine, paylaşımlı bir ortamdan çok daha öngörülebilir davranır.
Dayanıklılık tarafında iki ayar belirleyicidir. Üretici tarafındaki acks=all, mesajın yalnızca lidere değil senkron replikaların hepsine yazıldığında onaylanmasını sağlar. Broker tarafındaki min.insync.replicas=2 ise en az iki replika ayakta değilse yazmayı reddeder. Bu ikisi birlikte kullanılmazsa acks=all tek başına bir şey garanti etmez — tek replika ayaktaysa "hepsi" yine tek replikadır. Üç düğümlü bir kümede replication.factor=3 ve min.insync.replicas=2 yaygın ve dengeli bir seçimdir.
Sık Yapılan Hatalar ve Kafka'yı Seçmemek Gereken Yerler#
En sık gördüğüm hata, Kafka'yı ihtiyaç yokken kurmaktır. Günde birkaç bin işin sıraya girdiği bir uygulamada Kafka; üç düğüm, disk planlaması, izleme ve şema yönetimi maliyeti getirir ve karşılığında hiçbir şey kazandırmaz. Bu ölçekte RabbitMQ ya da daha hafifi olan NATS çok daha az bakım ister. Kafka'nın kazandırmaya başladığı yer, aynı olay akışını birden fazla tüketicinin bağımsız okuduğu ve geçmişe dönük yeniden işleme ihtiyacının bulunduğu sistemlerdir.
İkinci klasik hata anahtarsız üretim yapıp sonra sıralamadan şikâyet etmektir. Anahtar vermezsen mesajların hangi partition'a düşeceği garanti değildir; aynı varlığa ait olaylar farklı partition'lara dağılır ve tüketiciler bunları farklı sırayla görür. Üçüncüsü, tüketicide hatayı yutup offset'i ilerletmektir: mesaj işlenemediği hâlde offset ilerlerse veri sessizce kaybolur. Doğru davranış, işlenemeyen mesajı ayrı bir "hatalı" topic'ine yazıp ilerlemek ya da yeniden denemektir.
Dördüncüsü, tek broker'ı üretimde kullanmaktır. Tek düğümlü Kafka geliştirme için mükemmeldir ama replikasyon yoktur; disk giderse veri gider. Beşincisi ise izlemeyi atlamaktır: consumer lag ve disk doluluk oranı için alarm kurmadığın bir Kafka, sorunu ancak kullanıcı şikâyetiyle haber verir. Servislerinin ayakta olup olmadığını dışarıdan izlemek istersen uptime ve SLA hesaplayıcı aracıyla hedeflerini netleştirebilirsin.
Sıkça Sorulan Sorular#
Kafka için ZooKeeper hâlâ gerekli mi#
Hayır, güncel Kafka sürümlerinde ZooKeeper'a ihtiyaç yok. Koordinasyon işini Kafka'nın kendi içindeki KRaft modu üstleniyor ve bu artık varsayılan kurulum biçimi. ZooKeeper'lı kurulumları yalnızca eski sürümlerden devraldığın sistemlerde görürsün; yeni bir kurulum yapıyorsan doğrudan KRaft ile başla, hem bir bileşen az yönetirsin hem de dokümantasyon bu yönde ilerliyor.
Kafka ücretsiz mi, lisans maliyeti var mı#
Apache Kafka, Apache 2.0 lisansıyla dağıtılan açık kaynaklı bir yazılımdır ve kendi sunucuna kurup kullanman tamamen ücretsizdir. Maliyet lisanstan değil altyapıdan gelir: diskler, bellek, üç düğümlü bir küme ve bunların bakımı. Yönetilen Kafka hizmeti sunan bulut sağlayıcıları ayrı ücretlendirir; kendi VDS'inde çalıştırırsan yalnızca sunucu bedelini ödersin.
Kafka mesajları ne kadar süre saklar#
Varsayılan saklama süresi klasik olarak bir haftadır, ancak bu tamamen topic bazında ayarlanabilir. retention.ms ile süreyi, retention.bytes ile partition başına boyut tavanını belirlersin ve hangisi önce dolarsa temizlik başlar. cleanup.policy=compact seçersen süre yerine anahtar bazlı sıkıştırma yapılır ve her anahtarın en son değeri süresiz saklanır.
Kaç partition oluşturmalıyım#
Partition sayısı, bir tüketici grubunda çalışabilecek paralel tüketici sayısının üst sınırıdır; yani hedeflediğin paralelliğin altına düşmemelisin. Küçük ve orta ölçekli sistemlerde topic başına 3 ile 6 arasında bir değer makul bir başlangıçtır. Sonradan artırabilirsin ama artırdığın anda anahtar–partition eşlemesi değişeceği için sıralama garantisi geçiş sırasında bozulur; bunu planlı bir bakım penceresinde yapmalısın.
Tüketici gecikmesini (consumer lag) nasıl kontrol ederim#
En doğrudan yol kafka-consumer-groups.sh --describe --group <grup-adi> komutudur; çıktıdaki LAG sütunu her partition için üretilen son offset ile işlenen offset arasındaki farkı gösterir. Bu değerin sabit bir bantta kalması sağlıklıdır. Sürekli artıyorsa tüketicilerin yetişemediği anlamına gelir; üretim ortamında bu metriği bir izleme sistemine aktarıp eşik alarmı kurmak neredeyse zorunludur.
Kafka yerine ne kullanmalıyım#
İhtiyacın "işi arka plana at, bir işçi yapsın" ise Kafka fazla ağırdır; RabbitMQ ya da NATS JetStream bu iş için çok daha az bakım ister. Yalnızca zamanlanmış tekrarlı görevlerin varsa mesajlaşmaya hiç girmeden cron da yeterli olabilir. Kafka'yı, aynı olay akışını birden çok tüketicinin bağımsız okuduğu, geçmişi yeniden işleme ihtiyacının bulunduğu ve hacmin gerçekten yüksek olduğu sistemlerde tercih et.
Kapanış#
Kafka'yı doğru kullanmanın sırrı, onu bir kuyruk değil bir kayıt defteri olarak düşünmektir. Aklında tutman gereken dört alışkanlık var: sıralama önemliyse mesaj anahtarını mutlaka doldur, paralellik tavanının partition sayısı olduğunu unutma, saklama politikasını kurulum günü ayarla ve consumer lag için alarm kur. Bunların dördü de bir gün geriye dönüp saatlerce hata aradığın bir olayı baştan engeller.
Kafka gibi disk ve bellek isteyen bir servisi çalıştıracaksan altyapının öngörülebilir olması işin yarısıdır. Tam root erişimiyle kendi kümeni kurmak için VDS ve bulut sunucu paketlerimize, kurulumu ve bakımı bize bırakmak isterseniz sunucu yönetimi hizmetimize göz atabilirsiniz; birden fazla düğümü aynı fiziksel altyapıda çalıştırmak isteyenler için nested sanallaştırma seçeneği de mevcut.