Kafka / Redpanda: olay günlüğü + Avro
Kafka Redpanda + Avro
Bu derste neler öğreneceksin
- Append-only log yapısını açıklar
- Producer consumer pattern uygular
- Avro binary serialization ı dener (KSQL Kafka Streams opsiyonel)
Elif’in Olay Günlüğü
Elif artık siparişleri kağıda yazmaktan bıktı. Bir sipariş geldiğinde onu bir deftere yazıyor, altına tarih ve saat ekliyor. Defterin sayfaları asla silinmez, sadece eklenir. Yanlış bir sayfa olduysa yeni sayfa açar, eskisinin üstünü çizmez.
Bu defter olay günlüğü (event log). Bilgisayarlardaki en ünlü olay günlüğü sistemi Kafka (ve onun modern kuzeni Redpanda) tarafından tutulur.
Bu derste üç şey öğreneceğiz:
- Append-only log: deftere ekleme yapısı.
- Producer / consumer: deftere yazan ve okuyan kişiler.
- Avro: defterdeki yazıyı sıkıştırılmış tutma formatı.
Append-Only Log: “Asla Silme, Sadece Ekle”
Kafka’nın kalbi topic denen bir günlüktür. Topic bir dosya gibi düşünülebilir: sona ekler, baştan okuyabilirsin, ama ortasını değiştiremezsin.
Topic: "siparisler"
┌────┬──────────────────────────┬──────────────┐
│ # │ mesaj │ zaman │
├────┼──────────────────────────┼──────────────┤
│ 0 │ sipariş: limonata │ 10:00:01.234 │
│ 1 │ sipariş: çilekli │ 10:00:03.012 │
│ 2 │ ödeme: 25 TL │ 10:00:03.456 │
│ 3 │ sipariş: nane │ 10:00:05.778 │
│ 4 │ ... │ ... │
└────┴──────────────────────────┴──────────────┘
▲ eski yeni ▲
Her mesajın bir offset numarası (0, 1, 2, …) vardır. Bu numara, mesajın günlükteki kesin adresidir. Mesajlar asla silinmez (retention süresi dolmadıkça, genelde 7 gün). Yeni mesaj her zaman sona eklenir.
Bu yapı neden güçlü? Çünkü:
- Bir hata olursa geçmişi tekrar okuyabilirsin (kayıtlar duruyor).
- Birden çok kişi aynı günlüğü okuyabilir, birbirini bozmaz.
- Yavaşlayan okuyucu hızlı yazıyı engellemez (kendi hızında ilerler).
Producer: Deftere Yazan
Producer, günlüğe mesaj ekleyen programcıktır. Elif’in telefonu yeni siparişi Kafka’ya yazar:
from kafka import KafkaProducer
import json
producer = KafkaProducer(
bootstrap_servers="localhost:9092",
value_serializer=lambda v: json.dumps(v).encode("utf-8"),
)
# Sipariş olayı gönder
siparis = {"icecek": "limonata", "fiyat": 25, "musteri": "Ayşe"}
producer.send("siparisler", value=siparis)
producer.flush()
print("sipariş günlüğe yazıldı")
Çıktı:
sipariş günlüğe yazıldı
send() mesajı gönderir ama asenkron çalışır (anında dönmez). flush() tüm bekleyen mesajların gerçekten gönderilmesini bekler. Üretimde her mesajdan sonra flush çağırmak yavaştır; genelde batching yapılır.
Consumer: Defteri Okuyan
Consumer, günlükten mesaj okuyan programcıktır. Patronun ekranı veya rapor servisi consumer olarak bağlanır:
from kafka import KafkaConsumer
import json
consumer = KafkaConsumer(
"siparisler",
bootstrap_servers="localhost:9092",
group_id="rapor-servisi",
value_deserializer=lambda b: json.loads(b.decode("utf-8")),
auto_offset_reset="earliest", # en eski mesajdan başla
)
for mesaj in consumer:
siparis = mesaj.value
print(f"#{mesaj.offset} → {siparis['icecek']} ({siparis['musteri']})")
Çıktı:
#0 → limonata (Ayşe)
#1 → çilekli (Mehmet)
#2 → nane (Zeynep)
...
Burada iki kavram önemli:
group_id: aynı gruptaki consumer’lar mesajları paylaşır (her mesaj sadece birine gider). Farklı gruptaki consumer’lar aynı mesajı görür (her biri kendi kopyasını alır). Bu, “hem rapor servisi hem bildirim servisi aynı günlüğü dinliyor” demek.offset: her mesajın adresi. Consumer kaldığı yerden devam eder (offset’i kaydeder).
Avro: Yazıyı Küçültme Sanatı
JSON güzeldir ama şişman: her mesajda {"icecek": "limonata", "fiyat": 25} yazıyorsun, sürekli aynı sütun isimleri tekrar ediyor. Saniyede 1000 mesaj gelirse bu çok yer kaplar.
Avro bu sorunu çözer: veriyi şema ile birlikte saklar, sadece değerleri binary (ikili) olarak yazar. Şema bir kez tanımlanır, mesajlar sadece rakam olur.
Örnek şema (siparis.avsc):
{
"type": "record",
"name": "Siparis",
"fields": [
{"name": "icecek", "type": "string"},
{"name": "fiyat", "type": "int"},
{"name": "musteri", "type": "string"}
]
}
Producer’da Avro kullanmak:
from fastavro import parse_schema, writer
import io
sema = parse_schema(open("siparis.avsc", "rb").read())
buf = io.BytesIO()
writer(buf, sema, [
{"icecek": "limonata", "fiyat": 25, "musteri": "Ayşe"},
])
buf.seek(0)
producer.send("siparisler", value=buf.getvalue())
JSON’a kıyasla 3-5 kat küçük, çok daha hızlı. Dezavantajı: şema değişirse eski okuyucular eski şemayla çalışmaya devam eder (Confluent Schema Registry bu geçişi yönetir; biz basit tutuyoruz).
KSQL / Kafka Streams: Opsiyonel Bonus
Kafka’nın üstünde KSQL (SQL benzeri sorgu dili) veya Kafka Streams (Java kütüphanesi) ile akan veri üzerinde anlık sorgular yazabilirsin:
-- KSQL: her 30 saniyede en çok satılan içecek
SELECT icecek, COUNT(*) AS adet
FROM siparisler
WINDOW TUMBLING (SIZE 30 SECONDS)
GROUP BY icecek
EMIT CHANGES;
Bu derste bunu kurmuyoruz ama bil: gerçek projede akan veri üzerinde anlık pencere hesapları yapmak için bu araçlar var.
Özet
Bugün öğrendik:
- Append-only log: günlüğe sadece eklenir, silinmez. Kafka’nın kalbi.
- Producer / consumer: yazan ve okuyan taraflar. Offset ile kaldıkları yeri hatırlar.
- Consumer group: aynı grup mesajları paylaşır, farklı grup her mesajı görür.
- Avro: şema + binary format, JSON’dan 3-5 kat küçük.
- KSQL / Kafka Streams: akan veri üzerinde SQL benzeri sorgu yazma imkânı (opsiyonel bonus).
Küçük Ödev
- Kafka yerine kendi olay günlüğünü bir dosyada tut:
gunluk.jsonl, her satıra bir JSON event append et. group_id="ogrenci-1"ve"ogrenci-2"olarak iki consumer çalıştır. Aynı mesajı ikisi de görüyor mu?- Bir cümle yaz: “Producer flush() neden var, send() yetmiyor mu?”
Sıradaki
Günlüğümüz hazır, akan veriyi işleyip bir yere indirelim: PyFlink + Postgres →