Database Engineering

SQL'de Olay Kaynağı (Event Sourcing) Anlık Görüntüleri ve Projeksiyonları Ustalıkla Kullanma

Olay Kaynağı (Event Sourcing), uygulama durumunu bir olay dizisi olarak ele alan güçlü bir mimari kalıptır. Bu, değişmez bir denetim izi ve güçlü yeniden oynatma yetenekleri sağlar ancak şu önemli bir performans zorluğu getirir: Bir varlığın mevcut durumunu, her tek olayı yeniden oynatarak yeniden oluşturmak işlem maliyetlidir ve G/Ç (I/O) yoğun bir süreçtir. İlişkisel veritabanları üzerinde sistemler geliştiren ara ve ileri düzey geliştiriciler için çözüm, anlık görüntüler (snapshots) ve projeksiyonlar içeren sağlam bir stratejiyle yatmaktadır.

Olay Yeniden Oynatmanın Zorluğu

Saf bir Olay Kaynağı uygulamasında, bir varlığın durumunu yüklemek için, benzersiz kimliğiyle ilişkili tüm olayları alıp bunları sırayla uygulamak gerekir. Bir varlık aylar veya yıllar boyunca binlerce olay biriktirdiyse, bu süreç bir darboğaza dönüşür. Temel olarak, her sorgu yürütüldüğünde karmaşık bir nesneyi baştan sona yeniden inşa ediyorsunuz demektir.

Bunu hafifletmek için anlık görüntüler (snapshots) devreye alırız. Bir anlık görüntü, varlığın belirli bir zamandaki durumunun kalıcı bir kaydından ibarettir. Bir varlık yüklenirken sistem en son anlık görüntüyü alır ve ardından bu anlık görüntüden sonra gerçekleşen olayları yeniden oynatır. Bu, durumu yeniden oluşturmak için gereken veritabanı okuma ve CPU döngüsü sayısını büyük ölçüde azaltır.

İlişkisel Veritabanlarında Anlık Görüntü Uygulama

NoSQL belge depoları genellikle normalizasyondan uzak (denormalize) verilerle esneklikleri nedeniyle övülse de, ilişkisel veritabanları basit bir tablo yapısı aracılığıyla anlık görüntüleri etkili bir şekilde yönetebilir. Anahtar nokta, anlık görüntüyü toplam kökün (aggregate root) kimliğiyle ilişkilendirmek ve sürümünü takip etmektir.

İşte olaylar tablosuyla birlikte çalışacak şekilde tasarlanmış bir anlık görüntü tablosunun SQL'de nasıl yapılandırılacağına dair pratik bir örnek:

CREATE TABLE AggregateSnapshots (
    AggregateId UNIQUEIDENTIFIER NOT NULL,
    EventType VARCHAR(255) NOT NULL,
    SnapshotData NVARCHAR(MAX) NOT NULL,
    Version BIGINT NOT NULL,
    CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    CONSTRAINT PK_AggregateSnapshots PRIMARY KEY (AggregateId)
);

CREATE INDEX IX_AggregateSnapshots_Version 
ON AggregateSnapshots (AggregateId, Version DESC);

Bu şemada, SnapshotData varlığın serileştirilmiş durumunu saklar (modern SQL Server veya PostgreSQL örneklerinde genellikle JSON olarak). Version alanı kritik öneme sahiptir; uygulama mantığının, anlık görüntünün mevcut olay akışından daha eski olup olmadığını ve güncellenmesi gerekip gerekmediğini belirlemesini sağlar.

Optimistik Kilitleme ve Tutarlılık

İlişkisel bir veritabanında anlık görüntüleri güncellemenin ince detaylarından biri, eşzamanlılığı (concurrency) yönetmektir. Bir anlık görüntüyü basitçe üzerine yazamazsınız; bu, eşzamanlı yazımların veri kaybına yol açtığı yarış durumlarına (race conditions) neden olur. Bunun yerine optimistik kilitleme kullanmalısınız.

Anlık görüntüyü kaydederken, uygulama kodunuz veritabanındaki mevcut sürümün, anlık görüntü kaydını tetikleyen olayın sürümüyle eşleşip eşleşmediğini kontrol etmelidir. Sürümler farklıysa, başka bir işlem durumu değiştirmiş demektir ve işlemi yeniden denemeniz veya reddetmeniz gerekir. Bu, performansı düşürebilecek ağır işlemci kilitlemeleri (transactional locks) olmadan veri bütünlüğünü sağlar.

Projeksiyonlar: Olay Kaynağının Okuma Tarafı

Anlık görüntüler yazma ve varlık durumu yeniden oluşturma tarafını optimize eder. Ancak karmaşık okuma sorguları için, örneğin "Geçen yıl 1000 doların üzerinde harcama yapan tüm müşterileri göster" gibi, yardımcı olmazlar. Bunun için Projeksiyonlar gerekir.

Projeksiyonlar, olay akışından türetilen okuma için optimize edilmiş modellerdir. Yazma modelini okuma modelinden ayırırlar. İlişkisel bağlamda bu, genellikle olaylar kaydedildikçe olay işleyicileri (veya ayrı bir projeksiyon motoru) tarafından güncellenen normalizasyondan uzak tablolar oluşturma anlamına gelir.

CREATE TABLE CustomerSpendingTotals (
    CustomerId UNIQUEIDENTIFIER PRIMARY KEY,
    TotalSpent DECIMAL(18, 2) DEFAULT 0.00,
    LastOrderDate DATETIME2,
    UpdatedVersion BIGINT
);

-- Örnek Olay İşleyici Mantığı (Yalancı Kod)
public void Handle(OrderPlacedEvent @event) {
    // 1. Burada varlık anlık görüntüsü mantığını güncelleyin...
    
    // 2. Projeksiyonu güncelleyin
    UPDATE CustomerSpendingTotals 
    SET TotalSpent = TotalSpent + @event.OrderValue,
        LastOrderDate = @event.OrderDate
    WHERE CustomerId = @event.CustomerId;

Bu projeksiyon tablolarını koruyarak, uygulamanız her istekte olay akışını yeniden oynatmak zorunda kalmadan karmaşık raporları ve panelleri doğrudan SQL'den sunabilir.

Sonuç

Olay Kaynağını ilişkisel veritabanlarıyla birleştirmek, veri depolama konusunda düşünceli bir yaklaşım gerektirir. Anlık görüntüler, varlık durumu yeniden oluşturma için gerekli performans artısını sağlarken, projeksiyonlar verimli ve karmaşık sorgulara olanak tanır. SQL Server veya PostgreSQL'in JSON yeteneklerinden yararlanarak ve optimistik eşzamanlılık denetimini uygulayarak, okuma performansından ödün vermeden Olay Kaynağının avantajlarını koruyan, ölçeklenebilir ve sürdürülebilir sistemler oluşturabilirsiniz. Uygulamanız büyüdükçe, yazma ve okuma modellerinin ayrılmasının yalnızca bir mimari tercih değil, uzun vadeli performans için bir zorunluluk olduğunu unutmayın.

Share: