Distributed sistem tasarlarken CAP teoreminin consistency tarafını tamamen kaybedersiniz. Eventual consistency kabul edersiniz ama kullanıcı deneyiminde bu gerçeği saklamak sizin işiniz.
Son 2 yılda fintech ve e-ticaret projelerinde eventual consistency ile boğuştum. Kullanıcı “az önce eklediğim ürünü göremiyorum” diye destek ticket açıyordu, biz “database sonunda senkronize oluyor” açıklamasını veremiyorduk.
Bu yazıda nasıl çözdüğümü anlatıyorum.
Eventual consistency neden kaçınılmaz
Modern sistemler replication kullanıyor: read replica, multi-region, cache layer, search index (Elasticsearch), event stream (Kafka). Her replica yazma işlemini aynı anda göremiyor. Birkaç milisaniye ile saniye arasında senkronizasyon gecikmesi var.
Bunu elinizden alamazsınız. Replication zero-latency değil.
Ama kullanıcı eventual consistency’nin detayları ile ilgilenmez. “Az önce ekledim, niye yok?” sorusuna teknik cevap kabul edilmez.
Problem: read-your-writes tutarsızlık
En sık sorun: kullanıcı bir şey yazdı, hemen sonra aynı kaynağa read yaptı, yazdığını göremiyor.
Senaryo: kullanıcı profil foto’sunu güncelledi. Write master’a gitti, 30 ms süren replication’ı beklemeden user page’i yüklendi. Page read replica’dan geldi, eski foto’yu gösterdi. Kullanıcı iki kere tıklayıp “niye değişmedi?” diye şikayet ediyor.
Çözüm 1: Read-your-writes consistency
Kullanıcının kendi yazma işlemi için replica’dan değil master’dan oku. Implementation:
- User session’da son write timestamp’ini tut
- Read request’lerinde “son write’ımdan sonra gelen replica”yı kullan (Lamport timestamp veya LSN)
- Yoksa master’a düş
PostgreSQL’de pg_last_wal_replay_lsn() replica’nın replike ettiği WAL LSN’ini veriyor. Session’da tuttuğunuz write LSN’den büyükse replica güncel, read’i oradan alabilirsiniz. Yoksa master okuyorsunuz.
Complexity var ama user experience tamam.
Çözüm 2: Session consistency
User session’ının bütününde “belirli bir master snapshot”‘a sticky. Session kurulduğu anda master’dan baseline aldı, devamı bu snapshot’tan okuyor. Session-local consistency garantisi.
Database vendor’ları (AWS RDS, Cloud Spanner, CockroachDB) session pinning feature’ı sunuyor.
Kullanışlı: e-ticaret basket gibi session-bound data için. Global state için değil.
Çözüm 3: Optimistic UI
En sık kullandığım pattern: UI kullanıcı aksiyonunu hemen lokal state’te gösteriyor, backend’in confirmation’ını beklemeden. Backend onayı geldiğinde zaten senkron.
Kullanıcı “favorilere ekle” tıkladı. UI hemen kalp icon’u dolu gösteriyor. Background’da API call gidiyor. Başarılıysa sorun yok, başarısızsa icon’u geri al + error mesajı göster.
async function toggleFavorite(itemId) {
setOptimisticState(itemId, 'favorited'); // UI instantly updates
try {
await api.favorite(itemId);
// Real state matches optimistic state
} catch (e) {
setOptimisticState(itemId, 'not_favorited'); // Revert
showError('Favorilere eklenemedi');
}
}Avantajları:
– Kullanıcı instant feedback alıyor
– Network latency / eventual consistency gizlendi
– Perceived performance çok daha iyi
Dezavantajları:
– Failure path’i düşünmek zorundasınız
– State rollback UX’i delicate
– Read-only data için uygun değil
Çözüm 4: Polling + conflict resolution
Yazma sonrası kısa süre polling yapmak. Write attı, 200ms sonra replica’dan tekrar read et, aynıysa güncel, değilse replication henüz tamamlanmamış, 500ms daha bekle.
Basit ama hacky. Optimistic UI daha iyi çoğu case’de.
Çözüm 5: CRDT (Conflict-free Replicated Data Types)
Distributed durumda iki node aynı anda yazma yapabiliyor, conflict’i otomatik çözebilecek data structure’lar.
Popüler CRDT’ler:
– G-Counter (grow-only counter)
– LWW-Register (last-write-wins)
– OR-Set (observed-removed set)
Collaborative editor (Figma, Notion), offline-first app’lerin omurgası. Amaç: iki kullanıcı aynı anda yazdığında hiçbir yazma kaybolmasın, deterministic şekilde merge edilsin.
Kurulumu karmaşık, genelde CRDT kullanan spesifik library var (Automerge, Yjs). Yeni problem domain’ine atıyorsanız kütüphane kullanın.
Kullanıcıya gerçeği söyleme disipline
Bazen gizlemek doğru değil. Bir fintech’te bakiye transferi 2-5 saniye sürüyor (bank partner integration). Optimistic göstermek kullanıcıyı yanıltmak olurdu.
Pattern: transparent pending state. “Transfer işleniyor, 2-3 saniye sürebilir” spinner + UI’da pending işareti. Tamamlandığında green checkmark.
Kullanıcı net bilgi alıyor, yanlış beklenti olmuyor.
Read replica lag’ini monitor edin
Eventual consistency kabul ettiğiniz anda replica lag’i ölçmeye başlamalısınız:
- p50 ve p95 replica lag (saniye cinsinden)
- Lag spike’ları (replication kesintisi sinyali)
- Lag threshold’u aşınca alarm
Prometheus query PostgreSQL için:
pg_replication_lag_seconds{instance="replica-1"} > 55 saniyenin üzerinde lag pek çok UX pattern’ı bozuyor. Alarm kurun, investigate edin.
Cache invalidation: gizli düşman
Cache layer (Redis, Memcached, CDN) eventual consistency’nin en görünmez kaynağı. DB güncellendi ama cache eski value veriyor, kullanıcı stale data okuyor.
Cache invalidation stratejileri:
- TTL-based: cache entry belirli süre sonra expire. Basit ama stale data kabul ediyorsunuz.
- Write-through: write sırasında cache’i güncelle. Tutarlı ama write latency artar.
- Event-driven invalidation: DB write event’i yayınlıyor, cache event’i alıp invalid ediyor. Pub/sub altyapısı gerekiyor.
- Versioning: cache key’i includes version number, write sonrası version artıyor, yeni cache key.
Cache invalidation “bilgisayar bilimi’nin 2 zor probleminden biri” deniyor. Ciddiye alın.
Son ders
Eventual consistency teknik gerçek, UX tercihi. Kullanıcı eventual consistency’yi görmüyor, o bilinç onun değil sizin.
Tek bir silver bullet yok. Read-your-writes, optimistic UI, session consistency, transparent pending state – hepsini farklı durumlarda kullanıyorum. Doğru pattern’ı seçmek context-dependent.
General tavsiye: UX’i başlangıçta düşünün, “database eventual consistent, UI’da gösterilecek” diye bırakmayın. Tasarım aşamasında hangi pattern hangi flow’da, nasıl görünecek, hangi failure mode hangi mesaj – bunları netleştirin. Sonradan retrofit etmek zor.