Database Engineering

PostgreSQL ile Olay Kaydı (Event Sourcing) Uygulayarak Denetim İzlenebilirliği Sağlamak

Modern veritabanı mühendisliği manzarasında, değiştirilemez veri izlerine olan talep hiç olmadığı kadar yüksek. Bu talep finansal uyumluluk, karmaşık durum geçişlerinin hata ayıklanması veya bir kesinti sonrası sistem davranışının yeniden oluşturulması için olabilir. Geleneksel "mevcut durum" (current state) anlık görüntüleri genellikle yetersiz kalır. İşte tam da bu noktada Olay Kaydı (Event Sourcing) devreye girer. Her durum değişikliğini bir olay dizisi olarak yakalayarak veritabanını basit bir depolama mekanizmasından, sistem geçmişinin kapsamlı ve kendini belgeleyen bir defterine dönüştürürüz.

Olay Kaydı genellikle Kafka gibi mesaj aracıları kullanan olay tabanlı mimarilerle ilişkilendirilse de, bu desenin temeli veritabanıdır. PostgreSQL burada sağlam ACID garantileri, güçlü JSONB desteği ve eklemeye dayalı (append-only) iş yüklerini verimli bir şekilde işleme yeteneği ile benzersiz bir avantaj sunar. Bu yazıda, mutlak denetim izi güvenilirliği sağlamak için PostgreSQL kullanarak bir Olay Kaydı katmanı nasıl mimarileştirileceğini inceleyeceğiz.

Temel Felsefe: Eklemeye Dayalı (Append-Only) Olmak Zorunludur

Olay Kaydının temel kuralı, olayların eklenmesi gerektiği, asla değiştirilmemesi veya silinmemesidir. Bir olayı güncellereniz, nedensellik zincirini bozarsınız. PostgreSQL'de bu, genellikle monoton olarak artan bir kimlik sütunu (genellikle serial veya bigserial birincil anahtar) kullanan bir şema yapısı oluşturularak sağlanır. Uygulama durumunuzdaki olayların sırası, tamamen veritabanına yazılma sıralarına bağlıdır ve bu sıralama veritabanının işlem günlükleri (WAL) tarafından garanti altına alınır.

Güvenilirliği sağlamak için, değiştirilemezliği veritabanı seviyesinde zorlamalıyız. Uygulama mantığı güncellemeleri engelleyebilir, ancak veritabanı nihai otorite olmalıdır. Bunu kısıtlamalar oluşturarak veya tetikleyiciler kullanarak başarırız; ancak erişim katmanı sıkı bir şekilde tasarlanmışsa, iyi yapılandırılmış bir şema genellikle açık kısıtlamaları gereksiz kılar.

Olay Akışları İçin Şema Tasarımı

Şema tasarımı, sorgu performansını olay kaydı doğası gereği yazma ağırlıklı iş yükü ile dengelemeyi gerektirir. Standart bir yaklaşım, olay deposu olarak hizmet veren bir tablo içerir. Önemli sütunlar arasında benzersiz bir olay kimliği, ilgili olayları gruplamak için bir akış kimliği (stream_id), olay türü, esneklik için genellikle JSONB olan yük (payload) ve umutlu kilitleme (optimistic locking) için bir sürüm numarası bulunur.


CREATE TABLE events (
    id BIGSERIAL PRIMARY KEY,
    stream_id UUID NOT NULL,
    event_type VARCHAR(255) NOT NULL,
    version INTEGER NOT NULL,
    payload JSONB NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT unique_event_version 
        UNIQUE (stream_id, version)
);

-- Akışların verimli okunması için stream_id ve version üzerinde bir indeks oluşturun
CREATE INDEX idx_events_stream_version 
ON events (stream_id, version);

-- Tüm akışlar üzerinde küresel arama için event_type üzerinde bir indeks oluşturun
CREATE INDEX idx_events_type 
ON events (event_type);

JSONB kullanan payload sütunu hayati önem taşır. Bu, her alan için katı SQL sütunları gerektirmeden heterojen olay verilerini (örneğin OrderCreated ile PaymentFailed arasında) saklamamıza olanak tanır. PostgreSQL'in JSONB türü ayrıca, gerekirse GIN indeksleri kullanarak olay yükü içindeki belirli öznitelikleri verimli bir şekilde sorgulamamıza da izin verir.

Umutlu Kilitleme ile Olay Yazma

Olay Kaydında güvenilirlik sadece veri eklemekle ilgili değildir; aynı zamanda birden fazla işlem aynı akışı güncellemeye çalıştığında tutarlılığı sağlamakla da ilgilidir. İşte burada Umutlu Eşzamanlılık Kontrolü (OCC) hayati önem kazanır. Kayıp güncellemeleri önlemek için version sütununu kullanırız.

Yeni bir olay kaydedilirken, uygulama beklenen sürümü (genellikle last_known_version + 1) hesaplar. Veritabanı işlemi, o stream_id ve version ile mevcut satır hala eşleşiyorsa sadece o zaman onaylanır. Başka bir işlem araya girerse, sürüm kontrolü başarısız olur ve işlem geri alınır.


-- Sürüm kontrolü ile bir olay kaydetmek için sahte SQL
INSERT INTO events (stream_id, event_type, version, payload, created_at)
VALUES (
    'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
    'OrderCreated',
    5, 
    '{"orderId": 123, "total": 99.99}',
    NOW()
)
ON CONFLICT (stream_id, version) 
DO NOTHING;

-- Uygulama, ekleme işleminin başarılı olup olmadığını kontrol etmelidir.
-- Çakışma nedeniyle başarısız olduysa, işlem yeniden denenmelidir.

Bu mekanizma, hiçbir iki eşzamanlı işlemin olay dizisini bozamasını sağlar. Bir çakışma oluşursa, uygulama akışın en son durumunu alabilir ve işlemi yeniden deneyebilir, böylece yarış durumlarını zarif bir şekilde yönetebilir.

Geçmişi Sorgulama ve Durumu Yeniden İnşa Etme

Bu mimarinin en güçlü yönlerinden biri, olayların başlangıcından (veya bir anlık görüntüden) yeniden oynatılarak bir varlığın mevcut durumunu yeniden oluşturabilme yeteneğidir. PostgreSQL, olayları birincil anahtara göre sıkı bir sırada sakladığı için, akış geçmişini güvenilir bir şekilde okuyabiliriz.


-- Belirli bir akış için tüm olayları kronolojik sırada getirin
SELECT payload 
FROM events 
WHERE stream_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
ORDER BY version ASC;

Yüksek hacimli sistemlerde, başlangıçtan itibaren her olayı okumak verimsizdir. Yaygın bir desen, mevcut durumun bir "anlık görüntüsünü" (snapshot) ayrı bir tabloda saklamaktır. Yeniden oynatma sırasında sistem en son anlık görüntüyü yükler ve ardından o noktadan sonra gerçekleşen olayları uygular. Bu hibrit yaklaşım, denetim izleri için olay kaydının güvenilirliği ile geleneksel durum depolamanın performansı arasında en iyi ikisini sunar.

Denetim İzi Güvenilirliğini Sağlamak

PostgreSQL'i denetim izleri için gerçekten kullanmak için veritabanını doğru şekilde yapılandırmanız gerekir. Nokta zamanlı kurtarma (point-in-time recovery) kullanıyorsanız, günlük arşivleme sorunlarını önlemek için replikasyon yuvalarının (replication slots) yapılandırıldığından emin olun. Ek olarak, PostgreSQL'in Yazma Öncesi Günlüğü (WAL), veritabanını herhangi bir zamana geri yüklemek için bir mekanizma sağlar ve bu, olay akışınız için nihai yedekleme olarak hizmet eder.

Uyumluluk için, payload sütunu içindeki hassas alanları şifrelemek için Tablo Seviyesi Şifreleme'yi etkinleştirmeyi veya pgcrypto kullanmayı düşünün; böylece denetim izi kendisi de güvenli olur.

Sonuç

PostgreSQL ile Olay Kaydı uygulamak, veritabanınızı güvenilir, değiştirilemez bir denetim izine dönüştürür. Eklemeye dayalı desenlere sıkı sıkıya bağlı kalarak, esnek yükler için PostgreSQL'in JSONB'sinden yararlanarak ve sürümlerde umutlu kilitleme uygulayarak, hem sağlam hem de şeffaf bir sistem oluşturursunuz. Bu desen, okuma ve durum yeniden inşası açısından karmaşıklık getirse de, faydaları -izlenebilirlik, kolay hata ayıklama ve uyumluluk- modern, kritik uygulamalar için paha biçilemezdir.

İleriye giderken, Olay Kaydının karmaşıklığı yönetmek için bir araç olduğunu, sihirli bir mermi (silver bullet) olmadığını unutmayın. Değişikliklerin geçmişinin, mevcut durum kadar önemli olduğu yerlerde kullanın ve PostgreSQL'in bu geçmişin asla kaybolmamasını sağlama yükünü onun üstlenmesine izin verin.

Share: