Bir teknoloji ekibi, CQRS modelindeki olayların ayrı bir Kafka kümesinde tutulması ve verilerin PostgreSQL'te saklanması nedeniyle karşılaştıkları ciddi performans sorunlarını ve bu sorunlara getirdikleri çözümü detaylı bir şekilde açıklıyor. Sistemdeki gecikmeler, tüketici gecikmeleri ve maliyet artışları nedeniyle üç gece boyunca uykusuz kalan ekip, sonunda olayları veritabanında birleştirerek devrim niteliğinde bir iyileştirme gerçekleştirdi. Bu süreçte yaşananları ve elde edilen sonuçları merak ediyor musunuz?
Olayların Ayrı Tutulmasının Getirdiği Sorunlar
Ekip, komut sorgulama sorumluluk ayrımı (CQRS) modelini uygularken olayları (events) ayrı bir Kafka kümesinde, verileri ise PostgreSQL'de saklamaya karar vermişti. Bu yaklaşımın amacı, nihai tutarlılık (eventual consistency) sağlamak ve veri kaybını önlemekti. Ancak uygulamada, olayları Kafka'ya yazmak için Debezium kullanıldığında ve okuma tarafında tüketicilerin materyalize görünümleri oluşturduğunda, sistem ciddi performans sorunlarıyla karşılaştı.
Yazma yolunun 40 milisaniye, okuma yolunun ise 200 milisaniye sürdüğü tespit edildi. Sistem 800 istek/saniye (RPS) hızına ulaştığında materyalize görünümlerin 2,3 saniye gecikmeyle güncellendiği görüldü. Bu sayı 250.000 RPS'ye çıktığında ise gecikme 4,2 milyon olayla zirve yaptı ve tüketiciler her 15 dakikada bir Zookeeper oturum zaman aşımı nedeniyle yeniden başlatılmaya başladı. Üç gece boyunca PagerDuty uyarılarıyla uyanan ekip, sistemin temel mimari hatasını fark etmeye başladı.
İlk Deneyimler ve Neden Başarısız Oldukları
Ekip, sorunu çözmek için üç farklı iyileştirme denedi ancak hiçbiri kalıcı bir çözüm getirmedi. İlk olarak, Kafka'yı 3.5 sürümüne yükselttiler ve işlemleri idempotent hale getirdiler. Bu iyileştirme gecikmeyi %12 oranında azaltsa da hala 3,7 milyon olay işlenmemiş olarak kaldı. İkinci olarak, tüketici tarafını üç farklı kullanılabilirlik bölgesinde (AZ) 12 Kubernetes pod'una taşıdılar. Bu değişiklik CPU hırsızlığının %45'e çıkmasına ve 99. yüzdelik okuma gecikmesinin 900 milisaniyeye ulaşmasına neden oldu.
Üçüncü denemede ise Debezium yerine Kafka Connect ve JDBC kaynak bağlayıcısını kullanmaya başladılar. Bu değişiklik, şema evriminin yol açtığı duraksamaları ortadan kaldırsa da gecikmeyi 5,1 milyona çıkardı. Her iyileştirme bir metriği iyileştirirken diğerini olumsuz etkiledi ve asıl sorun olan iki veritabanı ve iki ağ katmanı arasındaki gecikmeyi çözmedi.
Alınan Mimari Karar: Olayları Veritabanında Birleştirmek
Ekip sonunda, olayları ve verileri aynı veritabanında saklamaya karar verdi. PostgreSQL kümesine olayları jsonb sütunlarında saklayan ve {aggregate_id, event_sequence} üzerine gin indeks oluşturan bir yapı kurdular. Kafka yerine mantıksal replikasyon yuvalarını (logical replication slots) kullanarak verileri doğrudan PostgreSQL'den okuyan ve Golang tabanlı tek bir hizmete ileten bir sistem geliştirdiler. Bu hizmet, olayları dahili gRPC akışlarına sıkıştırılmış ikili formatta yayınladı.
Yeni sistemde yazma işlemi bir tek turdan oluşuyordu: istemci → PostgreSQL → replikasyon yuvası → gRPC. Okuma tarafında ise olaylar, aynı kümedeki yabancı tablolar üzerinden okunduğu için materyalize görünümler 2,3 saniyede değil, 50 milisaniyede yenilenmeye başladı. Bu değişiklikle iki önemli ödün verildi: Kafka'nın disk taşma avantajını kaybettiler ve PostgreSQL'i 5 TB'lık düğümlere bölmek zorunda kaldılar. Ancak elde edilen kazançlar oldukça büyüktü; p99 gecikmesi %35 düştü ve gecikme metriği tamamen ortadan kalktı. Toplam sahip olma maliyeti de %28 azaldı çünkü Kafka ve Debezium gibi iki yönetilen hizmetten kurtulmuş ve altı Prometheus exporter'ını devre dışı bırakmışlardı.
Yeniden Yapılan Testler ve Elde Edilen Sonuçlar
Değişiklik sonrası ekip, üretim trafiğine 48 saatlik bir yük testi uyguladı. p99 yazma gecikmesi 48 milisaniyeden 12 milisaniyeye, p99 okuma gecikmesi ise 900 milisaniyeden 45 milisaniyeye düştü. Replikasyon yuvası gecikmesi tüm test boyunca sıfırda kaldı. PostgreSQL düğümlerinin CPU kullanımı %35'ten %60'a yükselse de, paylaşılan arabellekler (shared buffers) RAM'in %25'ine ayarlanarak bellek baskısı dengelendi.
Ekip, altyapıyı 24 Kafka broker'ından 12 PostgreSQL düğümüne indirerek aylık yönetilen hizmet maliyetini 18.000 dolardan 13.000 dolara düşürdü. Yeni karşılaşılan tek başarısızlık modu, birincil düğümün çökmesi durumunda ortaya çıktı. Bu sorunu çözmek için bir düğümü sıcak bekleme (hot standby) olarak sabitlediler ve pg_rewind aracını kullanarak 20 milisaniyelik bir aktarım süresi eklediler. Bu değişiklikle ana düğüm değiştirme sırasında bile gecikme sıfırda kaldı.
Gelecekte Alınabilecek Dersler ve Öneriler
Ekip, gelecekte olayları ilk günden itibaren aynı veritabanında saklamayı ve mantıksal replikasyonu bir olay taşıyıcı olarak kullanmayı planlıyor. Olayların ham akışlarını nesne depolamasında tutmaya devam edecekler, ancak ikinci bir veritabanının yol açtığı ağ ve gecikme maliyetini asla ödemeyecekler.
Bu ekip, mimari kararların veri ve gerçek performans ölçümleriyle desteklenmesi gerektiğine dikkat çekiyor. Olay tabanlı sistemler geliştirirken, soyutlamaların ve modellerin gerçek dünya koşullarına uygunluğunu sürekli olarak sorgulamak kritik önem taşıyor. Gelecekteki projelerde, mimari seçimlerin nedenlerini daha erken sorgulamak için matematiksel hesaplamaları erkenden yapmaya başlayacaklarını belirtiyorlar. Unutmamak gereken en önemli ders, Kafka'nın kötü bir araç olmadığı, ancak hizmet sınırlarının gerçek verilere dayanması gerektiğidir. Matematiği ateşten sonra değil, ateşten önce yapmak gerekiyor.
Yapay zeka özeti
CQRS mimarisinde olayları ve verileri ayrı tutmak performans krizine yol açabilir. Bir ekip, olayları PostgreSQL'de saklayarak nasıl %28 maliyet düşürdü ve sistem gecikmelerini sıfırladı.