Modern dağıtık sistemlerde "tek gerçeklik kaynağı" genellikle bir efsanedir. Bunun yerine, verinin evrimini yakalamak için değişmez olay akışlarına güveniriz. Olay Kaynaklı Mimarisi (Event Sourcing) popüler bir mimari desen haline gelse de, belirli bir zaman noktasındaki durumu yeniden inşa etme sorunu hala karmaşık bir sorundur. Birçok organizasyon için en kritik gereksinim, olayları sadece depolamak değil, denetim, uyumluluk veya hata ayıklama amaçları için herhangi bir tarihsel anda uygulama durumunu verimli ve doğru bir şekilde yeniden inşa etmektir. Güçlü özellik setiyle PostgreSQL, performanstan ödün vermeden bu sorunu çözmek için güçlü desenler sunar.
Bu makale, PostgreSQL ekosistemi içinde olay akışlarından tarihsel durumu yeniden inşa etmek için gelişmiş teknikleri incelemektedir. Basit loglamayı aşarak, karmaşık zaman tabanlı sorgulama ve olay tabanlı durum yeniden inşa stratejileri uygulayacağız.
Geleneksel Anlık Görüntülerin Sınırlamaları
Geleneksel olarak, geliştiriciler veritabanı durumunun düzenli aralıklarla (örneğin günlük yedekler) veya her kritik işlemden sonra bir anlık görüntüsünü depolayabilirler. Bu yaklaşım basit olsa da, kurtarma sürecinde bir boşluk yaratır. Eğer 14:05'te bir veri anomalisi oluşursa ve son anlık görüntü 12:00'de alındıysa, mantıksal durum yeniden inşası için doğrudan sorgulanması zor olan ikili günlük dosyalarına (binary logs) güvenmek zorunda kalırsınız. Ayrıca, tam anlık görüntüler depolamak büyük miktarda depolama alanı ve I/O tüketir.
Bunun yerine, olay akışını birincil varlık olarak ele almalıyız. PostgreSQL'in zaman tabanlı verileri verimli bir şekilde işleme yeteneğinden yararlanarak, durumu istek üzerine hesaplayabilir veya kademeli olarak somutlaştırabiliriz.
Desen 1: Ekleme-Sadece Olay Depolama
Temel adım, ekleme-sadece (append-only) bir olay deposu tasarlamaktır. Bu tablo, her durum değiştirici eylemi kesin bir zaman damgasıyla yakalar. Şema, agregat kimliği, olay türü, yük (genellikle JSONB) ve sıralama numarasını içermelidir.
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
aggregate_id UUID NOT NULL,
event_type VARCHAR(50) NOT NULL,
payload JSONB NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_events_aggregate_time ON events (aggregate_id, occurred_at);
Belirli bir zamandaki bir agregatın durumunu yeniden inşa etmek için, son satırı basitçe seçmek yeterli değildir. Hedef zaman damgasına kadar akışı baştan yeniden oynatmanız gerekir. Bu, ham tablo üzerinde O(N) karmaşıklığına sahip olsa da, olayların sırasını eklemek verimli aralık sorgularına olanak tanır.
Desen 2: Durum Yeniden İnşası için PostgreSQL Lateral Joins Kullanımı
Orta seviye geliştiriciler için olayları yeniden oynatmak üzere karmaşık bir özyinelemeli ortak tablo ifadesi (CTE) yazmak hataya açıktır ve genellikle yavaştır. Daha zarif bir yaklaşım, durumu tek bir sorguda yeniden inşa etmek için PostgreSQL'in LATERAL join'lerini pencere fonksiyonlarıyla birleştirmeyi kullanır. Bu, birden fazla zaman noktasındaki durumların bir "rapor kartı"nı oluşturmak için özellikle yararlıdır.
WITH RECURSIVE state_replay AS (
SELECT
e.aggregate_id,
e.occurred_at,
e.payload,
ROW_NUMBER() OVER (PARTITION BY e.aggregate_id ORDER BY e.occurred_at) as rn
FROM events e
WHERE e.aggregate_id = 'some-uuid'
)
SELECT
aggregate_id,
occurred_at,
(payload->>'status') as status,
(payload->>'amount') as amount
FROM state_replay
ORDER BY aggregate_id, occurred_at;
Bu sorgu ham olayları gösterse de, gerçek güç, bunu uygulama mantığıyla veya somutlaştırılmış bir görünümle birleştirmektedir. Gerçek zamanlı durum yeniden inşası için, JSONB yükündeki farkları (deltas) uygulayıp kümülatif durumu güncelleyen Postgres içinde özel bir fonksiyon uygulayabilirsiniz.
Desen 3: Zaman Yolculuğu Sorguları için Zaman Tablosu Tabloları
Kullanım durumunuz, tüm akışı yeniden oynatmadan sık sık tarihsel verilere erişim gerektiriyorsa, PostgreSQL 14+ yerel bir alternatif sunar: Zaman Tablosu Tabloları (Temporal Tables). Bu özellik, zaman içindeki değişiklikleri otomatik olarak izleyen tablolar tanımlamanıza olanak tanır. Katı bir Olay Kaynaklı (Event Sourcing) anlamda tam bir olay akışı olmasa da, denetim kurtarma için güçlü bir "zaman yolculuğu" yeteneği sağlar.
CREATE TABLE orders (
order_id UUID PRIMARY KEY,
status VARCHAR(20),
total_amount DECIMAL(10, 2),
period SYSTEM_TIME,
PERIOD FOR SYSTEM_TIME (valid_from, valid_to)
);
Etkinleştirildikten sonra, FOR SYSTEM_TIME AS OF ifadesini kullanarak tablonun geçmişte nasıl göründüğünü tam olarak sorgulayabilirsiniz:
SELECT * FROM orders
FOR SYSTEM_TIME AS OF TIMESTAMP '2023-10-25 14:00:00';
Bu desen, belirli bir zaman damgasında verinin nasıl göründüğünü kanıtlamanız gereken düzenleyici uyumluluk durumları için idealdir ve manuel olay yeniden oynatma mantığı olmadan durumu etkili bir şekilde yeniden inşa eder.
Desen 4: Somutlaştırılmış Görünümlerle Kademeli Durum Birleştirme
Yüksek okuma ortamlarında, her istek için akışı yeniden oynatmak kabul edilemez. Çözüm, mevcut durumu temsil eden ve kademeli olarak yenilenen bir somutlaştırılmış görünüm (materialized view) sürdürmektir. Yeni bir olay events tablosuna eklendiğinde, bir tetikleyici durum birleştirme görünümünü güncelleyebilir. Tarihsel yeniden inşayı desteklemek için bu görünümleri sürümleyebilir veya durum değişikliklerini ayrı bir geçmişi tablosunda saklayabilirsiniz.
Bu yaklaşım, hesaplama maliyetini okuma yolundan yazma yoluna kaydırır, böylece tarihsel durum yeniden inşası ağır bir yeniden oynatma işlemi yerine hızlı bir arama işlemi olur.
Sonuç
Olay akışlarından tarihsel durumu yeniden inşa etmek, güvenilir ve denetlenebilir sistemler oluşturmak için temel bir gereksinimdir. PostgreSQL, basit ekleme-sadece günlüklerden gelişmiş zaman tablosu özelliklerine kadar çeşitli bir araç seti sunar. Doğru deseni seçerek—ister istek üzerine yeniden oynatma için lateral join'ler, ister zaman yolculuğu sorguları için zaman tablosu tabloları, ister performans için kademeli birleştirme—denetim izinizin hem kapsamlı hem de erişilebilir olduğundan emin olabilirsiniz. Veri yönetişimi düzenlemeleri daha sıkı hale geldikçe, bu desenleri ustalaşmak artık bir seçenek değil; modern veritabanı mühendisi için bir zorunluluktur.