

Mikroservis mimarilerinde Apache Kafka bir veri taşıyıcısından (message broker) daha fazlasıdır; servisler arası iletişimde asenkron veri sözleşmelerinin (data contracts) uygulandığı omurgadır. 2021 yılında yönettiğim bir ödeme geçidi (payment gateway) projesinde, upstream ekibinin Avro şemasından tek bir default değerini kaldırması, downstream fraud-detection consumer grubunda 4.2 milyon mesajlık bir lag oluşmasına ve p99 gecikme sürelerinin 120ms’den 18.5 saniyeye fırlamasına neden oldu. Bu kesinti tam 46 dakika sürdü.
Veri sözleşmeleri, tıpkı API sözleşmeleri gibi katı olmalıdır. Confluent Schema Registry (veya AWS Glue, Apicurio) kullanmak tek başına bu sözleşmeleri korumaz. Şema evrimi (schema evolution) kurallarının ihlali, derleme zamanında (compile-time) değil, çalışma zamanında (runtime) ortaya çıkan ve geri alınması (rollback) son derece maliyetli olan consumer çöküşlerine yol açar.
Aşağıda, production ortamında bizzat karşılaştığım, veri sözleşmelerini sessizce bozan 5 temel şema evrimi anti-pattern’ini, çözüm stratejilerini ve trade-off analizlerini detaylandırıyorum.
Kafka topic’lerine yazan producer uygulamasına yeni bir iş gereksinimi gelir ve şemaya device_id adında yeni bir alan eklenir. Şema BACKWARD uyumluluk modunda çalışmasına rağmen, bu yeni alana bir varsayılan değer (default value) atanmaz. Yeni producer versiyonu deploy edilir edilmez, eski şemayı kullanan consumer’lar bu yeni mesajları okumaya çalıştıklarında AvroTypeException fırlatarak çökerler.
Şemaya eklenen her yeni alan için mutlak surette mantıklı bir default değer sağlanmalı ve tip tanımı Union (null ve beklenen tip) olarak yapılmalıdır.
// YANLIŞ (Anti-Pattern)
{
"type": "record",
"name": "Transaction",
"fields": [
{ "name": "transaction_id", "type": "string" },
{ "name": "device_id", "type": "string" } // HATA: default yok
]
}
// DOĞRU (Best Practice)
{
"type": "record",
"name": "Transaction",
"fields": [
{ "name": "transaction_id", "type": "string" },
{
"name": "device_id",
"type": ["null", "string"],
"default": null
}
]
}
Veri modeline yeni metrikler, opsiyonel takip ID’leri veya zenginleştirme (enrichment) verileri eklendiğinde. Consumer’ların bu yeni veriyi işlemek için acil bir kod değişikliğine gitmesi gerekmediği durumlarda (BACKWARD compatibility).
Regülasyon (örn: GDPR, PCI-DSS) gereği, eklenen alanın kesinlikle işlenmesi zorunlu ise. Bu durumda uyumluluk bilerek kırılır (veya yeni bir topic/versiyon açılır), topic adı v2 olarak güncellenir ve tüm consumer’ların yeni formata geçmesi zorunlu kılınır.
Union [“null”, “type”] vs Standart Default (örn: “”): String tipler için "default": "" (boş string) kullanmak null pointer exception (NPE) riskini azaltır ancak verinin bilerek mi boş gönderildiği, yoksa eski producer tarafından hiç mi gönderilmediği (null) ayrımını yok eder. Veri mühendisliği (Data Engineering) süreçlerinde analiz tutarlılığı için null kullanmak ve Union tanımlamak her zaman daha güvenlidir.
Geliştirme ekibi, veritabanı tablolarındaki isimlendirme standartlarını Kafka şemalarına da yansıtmak ister. userId alanı customer_id olarak değiştirilir. Schema Registry bunu teknik olarak userId alanının silinmesi ve customer_id alanının eklenmesi olarak algılar. Eski consumer’lar userId arar, bulamaz ve default değere düşer (veri kaybı yaşanır). Yeni consumer’lar eski mesajları okurken customer_id alanını boş görür.
Avro’nun aliases özelliği kullanılarak, alanın eski adları şema içerisine gömülür. Schema Registry bu sayede iki alanın aynı mantıksal veriyi taşıdığını anlar.
{
"type": "record",
"name": "UserEvent",
"fields": [
{
"name": "customer_id",
"type": "string",
"aliases": ["userId"] // Çözüm noktası
}
]
}
Şirket içi isimlendirme (naming convention) revizyonlarında, legacy sistemlerin modernizasyonunda ve birleşme/satın alma (M&A) sonrası sistem entegrasyonlarında veri modelleri eşitlenirken.
Alanın sadece adı değil, iş mantığı da değiştiyse (örneğin userId bir UUID iken, customer_id bir integer değer ise). Bu durumda alias kullanmak tip uyuşmazlığına (TypeMismatchException) neden olur.
Avro Aliases vs Protobuf Field Tags: Protobuf, alanları isimlerine göre değil numerik tag’lere (string customer_id = 1;) göre eşleştirir. Eğer projenizde sık sık alan adı değişikliği yaşanıyorsa (ki bu mimari bir zafiyettir), Kafka Serialization formatı olarak Avro yerine Protobuf tercih etmek, isim değişikliği kaynaklı kırılmaları temelden çözer.
Sipariş durumunu tutan bir enum (PENDING, COMPLETED) şemada mevcuttur. Producer’a yeni bir REFUNDED durumu eklenir. Şema güncellenir. Consumer’lar henüz bu yeni durumu bilecek şekilde derlenmemiştir (eski şema versiyonunu kullanmaktadırlar). Consumer, REFUNDED değerini okuduğunda, kendi lokal enum tanımında bu değer olmadığı için doğrudan çöker. Kafka topic’inde lag birikmeye başlar.
Avro 1.9.0 ve sonrasında gelen enum default özelliğini kullanarak, bilinmeyen enum değerleri için güvenli bir fallback mekanizması oluşturulur.
{
"type": "record",
"name": "OrderState",
"fields": [
{
"name": "status",
"type": {
"type": "enum",
"name": "StatusEnum",
"symbols": ["UNKNOWN", "PENDING", "COMPLETED", "REFUNDED"],
"default": "UNKNOWN" // Bilinmeyen değerler buraya düşer
}
}
]
}
Durum makineleri (state machines), kategori belirteçleri ve zamanla genişleme ihtimali olan (%99 ihtimalle genişler) herhangi bir kapalı küme (closed-set) veri tipinde.
Enum değerinin, finansal bir hesaplama için mutlak doğruluk gerektirdiği durumlarda. (Örn: Vergi tipleri. Bilinmeyen bir vergi tipini “UNKNOWN” yapıp %0 vergi uygulamak felakete yol açar. Bu durumlarda consumer’ın çökmesi (fail-fast), yanlış hesaplama yapmasından daha iyidir).
Geliştiriciler, Schema Registry’nin katı kurallarından ve CI/CD pipeline’larındaki şema doğrulama adımlarından kaçınmak için şemayı aşağıdaki gibi tanımlar. Tüm gerçek veri, payload adındaki bir string alana JSON formatında serileştirilerek gömülür. Bu durum, veri sözleşmesini tamamen anlamsız kılar; CPU üzerinde gereksiz JSON parsing yükü yaratır (p99 süresini 15ms’den 85ms’ye çıkarttığını gözlemledim) ve downstream sistemleri her türlü şema hatasına açık hale getirir.
// KÖTÜ KULLANIM
{
"type": "record",
"name": "Event",
"fields": [
{ "name": "event_id", "type": "string" },
{ "name": "payload", "type": "string" } // Tüm veri burada saklı
]
}
Avro’nun nested records (iç içe kayıtlar) ve karmaşık tipleri (arrays, maps) kullanılarak veri yapısı Schema Registry’ye tam olarak tanıtılmalıdır.
// DOĞRU KULLANIM
{
"type": "record",
"name": "Event",
"fields": [
{ "name": "event_id", "type": "string" },
{
"name": "payload",
"type": {
"type": "record",
"name": "PayloadData",
"fields": [
{ "name": "amount", "type": "double" },
{ "name": "currency", "type": "string" }
]
}
}
]
}
Tüm standart mikroservis iletişimlerinde, veri ambarı (Data Warehouse) aktarımlarında ve stream processing (Kafka Streams / Flink) topolojilerinde.
Sistemin sadece bir “pass-through” (geçiş) katmanı olduğu, verinin içeriğiyle kesinlikle ilgilenmediği durumlarda. Örneğin, farklı takımların serbestçe veri attığı ve şemasız depolama yapılan bir S3 raw-data bucket’ına veri taşınırken.
Kafka producer konfigürasyonlarında yer alan auto.register.schemas parametresi, varsayılan olarak true değerine sahiptir. Bu, uygulamanın her ayağa kalkışında, eğer şemada bir değişiklik varsa bunu otomatik olarak Schema Registry’ye kaydetmeye çalışması anlamına gelir. Production ortamında bu durum; farklı pod’ların yarış durumuna (race condition) girmesine, registry üzerinde yüzlerce gereksiz şema versiyonu oluşmasına ve uyumsuz şemaların test süreçlerinden geçmeden canlıya alınmasına sebep olur.
Production ve Pre-Prod ortamlarında bu parametre kesinlikle false yapılmalı ve client’ın use.latest.version=true parametresini kullanması sağlanmalıdır. Şema kayıt işlemi CI/CD pipeline’ı üzerinden (Örn: schema-registry-maven-plugin veya Terraform kullanılarak) yönetilmelidir.
// Spring Boot / Kafka Client Konfigürasyonu
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka-broker1:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
props.put("schema.registry.url", "http://schema-registry:8081");
// GÜVENLİK KİLİDİ: Runtime'da şema kaydını engelle
props.put("auto.register.schemas", "false");
props.put("use.latest.version", "true");
Dev, Test veya Local (Docker-compose) ortamları haricindeki tüm üst ortamlarda (Staging, UAT, Production). Sistem kararlılığının öncelikli olduğu her yerde.
Geliştiricilerin kendi bilgisayarlarında hızlı iterasyon yaptığı (Local) geliştirme süreçlerinde. Burada manuel şema kaydı zaman kaybı olacağından true bırakılabilir.
Data contract yönetiminde sadece kodu düzeltmek yetmez, altyapıyı da gözlemlemek gerekir. Production tecrübelerimden süzülen checklist:
ErrorHandlingDeserializer kullanarak şema uyumsuzluğundan (DeserializationException) kaynaklı hataları ayrı bir topic_name_dlq kuyruğuna atın. Böylece zehirli mesaj (poison pill) tüm partition okumasını durdurmaz.schema-registry-request-latency-max ve schemas-created-per-sec metriklerini Prometheus/Grafana üzerinde izleyin. Beklenmeyen bir şema yaratım artışı, kontrolsüz bir uygulamanın (auto.register.schemas=true kalmış) sisteme dahil olduğunu gösterir.test-compatibility komutunu çalıştırın. Şema evrimi kurallarını bozan (compatibility kırılması yaratan) bir commit, daha kod review aşamasındayken pipeline’ı patlatsın.BACKWARD modunda, eski şemayı kullanan bir consumer, yeni şemayla üretilmiş mesajları okuyabilir (Yeni alanlar için default değer gerekir). FORWARD modunda ise, yeni şemayı kullanan bir consumer, eski şemayla üretilmiş mesajları okuyabilir. Genellikle data pipeline’larında BACKWARD veya FULL (ikisini de kapsar) tercih edilir.
Eğer Kafka’yı sadece stream processing (KSQL, Flink) ve veri ambarı beslemesi için kullanıyorsanız (Hadoop, Hive uyumluluğu sebebiyle) Avro 1.11.x sürümleri rakipsizdir. Ancak gRPC kullanan yoğun mikroservis mimarileriniz varsa ve alan adı değişiklikleri sıklıkla yaşanıyorsa, numerik tag tabanlı çalışan Protobuf 3.x’e yönelmek uyumluluk krizlerini minimize eder.
Hayır. Kafka Producer ve Consumer client’ları, daha önce okudukları/yazdıkları şemaları lokal memory’lerinde (veya disk cache) tutarlar. Schema Registry 7.3.0 ve sonrasında, eğer yeni bir şema kaydedilmeyecekse veya client’ın cache’inde var olan bir ID (örneğin ID: 45) kullanılacaksa, sistem registry kapalı olsa bile p99 hızlarından taviz vermeden çalışmaya devam eder.
Spring Kafka kullanıyorsanız ErrorHandlingDeserializer sınıfını key ve value deserializer’ı olarak sarmalayın (wrap). Gerçek deserializer’ı (Örn: KafkaAvroDeserializer) delege edin. Hata durumunda mesaj deserialize edilmeye çalışılmaz, raw (byte[]) hali ile DLQ topic’ine header’larına exception stacktrace’i eklenerek yönlendirilir.
Apache Kafka’da veri sözleşmeleri, yazılım mimarisinin kırılgan olduğu noktalardan biridir. Yukarıda incelediğimiz 5 anti-pattern, çoğu zaman geliştirici dikkatsizliğinden ziyade, şema evriminin getirdiği çalışma zamanı (runtime) etkilerinin öngörülememesinden kaynaklanır. Şema değişikliklerini kod değişiklikleri ile aynı ciddiyette ele almak, veri akış platformlarının sürdürülebilirliği için şarttır.
Hemen bugün, production ortamındaki Kafka client konfigürasyonlarınızı kontrol ederek auto.register.schemas değerinin false olduğundan emin olun ve CI/CD süreçlerinize şema uyumluluk testlerini (compatibility checks) ekleyin. Bu basit aksiyonlar, gecenin bir yarısı alacağınız P1 seviyesindeki bir “Consumer Lag Alert” uyarısını engelleyecektir.

Mobil uygulamalarda veri kaybını sıfıra indiren offline-first mimarisi. Hybrid Logical Clock (HLC), SQLite ve CRDT kullanarak deterministik çakışma yönetimi.

WebAssembly (Wasm) ile web uygulamalarınızda eşsiz bir performans artışı sağlayın. Bu rehberde, Rust ve Go dillerini kullanarak tarayıcıda yüksek performanslı işlemler geliştirmenin yollarını ve gerçek dünya kullanım senaryolarını keşfedin.

Üretim tesislerinden gelen yoğun zaman serisi verilerini etkin bir şekilde depolamak ve yönetmek kritik bir zorluktur. Bu yazımızda, üretim sahası verileri için büyük ölçekli zaman serisi depolama ihtiyaçlarını ve çeşitli çok modelli veritabanı entegrasyon yaklaşımlarını inceleyeceğiz.