TB
← Tüm yazılar

Okuma/yazma ayrimi

CQRS ve read replica stratejileri ile okuma ve yazma işlemlerini ayırmak, ölçeklenebilir sistemler için temel bir mimari karardır.

Okuma yoğun bir e-ticaret sitesinde saniyede on binlerce ürün listeleme sorgusu çalışırken, yazma tarafında yalnızca sipariş ve stok güncellemeleri gerçekleşir. Tek bir PostgreSQL veya SQL Server instance'ına hem okuma hem yazma yükünü bindirmek, CPU ve I/O doygunluğunda erken tıkanmaya yol açar. Okuma/yazma ayrımı, bu iki trafik profilini farklı kaynaklara yönlendirerek ölçeklenebilirliği artırır. CQRS, read replica ve connection routing bu ayrımın pratik araçlarıdır.

Okuma ve yazma trafiğinin farklı karakteristikleri

Yazma işlemleri transaction, lock ve WAL/log yazımı gerektirir; tutarlılık önceliklidir. Okuma işlemleri çoğunlukla SELECT, join ve aggregate içerir; throughput ve gecikme önceliklidir. Tipik web uygulamasında okuma/yazma oranı 80/20 veya 95/5 olabilir. Bu dengesizlik, okuma kapasitesini bağımsız artırmayı ekonomik kılar.

  • Yazma (primary): INSERT, UPDATE, DELETE, DDL, migration
  • Okuma (replica): Rapor, arama, dashboard, public API listeleme
  • Karma: Transaction içinde okuma-yazma birlikte primary'de kalmalı

Read replica mimarisi

PostgreSQL streaming replication, WAL kayıtlarını standby sunucuya aktarır. SQL Server Always On Availability Groups benzer rol dağılımı sunar. Replica, primary'den saniyeler (veya milisaniyeler) gecikmeli olabilir; bu replication lag eventual consistency'nin somut göstergesidir.

-- PostgreSQL: replica lag kontrolü
SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS lag_seconds;

Lag yüksek trafikte veya büyük batch update sırasında artar. Okuma tarafında güncel olmayan stok veya fiyat göstermek kabul edilebilir mi? Ürün kataloğu evet, ödeme onayı hayır — bu iş kararı routing kurallarını belirler.

CQRS: komut ve sorgu ayrımı

Command Query Responsibility Segregation, yazma modeli (command) ile okuma modelini (query) ayırır. Basit CQRS: aynı veritabanı, farklı repository/handler sınıfları. Gelişmiş CQRS: yazma OLTP veritabanı, okuma denormalize read store (Elasticsearch, Redis, materialized view).

Basit CQRS örneği

// Command — primary connection
public async Task Handle(CreateOrderCommand cmd)
{
    await _writeDb.Orders.AddAsync(order);
    await _writeDb.SaveChangesAsync();
}

// Query — read replica connection
public async Task Handle(GetOrdersQuery q)
{
    return await _readDb.Orders
        .AsNoTracking()
        .ProjectTo()
        .ToListAsync();
}

Mediator pattern ile command ve query handler'ları ayrı pipeline'larda validation, logging ve yetkilendirme alır. Yazma tarafında domain kuralları; okuma tarafında performans optimizasyonu öncelenir.

Connection routing

Uygulama iki connection string taşır: Primary ve ReadOnly. EF Core'da iki DbContext veya interceptor ile connection seçimi yapılır. Npgsql ve Microsoft.Data.SqlClient ApplicationIntent=ReadOnly ile SQL Server readable secondary'ye yönlendirme destekler.

services.AddDbContext(o =>
    o.UseNpgsql(config.GetConnectionString("Primary")));
services.AddDbContext(o =>
    o.UseNpgsql(config.GetConnectionString("Replica")));
    o.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);

Read-your-writes tutarlılığı

Kullanıcı profil güncelledikten hemen sonra profil sayfasını açtığında eski veriyi görmemeli. Çözümler:

  1. Güncelleme sonrası kısa süre primary'den okuma (sticky session veya user flag)
  2. Cache invalidation ile replica yerine primary cache'ten servis
  3. Version/token ile client'a "henüz replica'da yok" sinyali

Session bazlı routing, son yazma işleminden sonra N saniye primary okuma yapar. Bu süre ortalama replication lag'in üzerinde seçilmelidir.

Materialized view ve read model

Karmaşık join'li rapor sorguları replica'da bile pahalı olabilir. Materialized view denormalize okuma modeli sunar:

CREATE MATERIALIZED VIEW mv_daily_sales AS
SELECT DATE(created_at) AS day, SUM(total) AS revenue
FROM orders WHERE is_deleted = false
GROUP BY DATE(created_at);

REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_sales;

Refresh periyodu iş gereksinimine göre ayarlanır: saatlik, dakikalık veya event tetiklemeli. Event-driven read model güncellemesi (Kafka + consumer) daha düşük gecikme sağlar ancak operasyonel karmaşıklık artar.

Ne zaman ayırmamalı?

Her sistem read replica gerektirmez. Düşük trafikli internal uygulamalarda tek instance yeterlidir. Ayrım maliyeti:

  • Replica sunucu maliyeti ve yönetimi
  • Replication lag kaynaklı tutarsızlık yönetimi
  • İki connection pool, iki migration stratejisi karmaşıklığı
  • Dağıtık transaction ihtiyacı (mümkünse kaçınılmalı)

Önce vertical scale ve indeks optimizasyonu denenmeli; metrikler primary CPU ve I/O'yu %70 üzerinde sürekli gösteriyorsa replica değerlendirilmelidir.

Monitoring ve alerting

Replica lag, replication slot disk kullanımı ve primary-replica bağlantı kopması kritik metriklerdir. Lag eşiği aşıldığında okuma trafiğini geçici olarak primary'ye yönlendiren otomatik failover mantığı veya alarm tanımlanmalıdır. Prometheus postgres_exporter veya cloud provider metrikleri bu izlemeyi sağlar.

Transaction sınırları

Cross-database transaction (primary yaz, replica oku, aynı transaction) mümkün değildir. Saga veya outbox pattern ile eventual consistency kabul edilmelidir. Sipariş oluşturma primary'de tamamlanır; arama indeksi asenkron güncellenir. Kullanıcıya "siparişiniz alındı" mesajı transaction commit sonrası döner.

Özet

Okuma/yazma ayrımı, yüksek okuma oranlı sistemlerde maliyet-etkin ölçekleme sağlar. Read replica, CQRS ve connection routing birlikte değerlendirilmeli; replication lag iş kurallarıyla uyumlu olmalıdır. Read-your-writes senaryoları primary fallback ile çözülür. Erken ayrım yerine metrik odaklı karar, gereksiz operasyonel yükten kaçınmayı sağlar.

Operasyonel perspektif

Okuma/yazma ayrimi konusunda üretim ortamında karşılaşılan senaryolar, geliştirme ortamından farklıdır. Trafik hacmi, eşzamanlı bağlantı sayısı, disk I/O ve replikasyon gecikmesi gibi faktörler tasarım kararlarını doğrudan etkiler. Metrik toplama ve düzenli kapasite gözden geçirmesi, sorunları kullanıcı şikâyetine dönüşmeden yakalamayı sağlar.

Kapasite planlama

Veritabanı katmanında CPU, bellek, disk throughput ve connection pool kullanımı birlikte izlenmelidir. Ani trafik artışlarında autoscaling uygulama katmanında mümkün olsa da veritabanı ölçeklendirmesi genellikle planlı yapılır. Read replica eklemek, connection pool boyutunu artırmak veya sorgu optimizasyonu yapmak gibi seçenekler yük testi sonuçlarına göre sıralanmalıdır.

Güvenlik ve erişim kontrolü

Veritabanı kullanıcıları en az ayrıcalık ilkesine göre tanımlanmalıdır. Uygulama hesabı yalnızca gerekli DML yetkilerine sahip olmalı; DDL ve yönetim işlemleri ayrı role ayrılmalıdır. Bağlantı string'leri secret manager'da tutulmalı, rotation politikası uygulanmalıdır. Audit log erişimi yalnızca denetim ve operasyon rollerine açık olmalıdır.

Ekip disiplini ve dokümantasyon

Mimari kararlar Architecture Decision Record (ADR) ile belgelenmelidir. Onboarding dokümanında bu konuya özel bölüm yer almalı; yeni geliştiriciler global filter, routing kuralı veya yedekleme penceresi gibi kritik detayları atlamamalıdır. Code review checklist'ine ilgili maddeler eklenmesi regresyon riskini azaltır.

Kalite kapıları

  1. Pull request'te ilgili entegrasyon testlerinin çalışması
  2. Staging ortamında gerçekçi veri hacmi ile smoke test
  3. Performans regression eşiği (p95 latency, sorgu sayısı)
  4. Migration rollback planının PR açıklamasında belirtilmesi

Bu disiplinler tek başına mucize yaratmaz; ancak veritabanı katmanındaki hataların maliyeti yüksek olduğundan, erken yakalama yatırımı uzun vadede ödenir. Post-incident review'larda kök neden analizi veritabanı tasarımına geri beslenmelidir.

Operasyonel perspektif

Okuma/yazma ayrimi konusunda üretim ortamında karşılaşılan senaryolar, geliştirme ortamından farklıdır. Trafik hacmi, eşzamanlı bağlantı sayısı, disk I/O ve replikasyon gecikmesi gibi faktörler tasarım kararlarını doğrudan etkiler. Metrik toplama ve düzenli kapasite gözden geçirmesi, sorunları kullanıcı şikâyetine dönüşmeden yakalamayı sağlar.

Kapasite planlama

Veritabanı katmanında CPU, bellek, disk throughput ve connection pool kullanımı birlikte izlenmelidir. Ani trafik artışlarında autoscaling uygulama katmanında mümkün olsa da veritabanı ölçeklendirmesi genellikle planlı yapılır. Read replica eklemek, connection pool boyutunu artırmak veya sorgu optimizasyonu yapmak gibi seçenekler yük testi sonuçlarına göre sıralanmalıdır.

Güvenlik ve erişim kontrolü

Veritabanı kullanıcıları en az ayrıcalık ilkesine göre tanımlanmalıdır. Uygulama hesabı yalnızca gerekli DML yetkilerine sahip olmalı; DDL ve yönetim işlemleri ayrı role ayrılmalıdır. Bağlantı string'leri secret manager'da tutulmalı, rotation politikası uygulanmalıdır. Audit log erişimi yalnızca denetim ve operasyon rollerine açık olmalıdır.

Ekip disiplini ve dokümantasyon

Mimari kararlar Architecture Decision Record (ADR) ile belgelenmelidir. Onboarding dokümanında bu konuya özel bölüm yer almalı; yeni geliştiriciler global filter, routing kuralı veya yedekleme penceresi gibi kritik detayları atlamamalıdır. Code review checklist'ine ilgili maddeler eklenmesi regresyon riskini azaltır.

Kalite kapıları

  1. Pull request'te ilgili entegrasyon testlerinin çalışması
  2. Staging ortamında gerçekçi veri hacmi ile smoke test
  3. Performans regression eşiği (p95 latency, sorgu sayısı)
  4. Migration rollback planının PR açıklamasında belirtilmesi

Bu disiplinler tek başına mucize yaratmaz; ancak veritabanı katmanındaki hataların maliyeti yüksek olduğundan, erken yakalama yatırımı uzun vadede ödenir. Post-incident review'larda kök neden analizi veritabanı tasarımına geri beslenmelidir.