Kafka mı, Event Bus mı? SNS/SQS/EventBridge'i Aşmanız Gerektiğini Söyleyen Sinyaller
Yönetilen bir event bus'tan Kafka'ya geçişi hak eden sinyaller ve rip-and-replace yapmadan taşımak için outbox tabanlı dört aşamalı geçiş planı.
SNS+SQS, EventBridge, Pub/Sub veya Service Bus üzerinde çalışan ekiplerin çoğu eninde sonunda bir duvara çarpar ve "Kafka'ya geçmeli miyiz?" sorusunu sorar. Sorun şu: "duruma bağlı" cevapları hangi koşulların gerçekten önemli olduğunu sıralamadığı için karar, en son okunan dokümana doğru kayar. Kafka; dayanıklılık, replay, sıralama ve sürdürülebilir throughput sorunlarının cevabıdır; yönetilen event bus'ta iki veya daha fazla somut sinyal ateşlenene kadar kalın, sonra rip-and-replace yerine transactional outbox ile aşamalı geçiş yapın.
Yazı, bugün üretimde bir event bus çalıştıran backend geliştiricileri ve cloud mimarları için. Yedi sinyali, sizi bus'ta tutması gereken karşı sinyalleri ve outbox desenine dayanan dört aşamalı geçiş planını sıralıyor.
Kafka'ya İten Sinyaller
Her sinyal aynı kalıbı izler: ne gözlemlersiniz, yönetilen bus bunu neden çözemez, Kafka bunun yerine ne sunar. Tek sinyal genellikle bir yamadır. İki veya daha fazlası geçiştir.
Sinyal 1: Replay veya zaman yolculuğuna ihtiyaç duyuyorsunuz
Tetikleyici şunlardan biri gibi görünür: event'leri kaydetmeden tüketen bir bug'ı ayıklamak, akış ortasında yeni bir tüketici eklemek ve son yedi günün geçmişine ihtiyaç duymak, ya da bir event penceresini mevcut kod yolunda yeniden işlemek için gelen düzenleyici bir talep.
SQS, ack edilmemiş mesajları maksimum ayarda 14 güne kadar tutabilir (varsayılan 4 gün); ancak bir tüketici mesajı ack edip sildiği anda mesaj gitmiş olur; geri dönüp seek edebileceğiniz replay'lenebilir, değişmez (immutable) bir log yoktur. SNS mesajları hiç saklamaz. EventBridge'de Archive and Replay vardır ve yolun bir kısmını çözer: arşivler uzun vadeli, yapılandırılabilir saklama sunar (RetentionDays=0 maksimumu seçer) ve replay'ler bunları kurallarınızdan geçirir. Tuzağı AWS belgeliyor: replay'ler çok iş parçacıklı (multi-threaded) çalışır ve event'ler arşivlendikleri sıradan farklı bir sırayla teslim edilebilir. Sıralı replay, Kafka'nın offset tabanlı seek özelliği ile aynı ürün değildir.
Kafka, event'leri topic başına append-only bir log olarak modeller ve saklama süresi ayarlanabilir. Tüketiciler offset veya timestamp ile seek eder; sıralama bir partition içinde korunur. Yeni bir tüketici için yedi günlük event'i orijinal sırasıyla yeniden işlemek gerekiyorsa, bus'taki cevap "arşiv, S3 ve orkestrasyon koduyla üstüne kur" olur. Kafka'daki cevap seek(offset)'tir.
Sinyal 2: Yüksek throughput altında sıkı anahtar bazlı sıralama
Tetikleyici iki gereksinim arasındaki çekişmedir: aynı varlığın (hesap, sipariş, cihaz) event'leri kaynak sırasına göre işlenmeli ve throughput'unuz tırmanıyor.
SQS FIFO varsayılan olarak API aksiyonu başına batching olmadan saniyede 300, batching ile 3.000 işlem (TPS) sunar. High-throughput modu bu sınırı bölgesel bir tavana yükseltir: us-east-1, us-west-2 ve eu-west-1'de 70.000 TPS; us-east-2 ve eu-central-1'de 19.000; Asya Pasifik'in dört bölgesinde 9.000; Londra ve São Paulo'da 4.500; diğer tüm bölgelerde 2.400 TPS. Konsoldan açılan bu mod dedup kapsamını "Message group" yapar ve throughput sınırını "Per message group ID" olarak ayarlar. Birçok iş yükü için yeter. Diğerleri için yetmez ve bunun ötesinde bir kademe yoktur.
EventBridge daha açıktır: bus'ın kendisi mesaj sıralama garantisi vermez, event'leri hedeflere rastgele sırada iletir. EventBridge Pipes farklı bir entegrasyon primitive'idir: Kinesis veya DynamoDB Streams gibi sıralı kaynaklar için kaynak sıralamasını koruyabilir, ama bus-kural üzerinden fan-out yolu koruyamaz.
Kafka, sıralamayı partition başına korur; producer bunun için doğru yapılandırıldığı sürece: enable.idempotence=true (Kafka 3.0'dan beri varsayılan) ve max.in.flight.requests.per.connection ≤ 5 (yine varsayılan değer). Idempotence kapalıyken in-flight retry'lar bir partition içinde mesajları yeniden sıralayabilir. Partition'lar bir topic içinde yatay olarak ölçeklenir; anahtarın her zaman aynı partition'a hash'lenmesi koşuluyla anahtar bazlı sıralama tutar. Saniyede beş haneli throughput altında anahtar bazlı sıralama, tam da partitioning modelinin çözmek için tasarlandığı problemdir.
Bunun mekaniği SQS FIFO'dan farklıdır. SQS FIFO, bir MessageGroupId içinde uçuştaki (in-flight) teslimi sıralı yapar: C mesajı visibility timeout altında tutulurken, aynı gruptaki D ve E mesajları teslim edilmez. Kafka'da bu tür bir kuyruk-tarzı in-flight serializasyonu yoktur. Broker mesajları üretildikleri sırada saklar; consumer'lar her partition'ın log'unu sıralı okur ve C üzerinde yavaş kalan bir consumer yalnızca kendi offset ilerlemesini bloklar, başka consumer'ların partition görünümünü değil.
Kafka'nın gerçekten bir gap zorladığı iki dar senaryo da teslim serializasyonu değildir. Idempotent producer ile (enable.idempotence=true), broker out-of-order producer retry'ını OutOfOrderSequenceException ile reddeder. isolation.level=read_committed ile consumer'ın Last Stable Offset'i, sonradan commit edilen transaction'ların mesajlarını önceki transaction'lar commit veya abort olana kadar gizler. İkisi de producer tarafı veya transaction garantileridir, SQS FIFO'nun verdiği anahtar bazlı head-of-line blocking değil.
Sinyal 3: Farklı hızlarda okuyan birçok bağımsız tüketici
Aynı event akışı birkaç ekibi veya alt sistemi beslediğinde ve bunlardan biri yavaşsa ya da sık yeniden başlıyorsa, her tüketicinin diğerlerine geri basınç uygulamadan kendi ilerlemesini takip etmesi gerekir.
SQS deseni iş görür: SNS, birden çok SQS kuyruğuna fan-out yapar, her tüketici bir kuyruğa sahiptir ve yavaş bir tüketici sadece kendi birikimini büyütür. Maliyet şekli ölçekte hesabı değiştirir. Aynı event'in fan-out edilen tüketici sayısıyla mesaj başına ücretlendirme doğrusal olarak büyür, çünkü her fan-out bir SQS mesajına daha mal olur.
Kafka consumer group'ları aynı log'tan bağımsız okur. Dördüncü bir tüketici eklemek depolamayı çoğaltmaz; aynı partition verisi üzerine bir offset işaretçisi daha ekler. Her tüketici yine fetch, broker egress ve ağ maliyeti üretir, ama mesaj başına fiyatlandırılan bus'larda tüketici sayısıyla doğrusal büyüyen mesaj başına teslim ücretinden kaçınırsınız. Aynı yüksek hacimli akışta beş veya daha fazla bağımsız tüketici olduğunda fiyat farkı açılır.
Sinyal 4: Mesaj başına fiyatlandırmada throughput eşiğini aşmak
Mesaj başına ücretlendirilen bus'lar her put ve her teslim için ücretlendirir. Kafka; broker-saat (MSK Provisioned), partition-saat artı GB (MSK Serverless) veya eCKU artı GB (Confluent Cloud) modeliyle ücretlendirir. Belli bir sürdürülebilir hızın altında bus fiyat olarak kazanır. Üstünde Kafka kazanır.
Kesin geçiş noktası; saklama süresi, replication factor, partition sayısı, payload boyutu, tüketici sayısı ve fan-out yoğunluğuna bağlıdır. Bu kararı verirken güncel AWS ve Confluent fiyat sayfalarından yeniden hesaplayın; bir yıl önceki benchmark'lar kanıt değildir. Sürdürülebilir saniyede 1.000 mesajın belirgin altında çalışan ekipler bu eşiğe nadiren ulaşır. Sürdürülebilir on binlerle mesaj/saniye çalışan ekipler genellikle ulaşmıştır, ancak eşik yukarıdaki değişkenlere göre kayar.
Sinyal 5: Akış teslimine değil, akış üzerinde işlemeye ihtiyacınız var
Tetikleyici, pencere bazlı join'ler, sessionization veya event akışından hesaplanan materialized view gereksinimidir. Örnekler: cihaz başına beş dakikalık kayan ortalama, bir saatlik pencerede sipariş ve sevkiyat event'lerinin join'i veya sürekli güncellenen aktif kullanıcı sayısı.
Event bus tüketicileri pencere bazlı join'leri yerel olarak yapamaz. Bunu DynamoDB veya Redis'teki state store'larla üstüne kurabilirsiniz, ama o noktada bir stream processor'ı yeniden yazıyorsunuz demektir. Kafka Streams, ksqlDB ve Apache Flink (Amazon Managed Service for Apache Flink, eski adıyla Kinesis Data Analytics üzerinden) bunun için tasarlanmıştır. EventBridge Pipes temel dönüşüm ve filtrelemeyi kapsar, fakat pencere bazlı stateful operatörler sunmaz.
Kayan pencerelere benzeyen şeyleri hesaplamak için özel state machine kodu yazıyorsanız, sinyal odur. Bir uyarı: Kafka'nın exactly-once semantics garantisi sınırlı kapsamlıdır. Producer-broker yazımlarını ve topology içi stream operatörlerini kapsar, ama alt taraftaki yan etkiler (veritabanı yazımları, üçüncü taraf API çağrıları, e-posta gönderimleri) hâlâ uygulama seviyesinde idempotency gerektirir.
Sinyal 6: Registry altında zorlanan şema evrimi
Üretici-tüketici sözleşme kayması, event-driven sistemlerde en sık görülen üretim hatalarından biridir. Bir alan yeniden adlandırılır, bir enum değeri eklenir, zorunlu bir alan opsiyonel olur. Yükseltmeyen tüketiciler ince hatalarla kırılır.
Confluent Schema Registry ve AWS Glue Schema Registry, üretim anında zorlanan uyumluluk modları (BACKWARD, FORWARD, FULL) sunar. EventBridge'in kendi Schema Registry'si vardır, ama keşif ve kod-binding üretimi odaklıdır; üretim anında sözleşme zorlaması yapmaz.
Sinyal "şema istiyoruz" değildir. Sinyal "şema kayması üretimde olaylara yol açıyor ve uyumsuz değişikliklerin üretim anında reddedilmesine ihtiyacımız var" şeklindedir.
Sinyal 7: Event kaynağınız bir veritabanı
Kayıt sisteminiz ilişkisel bir veritabanı (Postgres, MySQL) veya DynamoDB ise ve veritabanı durum değişikliklerinin event olmasını istiyorsanız, change data capture standart kanonik kanaldır. Debezium, Postgres veya MySQL write-ahead log'unu okur ve satır değişikliği başına bir event'i Kafka'ya yayar. Sonuç tam, sıralı ve üretici tarafında klasik uygulama seviyesi dual-write problemini önler.
EventBridge, Pipes üzerinden DynamoDB Streams ile iyi entegre olur, ama native Postgres CDC EventBridge'e birinci sınıf bir yol değildir. Event kaynağınız ilişkisel bir veritabanı ve hacminiz önemsiz değilse, Debezium'dan Kafka'ya iyi döşenmiş yoldur.
Karşı Sinyaller: Kafka Yanlış Cevap Olduğunda
Kafka konuşulurken bile sizi yönetilen bus'ta tutması gereken beş koşul:
- Düşük hacim. Sürdürülebilir saniyede yaklaşık 1.000 mesajın altında, mesaj başına fiyatlandırma ucuzdur ve yönetilen Kafka'nın broker yükü ödenmeye değmez.
- Geçici event'ler. Replay ihtiyacı, agregasyon ve anahtar bazlı sıralama gereksinimi yoksa bus daha basit alt yapıdır.
- Stateful operatör olmayan Lambda merkezli yığın. Serverless-only çalışan ekipler, alt yapı değişiminin yan etkisi olarak Kafka operasyonel becerileri edinmek istemez. Gerekçe ezici hale gelene kadar bus'ta kalın.
- Broker konuları için SRE kapasitesi yok. Yönetilen Kafka bile partition planlama, consumer group hata ayıklama, offset yönetimi ve rebalance araştırması ekler. Ekipte bunun sahibi yoksa bus daha güvenlidir.
- Asıl gereksinim "streaming UI" ise. Bir kullanıcı arayüzüne gerçek zamanlı güncelleme, WebSocket veya Server-Sent Events problemidir. Kafka tarayıcılara teslim etmez; backend'lere teslim eder.
Rip-and-Replace Yapmadan Nasıl Geçilir
İki veya daha fazla sinyal ateşlenip karar verildiğinde, geçişin kendisi ayrı bir mühendislik projesidir. Rip-and-replace geçişleri iyi belgelenmiş şekillerde başarısız olur: iki sistem senkrondan çıkar, geri dönüş imkânsız hale gelir ve ekip geçişe olan güvenini yarı yolda kaybeder. Aşağıdaki desen, bus'ı süreç boyunca çalışır tutar.
Aşama 0: Bus ve Kafka'yı paralel çalıştırın. Herhangi bir üretici Kafka'ya yazmadan önce kümeyi, topic'leri ve schema registry'yi ayağa kaldırın. Bağlantıyı, ACL'leri ve gözlemlenebilirliği doğrulayın. Bus, doğruluk kaynağı olarak kalır.
Aşama 1: Üreticilerden outbox artı dual-write. Bu, yükü taşıyan aşamadır. Uygulama kodundan doğrudan hem bus'a hem Kafka'ya çift yazmak, kanonik dağıtık sistem anti-pattern'idir: iki yazımdan biri başarısız olduğunda sistemler ayrışır ve uzlaştırma imkânınız kalmaz. Chris Richardson'ın microservices.io'da belgelediği transactional outbox deseni bunu çözer. Uygulama, aynı veritabanı işleminde bir domain satırı ve bir outbox satırı yazar; bir relay outbox'ı okur, bus'a ve Kafka'ya yayınlar, başarısızlık durumunda yeniden dener.
Minimal bir outbox tablosu şöyledir:
Uygulama, iş yazımıyla aynı işlem içinde outbox'a insert atar. Bir relay (app seviyesinde bir poller veya Postgres WAL'ı Outbox Event Router transform üzerinden okuyan Debezium) satırları boşaltır, her iki alt yapıya yayınlar ve published_at'i damgalar. İki yazım da olur ya da hiçbiri olmaz, çünkü ikisi de aynı commit edilmiş işlemin sonucudur.
Aşama 2: Tüketicileri bir feature flag arkasında, event tipi başına tek tek taşıyın. Bir event tipi seçin, en düşük riskli olanını. Kafka'dan okuyan yeni tüketiciyi ayağa kaldırın. Mevcut SQS veya EventBridge tüketicisine karşı eşdeğerliği doğrulayın: mesaj sayıları eşleşiyor, örneklenen payload diff'leri temiz, alt taraftaki yan etkiler idempotent. Flag'i çevirin, izleyin, sonra bir sonraki event tipine geçin. İki event tipini aynı anda taşımayın.
Aşama 3: Üreticileri event tipi başına kesin. Tüketicileri tamamen taşınmış her event tipi için, onu bus'a yayınlamayı durdurun. Outbox, o tip için artık sadece Kafka'ya yazar. Taşınan tipler için bus trafiği sıfıra düşer.
Aşama 4: Devre dışı bırakın. Bus aboneliğini, bus topic veya kuralını ve son olarak taşınan event tipi için outbox satır şablonunu kaldırın. Artık o tip için geçiş tamamlanmıştır.
Yönetilen mi yoksa self-host mu sorusunda: ilk geçişte yönetilen Kafka'yı varsayılan alın. Operasyonel öğrenme eğrisi ve geçiş riski üst üste binince işi zorlaştırır. MSK Serverless broker boyutlandırmayı tamamen kaldırır; MSK Provisioned daha fazla kontrol verir; Confluent Cloud, Aiven ve Redpanda Cloud yönetim kolaylığı karşılığında lock-in yüzeyini takas eder. Strimzi üzerinden Kubernetes üstünde self-host etmek ayrı bir karardır ve sadece neyi değiştirmek istediğinizi tam olarak bildiğinizde gerekçelidir.
Kapanış
Yedi sinyalden ikisi veya daha fazlası sisteminizi tarif ediyorsa geçişi planlayın: outbox artı dual-write, tüketici başına geçiş, önce yönetilen Kafka. Tek sinyal ateşliyorsa önce bus'ı yamayın; EventBridge Archive, SQS FIFO high-throughput modu ve DynamoDB tabanlı anahtar bazlı sıralama ara katmanları bir sebepten var. Hiçbiri ateşlemiyorsa bus doğru cevaptır; yönetilen Kafka bile operasyonel yük getirir ve sinyaller olmadan bu yük kendini geri ödemez.
Herhangi bir mimari tartışmadan önceki dürüst ilk adım, sisteminizin bugün hangi sinyalleri kanıtla ateşlediğini listelemektir.
Kaynaklar
- Apache Kafka Documentation: Design - Log saklama, partitioning ve consumer group sıralama garantileri için birincil kaynak.
- Jay Kreps, The Log: What every software engineer should know - Kafka'nın neden bir kuyruk değil, log modellediğine dair kanonik makale.
- AWS: High throughput for FIFO queues in Amazon SQS - Bölgesel high-throughput FIFO tavanlarını (üç bölgede 70.000 TPS, diğerlerinde daha düşük) ve dedup kapsam anlamını belgeler.
- AWS: Amazon SQS message quotas - 300 ve 3.000 mesaj/saniye FIFO varsayılan değerlerinin kaynağı.
- AWS: Amazon EventBridge Archive and Replay - Yapılandırılabilir arşiv saklama, 90 günlük replay silme ve sırasız replay uyarısını belgeler.
- AWS: Amazon EventBridge quotas - PutEvents hızı ve hesap seviyesi limitler.
- AWS: Amazon SQS, Amazon SNS, or EventBridge? Decision Guide - Yönetilen bus sınırının AWS tarafından çerçevelenmesi.
- Chris Richardson, Pattern: Transactional outbox - Aşama 1 deseni için kanonik kaynak.
- Debezium: Outbox Event Router - Postgres veya MySQL için CDC tabanlı outbox uygulaması.
- AWS: Amazon MSK Serverless features - Yönetilen Kafka seçeneği için küme başına ve partition başına throughput rakamları.
- AWS: Amazon MSK pricing - MSK Provisioned ve Serverless fiyatlandırma modeli; karar anında geçiş eşiğini yeniden hesaplayın.
- Confluent Cloud pricing - Yönetilen Confluent seçeneği için eCKU fiyatlandırma modeli.
- AWS: AWS Glue Schema Registry - AWS-yerli yığında şema evrimi için uyumluluk modları.
- AWS Lambda: Apache Kafka event poller scaling modes - Provisioned Mode, partition başına poller limitleri ve partition sınırı altında sıralama.