Ali Gündoğdu CTO @gensuretech | Maker | Father

Dağıtık Sistemlerde Veri Bütünlüğü Sağlamak ve Debezium Kararı

Debezium Blog Featured Image

Bu yazımda, dağıtık sistemlerde veri bütünlüğünü sağlamak için çektiğimiz çileden ve nihayetinde bulduğumuz çözümden bahsetmek istiyorum.

Yazıya devam edebilmek için önce dağıtık sistemin ne olduğuna dair bir açıklama yapmak gerekiyor gibi;

Dağıtık sistem nedir ?

Dağıtık sistem en basit haliyle, bir uygulamayı ayakta tutabilmek için kurgulanmış farklı konumlardaki organların tek bir ağdaymış gibi davranmasına verilen genel isimdir. Mesela aramalar için MongoDB ya da MeiliSearch kullandık ama verimiz PostgreSQL veritabanında duruyor bunun yanı sıra diğer tarafta kullandığınız self-hosted bir marketing uygulaması kendi MySQL sunucusundan çalışıyor Tüm bu veritabanları, mikro servisler bir arada sizin uygulamanız / siteniz için birlikte çalışıyor. İşte bu teletabiler gibi el ele tutup güneşin batışını izleyen sistemlerin bir arada çalışmasına Dağıtık Sistem diyoruz.

Bu örneği çoğaltabiliriz, uygulamanız farklı lokasyondaki insanlara ayrı sunucu ve veritabanlarından servis veriyor olabilir ama auth işlemlerinizi tek bir noktadan da yönetiyor olabilirsiniz ya da E-Ticaret siteniz var Web sitenizin veritabanı ile muhasebe yazılımınız aynı veritabanında değil.

bu tarz durumlara genel olarak Dağıtık Yapı diyoruz.

Dağıtık Sistem Nedir ?

Veri Bütünlüğü

Tamam dağıtık bir yapımız var ve bu yapıda veritabanları arası veri bütünlüğünü nasıl sağlarız kısmına geldik.

Polling / Cron Job Hamallığı

  • İki sistem arasındaki veriyi güncel tutabilmek için en yaygın akla gelen sistemdir, workerlar olsun, belli zamanlarda çalışsın ve verileri güncellesin. Tabi bu durum kulağa güzel gelse de veritabanı büyüdükçe bu sorgular “Full Table Scan” yapmaya başlar, CPU’yu kilitler. Veri güncellenene kadar yeni veriler gelir ve gereksiz sistem yükü ile karşı karşıya kalırız.

  • Asla real-time bir çözüm olarak çalışmaz ve aradaki gap git gide büyür. Asla gerçek anlamda bir real-time olmayacağını bilerek söylüyorum zaten belli aralıklar ile yavaş şekilde update eden bir sistemde güncel data tamamen hayal olarak kalabilir.

  • Silinen kayıtları yakalama zorluğu. Ara sıra gidip veri güncelleyelim dediğinizde tek taraflı bir güncellemeden bahsederiz, select sorgusu ile attığınız isteklerde silinen yani hard delete edilen verileri görmeniz mümkün değildir, bu durum size ekstra bir ikinci kontrol mekanizmaları oluşturmaya zorlar.

Spagetti Kod ve Bağımlılık (Coupling)

Veriyi senkronize etmek için uygulama kodunun içine lojik gömmek.

diyelim ki UserService adında bir servisimiz var ve burada kullanıcı profil güncellediğinde ElasticSearch’ı güncellememiz gerekiyor, bu durumda gidip UserService içerisinde ilgili güncelleme metodunun içerisine ElasticSearch güncelleyen bir kod ekledik bunun sonucunda artık UserService sadece user işlemlerini yapmayan farklı bağımlılıklar ile hareket eden servis haline gelir. ElasticSearch yavaş geri dönerse “Güncelle” butonu yavaş çalışır, ola ki apiler ile haberleşen kodlar da eklenmiş ise ve onlarda hata döndürülmüş ise, “Güncelle” butonu bozuk çalışır. Görüldüğü gibi aslında güncelle butonu görevini yerine getirdi ancak 3. parti eklentiler veya spagetti kodlar yüzünden kod büyük bir hamallık haline geldi.

Dual Write (Çift Yazma) Laneti

Literatürde ‘Dual Write’ (Çift Yazma) olarak bilinen bu sorun, dağıtık sistemlerin en sinsi düşmanıdır.

“Kullanıcıyı veritabanına kaydettikten hemen sonra RabbitMQ’ya bir event atarım ve sonrasında o event ile diğer sistemleri haberdar ederim” dediğimde tüm topu RabbitMQ’ya atmış oluyoruz, eğer Queue yapımız çökerse size lazım olan veri Veritabanında dururken Queue sisteminiz geri geldiğinde onun içerisinde olmaz bu da veri bütünlüğünü sekteye uğratıyor.

Özetle, aradaki bir sistem devreden çıkıp tekrar geri geldiğinde veriyi tutarlı biçimde içeri alması gerekiyor. Diğer türlü eksik veriler olabilir, çift veri olabilir.

Race Condition

Nadir görüldüğü sanılsa da birbiri ardına bağlı olay(event) zincirinde sıralamalarda karışıklıklar olabilir asenkron biçimde çalışan yapılarda da benzer bir durum yaşanabilir.

Özellikle ikiden daha fazla yerde güncelleme yapma ihtimali doğan durumlarda sıralama düzgün tutturulmazsa bir yerde olan veri diğer yerde açılmadan üstüne güncelleme işlemi çalıştırılıp hata alınabilir.

Çözüm: Veritabanının Nabzını Tutmak (CDC)

Yukarıda saydığım sorunların (polling, dual write, spagetti kod) temelinde aslında tek bir yanlış yaklaşım yatıyor: Veritabanına sürekli “Sende ne değişti?” diye sormak.

Oysa veritabanı zaten her nefes alışını (her işlemi) bir yere not ediyor. PostgreSQL buna WAL (Write Ahead Log) diyor, MySQL Binlog diyor. Veritabanı çökse bile bu günlükler sayesinde ayağa kalkıyor.

İşte çözüm tam olarak burada gizli: Uygulama kodunun içine, veritabanına yük bindiren sorgulara veya karmaşık kuyruk yapılarına girmeden, direkt olarak veritabanının tuttuğu bu günlükleri (Log) dinlemek.

Biz buna literatürde Change Data Capture (CDC) diyoruz.

Dual Write

Debezium

Peki bu logları kim, nasıl okuyacak? Ham log dosyalarını parse etmek (ayrıştırmak) başlı başına bir mühendislik işi. İşte burada devreye Debezium giriyor.

Debezium, en basit tabirle veritabanınızın bir “gölgesi” gibi davranır. Kendisini veritabanına bir “replika” (kopya sunucu) gibi tanıtır ve veritabanı motoru “Bak bu satır eklendi, şu satır silindi” diye loglara yazdığı an, Debezium bunu yakalar.

Yakaldığı bu ham veriyi, herkesin anlayacağı standart bir JSON formatına (veya Avro’ya) çevirip Apache Kafka’ya bırakır.

Hatta burada güzel bir deyim ile devam edeyim “Eğer bir sorunu Kafka ile çözüyorsanız tebrikler artık iki sorununuz var” :)

Peki bu mimariye geçtiğimizde yukarıdaki çilelerimize ne oluyor?

  • Dual Write Biter: Artık kodun içinde “Hem DB’ye yaz hem RabbitMQ’ya at” demiyoruz. Sadece DB’ye yazıyoruz. Debezium, DB’ye yazılanı garantili bir şekilde alıp Event’e dönüştürüyor. (Outbox Pattern’in temeli).

  • Polling Hamallığı Biter: SELECT * FROM sorguları yok. Veritabanı motoruna ek yük yok. Debezium logları okur, bu işlem veritabanı için neredeyse “bedava”dır.

  • Hard Delete Sorunu Çözülür: Veritabanından bir satırı DELETE ile sildiğinizde, log dosyasına “Bu ID silindi” bilgisi düşer. Debezium bunu da yakalar ve size “Bak bu ID silindi, sen de ElasticSearch’ten sil” diye haber verir.

  • Gerçek Zamanlıya En Yakın (Near Real-time): Cron job’ın 5 dakikalık gecikmesi yerine, milisaniyeler mertebesinde gecikme ile veriyi yakalarsınız.

Nasıl Çalışıyor?

Kafada canlanması için basit bir akış şeması düşünelim:

  • Uygulama: Sadece PostgreSQL’e INSERT INTO users… komutunu atar. İşini bitirir.

  • PostgreSQL: Veriyi diske yazar ve Transaction Log’a (WAL) işler.

  • Debezium (Kafka Connect): WAL dosyasındaki bu değişikliği anında görür.

  • Kafka: Debezium bu değişikliği db.public.users topic’ine bir mesaj olarak bırakır.

  • Tüketiciler (Consumers): ElasticSearch, Cache, Marketing Tool vb. bu topic’i dinler ve veriyi kendine alır.

Böylece “Dağıtık Yapı” dediğimiz o karmaşık orkestra, artık tek bir şeften (Veritabanı Logları) talimat alarak senkronize çalar hale gelir.

Yazarın Notu (CTO Gözüyle): Tabii ki “Bedava peynir sadece fare kapanında olur”. Debezium harika bir araç olsa da, beraberinde Kafka yönetimini, şema değişikliklerini (Schema Evolution) ve operasyonel maliyeti getirir. Ancak ölçeklenen sistemlerde veri tutarlılığı (Data Consistency) uykularınızı kaçırıyorsa, ödeyeceğiniz bu bedel, gece rahat uyumanın yanında çok küçük kalır.

Debezium

Sonuç: Huzurlu Uykular ve Mutlu Teletabiler

Toparlamak gerekirse; dağıtık sistemler kurmak, lego parçalarını birleştirmeye benzemez. Lego’da parçalar sabittir, yazılımda ise o parçalar sürekli hareket eder, ağlar kopar ve sunucular nazlanır.

Sektörde “kıdem” dediğimiz şey, aslında prod ortamında patlayan veritabanlarının başında sabahladığımız gecelerin ve çözdüğümüz krizlerin bir toplamıdır. Bu süreçte öğrendiğim en net şey şudur: “Mükemmel kod yoktur, yönetilebilir kaos vardır.

Debezium ve CDC mimarisi, işte bu kaosu yönetilebilir kılan, spagetti koda dönüşmüş senkronizasyon işlerini temizleyen ve bize “single source of truth” (tek gerçeklik kaynağı) huzurunu veren modern bir yaklaşımdır. Elbette Kafka yönetmek, Cron Job yazmaktan daha zordur; ama Ferrari’ye binmenin de bir bakım maliyeti vardır, değil mi?

Benim mimari kararlarda önceliğim her zaman sadece günü kurtarmak değil, gece rahat uyumaktır. Çünkü iyi bir mühendislik, sadece kodun çalışması değil, o kodu yaşatanların da huzurlu olmasıdır.

Girişte bahsettiğimiz o el ele tutuşup güneşin batışını izleyen Teletabiler var ya? İşte onların o tasasız mutluluğuna erişmek istiyorsanız, aralarındaki iletişimi (network) sağlam tutun ve veriyi asla şansa bırakmayın.

Sağlıklı, tutarlı ve bol “commit”li günler.

Görseller Nano Banana Pro ile üretilmiştir.

Tavsiye Linkler

You might also enjoy