Dağıtık sistemler kurmaya başladığım ilk zamanlarda, her şeyin anında güncel olmasını beklerdim. Klasik bir SQL veritabanının sunduğu ACID garantileri kafamı rahatlatırdı. Ama işler büyüdükçe, bu garantilerin beni nasıl yavaşlattığını ve sistemin nasıl tıkanıklıklar yaşadığını bizzat deneyimledim. İşte o zaman eventual consistency ile tanıştım ve mimari kararlarımda yeni bir kapı açıldı diyebilirim.
Bu yazıda, strong consistency ve eventual consistency arasındaki temel farkları, kendi projelerimdeki gerçek senaryolar üzerinden ne zaman hangisini tercih ettiğimi ve her birinin getirdiği trade-off’ları anlatacağım. Amacım, size “doğru” cevabı vermek değil, kendi sistemleriniz için bilinçli kararlar almanızda yardımcı olacak bakış açısını sunmak.
Strong Consistency: Garanti ve Maliyeti
Strong consistency dendiğinde aklıma ilk gelen, tüm sistemdeki verinin her an, her yerden aynı olması garantisi. Klasik ilişkisel veritabanlarının ACID (Atomicity, Consistency, Isolation, Durability) özellikleri bu garantinin temelini oluşturur. Özellikle Isolation seviyesi SERIALIZABLE olarak ayarlandığında, eş zamanlı çalışan işlemlerin birbirini etkilemediği ve sanki tek tek çalışmış gibi bir sonuç ürettiği bir dünya hayal ederiz.
Ben, bir bankanın iç platformu için geliştirdiğim muhasebe modülünde veya bir üretim firmasının ERP’sindeki kritik stok takibi alanlarında strong consistency’den asla ödün vermedim. Orada yanlış bir bakiye ya da stok bilgisi, şirkete ciddi maddi zarar verebilir, hatta yasal sorunlara yol açabilirdi. Örneğin, bir kullanıcının para transferi yaparken aynı anda başka bir transfer daha yapmasını engellemek için SELECT ... FOR UPDATE ile satırları kilitlemek veya PostgreSQL’de SERIALIZABLE isolation level’ı kullanmak benim için kırmızı çizgiydi.
-- PostgreSQL'de SERIALIZABLE transaction örneği
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Hesap bakiyesini kontrol et
SELECT bakiye FROM hesaplar WHERE id = 123 FOR UPDATE;
-- Eğer bakiye yeterliyse transferi yap
UPDATE hesaplar SET bakiye = bakiye - 100 WHERE id = 123;
UPDATE hesaplar SET bakiye = bakiye + 100 WHERE id = 456;
COMMIT;
Bu garantinin bedeli ise yüksek. Strong consistency sağlamak, genellikle daha yüksek gecikme süreleri (latency), daha düşük işlem hacmi (throughput) ve dağıtık sistemlerde daha karmaşık bir mimari anlamına gelir. Örneğin, bir UPDATE işlemi, tüm replikalarda onaylanmadan dönmezse, bu, sistemin genel yanıt süresini doğrudan etkiler. Benim deneyimimde, yüksek trafikli anlarda bu tür sistemlerde işlem hacmini belli bir noktanın üzerine çıkarmak zorlayıcı olabiliyordu. Her zaman bir darboğaz oluşuyordu ve genellikle bu darboğaz, veritabanı kilitleri veya ağ gecikmeleri oluyordu.
Eventual Consistency: Esneklik ve Bedeli
Eventual consistency ise, adından da anlaşılacağı gibi, verinin sonunda tutarlı hale geleceğini garanti eder. Yani, bir veri güncellendiğinde, bu güncellemenin tüm sistemdeki kopyalarına yayılması biraz zaman alabilir. Bu süre zarfında, farklı yerlerden aynı veriye bakan kullanıcılar farklı sonuçlar görebilir. Ancak, eğer sisteme yeni bir güncelleme gelmezse, tüm kopyaların nihayetinde aynı değere ulaşacağı varsayılır.
Kendi yan ürünümün kullanıcı aktivite loglarını işlerken veya bir web sitesinin “popüler ürünler” listesini oluştururken, her logun ya da popülerlik bilgisinin anında her yerde güncel olmasına gerek yoktu. Birkaç saniye, hatta birkaç dakika gecikme sorun yaratmazdı. Örneğin, bir kullanıcının beğendiği bir ürünün sayacının anında artıp artmaması, genel kullanıcı deneyimini pek etkilemezdi. Bu tür senaryolarda Redis gibi bir cache sistemi veya Apache Kafka gibi bir mesaj kuyruğu kullanarak eventual consistency’den faydalandım.
Bu esnekliğin elbette bir bedeli var: data staleness (verinin eski olması) ve conflict resolution (çatışma çözümü) ihtiyacı. Eğer bir kullanıcı, verinin henüz güncellenmemiş bir kopyasını görürse, bu durum kafa karışıklığına veya hatalı işlemler yapmasına neden olabilir. Bu yüzden eventual consistency’yi tercih ettiğimde, bu potansiyel tutarsızlıkları nasıl yöneteceğimi ve kullanıcıya nasıl açıklayacağımı baştan düşünmek zorunda kaldım.
Gerçek Dünyada Seçimler: Benim Deneyimlerim
Mimari kararlarımda strong ve eventual consistency arasında bir denge bulmak, genellikle iş gereksinimlerine ve performans beklentilerine göre şekillendi. Bir üretim firmasının ERP’sini geliştirirken, hammadde stok takibi veya sevkiyat listesi gibi modüller için strong consistency vazgeçilmezdi. Yanlış stok bilgisi, üretim hattını durdurabilir veya yanlış sevkiyatlara yol açabilirdi. Burada yoğun bir kritik işlem trafiği vardı ve her birinin doğruluğu hayatiydi.
Ancak, aynı ERP’deki operatör ekranlarındaki üretim hızı göstergeleri veya fabrika genelindeki sensör verilerinin anlık görselleştirmesi için eventual consistency fazlasıyla yeterliydi. Zaten saniyede değişen bir metrik olduğu için, birkaç saniyelik gecikmeyle görmek kimseyi üzmezdi. Hatta bu sayede çok yüksek hacimli veri noktalarını işleyip, gerçek zamanlıya yakın dashboard’lar sunabiliyorduk. Bu, veritabanı üzerindeki yükü dramatik şekilde azaltarak sistemin genel performansını artırıyordu.
Örneğin, PostgreSQL’de strong consistency ile çalışan bir stok tablosu:
CREATE TABLE stoklar (
urun_id UUID PRIMARY KEY,
miktar INTEGER NOT NULL,
son_guncelleme TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
Ve eventual consistency ile çalışan bir üretim istatistikleri tablosu:
CREATE TABLE uretim_istatistikleri (
operator_id UUID,
vardiya_tarihi DATE,
uretim_sayisi INTEGER,
toplam_gecikme_ms INTEGER,
last_processed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
PRIMARY KEY (operator_id, vardiya_tarihi)
);
Bu iki tabloya yaklaşımım tamamen farklıydı. Stok tablosunda her işlem anında commit edilip tüm replikalara yayılmalıydı. Üretim istatistikleri ise bir worker tarafından düzenli aralıklarla toplanıp toplu olarak güncellenebilirdi. Bu yaklaşım, sistemin farklı parçalarının farklı ihtiyaçlara göre optimize edilmesini sağlıyordu.
Eventual Consistency’yi Yönetmek: Mekanizmalar ve Taktikler
Eventual consistency ile çalışmak, “veri sonunda tutarlı olacak” demek, “veriyi umursamayabiliriz” demek değildir. Aksine, tutarsızlık anlarını ve bunların nasıl giderileceğini çok daha dikkatli planlamak gerekir. Kendi projelerimde bu durumu yönetmek için birkaç temel mekanizma ve taktik kullandım:
-
Idempotency: API’larımda aynı isteğin birden fazla gelmesi durumunda sadece bir kez işlenmesini garantilemek için
idempotency keykullanmak benim vazgeçilmezim oldu. Özellikle ödeme sistemleri veya kritik operasyonlarda, ağ hataları nedeniyle aynı isteğin tekrar gönderilmesi sıkça karşılaştığım bir senaryoydu.POST /api/orders HTTP/1.1 Content-Type: application/json Idempotency-Key: e4d8c7a6-f1b2-4c3d-9e5a-7b0f1c2d3e4f { "product_id": "prod_abc", "quantity": 1 }Bu
Idempotency-Keysayesinde, sunucu aynı anahtarla gelen ikinci isteği işleme koymadan reddedebilir veya ilk işlemin sonucunu döndürebilir. -
Transaction Outbox Pattern: Veritabanına bir kayıt yazma ve bir mesaj kuyruğuna mesaj gönderme gibi iki ayrı operasyonun atomik olmasını sağlamak için
Transaction Outboxpattern’ini sıkça kullandım. Bu pattern sayesinde, veritabanı işlemi başarılı olursa, mesajın da kesinlikle gönderileceğinden emin oluyordum. Aksi takdirde, veritabanına yazılan veri ile mesaj kuyruğundaki durum arasında tutarsızlıklar oluşabilirdi. -
Conflict Resolution: Eğer iki farklı kullanıcı aynı veriyi eş zamanlı olarak güncelliyorsa ve bu güncellemeler farklı sunucularda işleniyorsa, bir çatışma oluşur. Bu durumda
last-writer-wins(son yazan kazanır),merge(birleştirme) veyacustom logic(özel mantık) gibi stratejiler belirlemek gerekir. Naif uygulanan birlast-writer-winsmantığı sessizce güncelleme kaybına yol açabilir; bu yüzden çoğu durumda versioning ve custom merge logic ile çatışmaları açıkça ele almak gerekir.
Karar Verme Kriterleri: Doğru Yaklaşımı Bulmak
Strong ve eventual consistency arasında bir seçim yapmak, “iyi” ya da “kötü” bir karar değildir; tamamen ihtiyaçlarınıza ve projenizin bağlamına bağlıdır. Ben genellikle aşağıdaki kriterleri göz önünde bulundurarak bir karar veririm:
| Kriter | Strong Consistency | Eventual Consistency |
|---|---|---|
| İş Alanı | Finans, muhasebe, stok yönetimi, sipariş işlemleri | Sosyal medya akışları, IoT verileri, analizler, loglar |
| Veri Kritikliği | Yüksek (veri kaybı veya yanlışlığı kabul edilemez) | Orta-Düşük (geçici tutarsızlık tolere edilebilir) |
| Kullanıcı Deneyimi | Anında güncel veri beklentisi | Hafif gecikme veya eski veri kabul edilebilir |
| Performans İhtiyacı | Yüksek veri doğruluğu, düşük işlem hacmi | Yüksek işlem hacmi, düşük gecikme (yazmada) |
| Ölçeklenebilirlik | Yatay ölçekleme zor, daha maliyetli | Yatay ölçekleme kolay, daha uygun maliyetli |
| Maliyet | Yüksek (altyapı, bakım) | Daha düşük |
Benim genel yaklaşımım, genellikle en basit çözümden başlayıp, ihtiyaçlar doğrultusunda kompleksliğe doğru ilerlemektir. Eğer iş gereksinimleri kesinlikle strong consistency gerektirmiyorsa, eventual consistency ile başlamak daha fazla esneklik ve ölçeklenebilirlik sağlar. Kritik yerlerde ise strong consistency garantileri eklemek veya bu kısımları izole etmek daha kolay oluyor. Örneğin, bir üretim ERP’sinde finansal işlemler için ayrı, yüksek strong consistency’ye sahip bir veritabanı kullanırken, üretim takip ekranları için ayrı, daha esnek bir eventual consistency mimarisi kurdum.
Sık Yapılan Hatalar ve Benim Çözümlerim
Bu iki consistency modelini kullanırken ben de birçok hata yaptım, bunlardan dersler çıkardım. En sık karşılaştığım hatalardan biri, “eventual” kelimesini yanlış yorumlamak ve verinin “bir şekilde” tutarlı olacağını varsaymaktı. Bu durum, veri kaybına veya uzun süreli tutarsız durumlara yol açabiliyordu.
Bir yan ürünümün senkronizasyon mekanizmasını tasarlarken, manuel bir senkronizasyon tetikleyicisi eklemeyi unuttum. Kullanıcılar, verilerinin güncellenmediğini düşündükleri zamanlarda manuel olarak senkronize edebilecekleri bir mekanizmaya ihtiyaç duyuyordu. Ben ise her şeyin arka planda otomatik olarak halledileceğini varsaydım. Sonuç olarak, bazı kullanıcılar bir süre eski verileri görmeye devam etti ve bu da can sıkıcı geri bildirimlere yol açtı. Bu durumdan sonra, kullanıcıya her zaman “şu an veriler güncel değil, istersen şimdi senkronize et” gibi şeffaf bir geri bildirim mekanizması eklemenin önemini anladım.
Bir başka hata, eventual consistency’ye sahip bir sistemde, strong consistency bekleyen bir iş akışı tasarlamaktı. Örneğin, bir siparişin oluşturulmasından hemen sonra, stoktan düşme işleminin anında gerçekleştiğini varsayarak başka bir işlemi tetiklemek gibi. Ancak stok bilgisi eventual consistency ile güncelleniyorsa, bu durum yanlış kararlara veya over-selling gibi sorunlara yol açabilirdi. Bu tür durumlarda, iş akışını yeniden tasarlayarak eventual consistency’yi hesaba katmam veya kritik adımlar arasına bir bekleme mekanizması (örneğin, bir polling mekanizması) eklemem gerekti. Bu genellikle systemd timer’ları veya bir mesaj kuyruğundaki delay queue özellikleri ile çözdüğüm bir konuydu.
# systemd timer ile bir kontrol scriptini düzenli çalıştırma örneği
# /etc/systemd/system/check-consistency.service
[Unit]
Description=Veri tutarlılık kontrolü
[Service]
Type=oneshot
ExecStart=/usr/local/bin/check_consistency_script.sh
# /etc/systemd/system/check-consistency.timer
[Unit]
Description=Veri tutarlılık kontrolünü her 5 dakikada bir çalıştır
[Timer]
OnBootSec=5min
OnUnitActiveSec=5min
[Install]
WantedBy=timers.target
Bu tür timer’lar ile arka planda tutarlılık kontrolleri yaparak, eventual consistency’nin “eventual” kısmını daha güvenilir hale getirdim.
Sonuç: Bağlama Göre Doğru Tercih
Sonuç olarak, mimari kararlarımızda “her derde deva” bir çözüm yok. Strong consistency ve eventual consistency yaklaşımlarının her ikisi de kendi avantaj ve dezavantajlarına sahip. Önemli olan, projenizin gereksinimlerini, bütçesini, ekibin yetkinliğini ve performans hedeflerini iyi anlayarak doğru dengeyi bulmaktır. Benim deneyimimde, çoğu zaman hibrit bir yaklaşım en iyi sonucu veriyor: kritik iş akışları için strong consistency’yi korurken, ölçeklenebilirlik ve performans gerektiren diğer alanlarda eventual consistency’nin esnekliğinden faydalanmak.
Unutmayın, bir sistemi tasarlarken verdiğimiz her karar, bir trade-off’u beraberinde getirir. Önemli olan, bu trade-off’ların farkında olmak ve en uygun çözümü bulmaktır. Bu konuda daha derinlemesine bilgi edinmek isteyenler için dağıtık sistemlerde hata yönetimi yazıma bakabilirler.