- Veri kurtarma ve mevzuata uyum için
archived_at sütununa dayalı soft delete sık kullanılır, ancak zamanla karmaşıklık ve verimsizlik büyür
- Bu yaklaşım sorguları, indeksleri, migration'ları ve geri yükleme mantığını karmaşıklaştırır; ayrıca arşivlenen verilerin çoğu bir daha okunmadığı için veritabanında gereksiz yük oluşturur
- Alternatif olarak uygulama olayı tabanlı arşivleme, trigger tabanlı arşivleme ve WAL (Change Data Capture) tabanlı arşivleme önerilir
- Her yaklaşım operasyonel karmaşıklık, altyapı gereksinimleri ve geri yükleme kolaylığı açısından farklılık gösterir; özellikle WAL tabanlı yöntem Kafka gibi harici sistemlerle entegrasyon gerektirir
- Yeni bir projede trigger tabanlı yaklaşım, basitlik ve bakım kolaylığı açısından en dengeli seçimdir
Soft delete'in sorunları
- Veriler genellikle
deleted boolean'ı veya archived_at timestamp sütunu kullanılarak mantıksal olarak silinir
- Müşteri veriyi yanlışlıkla sildiğinde geri getirme mümkündür
- Mevzuat ya da denetim amaçlarıyla saklama zorunluluğu da olabilir
- Ancak
archived_at sütunu, sorgular, operasyonlar ve uygulama kodu genelinde karmaşıklık yaratır
- Arşivlenen verilerin çoğu yeniden okunmaz
- API davranış sorunları veya otomasyon araçları (Terraform vb.) nedeniyle milyonlarca gereksiz satır birikebilir
- Arşiv verisini temizleme işi yapılandırılmazsa veritabanı yedekleme ve geri yükleme sırasında performans düşüşü yaşanır
- Sorgu ve indekslerde arşiv verisinin filtrelenmesi gerekir ve veri sızıntısı riski vardır
- Migration sırasında eski veriyi işlemek veya varsayılan değerleri değiştirmek zordur
- Geri yükleme mantığı karmaşıklaşır; harici sistem çağrıları gerektiğinde hata çıkabilir
- Sonuç olarak
archived_at yaklaşımı basit görünse de uzun vadede bakım maliyeti yüksektir
Uygulama seviyesi arşivleme
- Silme sırasında bir event yayınlanır ve bu event SQS'ye gönderilerek başka bir servis tarafından S3'e arşivlenir
- Avantajlar
- Ana veritabanı ve uygulama kodu sadeleşir
- Harici kaynakların temizliği asenkron olarak işlenir, böylece performans ve güvenilirlik artar
- JSON biçiminde serileştirilerek uygulama dostu bir yapıda arşivlenebilir
- Dezavantajlar
- Uygulama kodundaki hatalar nedeniyle arşiv verisi kaybolabilir
- Mesaj kuyruğu gibi bileşenlerle operasyonel altyapı karmaşıklığı artar
- S3'teki arşiv verisi için arama ve geri yükleme araçları gerekir
Trigger tabanlı arşivleme
- Silme öncesi trigger, satırı ayrı bir archive tablosuna JSON olarak kopyalar
- Örnek tablo:
archive(id, table_name, record_id, data, archived_at, caused_by_table, caused_by_id)
- Foreign key silmelerinde (cascade) silme nedenini izlemek için oturum değişkenleri (
archive.cause_table, archive.cause_id) kullanılır
- Hangi üst kaydın alt veriyi sildiği sorgulanabilir
- Avantajlar
- Canlı tablolar temiz kalır,
archived_at sütununa gerek olmaz
- Arşiv tablosunu temizlemek (
WHERE archived_at < NOW() - INTERVAL '90 days') kolaydır
- Sorgu ve indeks verimliliği korunur, migration'lar sadeleşir
- Yedek boyutu azalır
- Arşiv tablosu ayrı bir tablespace içinde ya da zaman tabanlı partitioning ile yönetilebilir
WAL (Change Data Capture) tabanlı arşivleme
- PostgreSQL'in WAL log'ları okunarak silme event'leri harici sistemlere stream edilir
- Temsili araç: Debezium (Kafka ile entegre)
- Örnek akış:
PostgreSQL → Debezium → Kafka → Consumer → Archive Storage
- Daha hafif alternatifler
- pgstream: WAL'ı doğrudan webhook'a veya mesaj kuyruğuna gönderir
- wal2json: WAL'ı JSON olarak çıktılar
- pg_recvlogical: PostgreSQL'in yerleşik logical replication aracı
- Operasyonel karmaşıklık
- Kafka tabanlı yapı izleme, arıza müdahalesi ve tuning gerektirir
- Consumer gecikirse WAL dosyaları birikir → disk alanı yetersizliği riski oluşur
- PostgreSQL 13+ içindeki
max_slot_wal_keep_size ayarıyla bu sınırlandırılabilir
- Replication slot gecikmesinin izlenmesi ve alarm üretilmesi şarttır
- Avantajlar
- Uygulama kodunu değiştirmeden tüm değişiklikler yakalanabilir
- çeşitli hedeflere (S3, data warehouse, arama indeksi) stream edilebilir
- Ana veritabanına ek yük bindirmez
- Dezavantajlar
- Operasyonel karmaşıklık ve altyapı maliyeti yüksektir
- Consumer gecikirse veri kaybı veya yeniden senkronizasyon ihtiyacı doğabilir
- Şema değişikliklerinde kaynak ile consumer arasında koordinasyon gerekir
Silmeleri işlemeyen bir replika fikri
- DELETE sorgularını yok sayan bir PostgreSQL replikası tutma fikri öneriliyor
- Silinmemiş tüm veriler kümülatif olarak saklanabilir
- Arşiv verisi doğrudan sorgulanabilir
- Olası sorunlar
- Hangi verinin silindiğini ayırt etmek mümkün olmayabilir
- Migration uygulanırken çakışma riski vardır
- Depolama alanı ve operasyon maliyeti artar
Sonuç
- Yeni projelerde trigger tabanlı arşivleme en pratik seçimdir
- Kurulumu basittir ve canlı tabloları temiz tutar
- Ek altyapı olmadan arşiv verisini sorgulamak ve yönetmek kolaydır
- Karmaşık bir altyapı zaten mevcutsa veya birden çok hedefe streaming gerekiyorsa WAL tabanlı yaklaşım uygundur
4 yorum
Tetikleyici tabanlıysa veritabanına yük bindirdiğini öğrenmiştim...? Yine de tetikleyici öneriyor yani
O kadar trigger'ın oluşturduğu yük sorun oluyorsa, trigger olmasa bile zaten durum baştan aşağı sorun doludur.
Her zaman olduğu gibi, düzenlemeler bir maliyet. Gerçi sonuçta bunun bedelini ödemek yine tüketicilere düşecek.
Hacker News görüşleri
Çalıştığım bankacılık alanında aslında soft delete’in daha avantajlı olduğunu hissettim
deleted_atsütunu olduğunda sorgu yazımı daha açık oluyor ve analiz sorguları ya da yönetici panelinde de aynı veri kümesiyle çalışılabiliyorSilme çoğu durumda nadir oluyor ve soft delete edilmiş satırların performans sorunu çıkardığına da neredeyse hiç rastlamadım
Ayrıca ilişkiler aynen korunduğu için geri alma (undo) da kolay
Hatta ben bir adım ileri gidip satırları tamamen değiştirilemez (immutable) yapmayı ve güncelleme gerektiğinde yeni satır eklemeyi tercih ediyorum
Log tutmak gerekiyorsa DB trigger’larıyla INSERT/UPDATE/DELETE sırasında kopya bir tabloya kayıt bırakmanın iyi bir yaklaşım olduğunu düşünüyorum
Gördüğüm tablolarda satırların %50~70’i soft delete edilmiş olduğunda performans düşüşü belirgindi
Sonuçta soft delete duruma göre değişir ve ön analiz gerekir
Çoğu durumda gerekmez ama RAM tasarrufu sağlayabilir
Gerçek çözüm Event Sourcing; yani tüm değişikliklerin olay olarak kaydedilmesi gerekir
Performans daha düşük olur ama snapshot ve senkronizasyon (sync) ile telafi edilebilir
Time travel özelliğiyle geçmiş durumlar eksiksiz biçimde sorgulanabilir
En güncel durum en büyük zaman damgasına sahip satırdaydı, geçmiş durumlar ise filtreyle sorgulanabiliyordu
Bu yaklaşım güçlü bir geçmiş yönetimi sağlıyordu
Soft delete’in en büyük tuzağı sorgu karmaşıklığı
Başta yalnızca
WHERE deleted_at IS NULLeklemenin yeterli olduğu düşünülüyor ama birkaç ay sonra filtre eksikliği yüzünden hayalet veriler raporlarda görünmeye başlıyorView ile çözülebilir ama sonuçta paralel erişim desenlerini korumak gerekiyor ve silinmiş verileri sorgularken soyutlamayı aşmak gerekiyor
Event sourcing daha temiz ama operasyon yükü yüksek olduğu için çoğu ekip hibrit yaklaşımı seçiyor
Sorun, birçok SWE ve BI mühendisinin SQL ve şema tasarımına yeterince hâkim olmaması
Soft delete’ten daha yaygın sorun ise Type 2 Slowly Changing Dimension işlemleri
Çoğu kişi gereksiz yere audit table oluşturup verimsiz UPDATE/INSERT işlemlerini tekrarlıyor
Aslında DB gerçekten çok güzel bir sistem ama o ölçüde hak ettiği saygıyı görmüyor olması üzücü
Soft delete’in DB yerleşik özelliği olarak sunulması iyi olurdu diye düşünüyorum
Tablo bazında etkinleştirilebilse ve silme stratejisi seçilebilse ideal olurdu
Ama birçok ekip özel gereksinimler nedeniyle sonunda SCD(Slowly Changing Dimension) yaklaşımıyla uyguluyor
Benim deneyimimde trigger tabanlı yaklaşım en istikrarlı olanıydı
Arşiv tablosu append-only tutulmalı ve geri yükleme uygulama katmanında ele alınmalı
Güncelleme soft delete gibi değerlendirilmeli ve trigger önceki durumu yakalamalı
Trigger mutlaka BEFORE aşamasında çalışmalı ve mantık basit tutulmalı
Partition’lar genelde aylık olur; yazma yükü fazlaysa günlük bölmek daha iyi olur
Ben DB’nin stateful → stateless yönünde evrilmesini istiyorum
Tüm değişikliklerin append-only olaylar olarak kaydedildiği ve gerekli verinin view ile ifade edildiği yapıyı tercih ediyorum
DB’nin materialized index’leri otomatik yönetmesi ideal olurdu
Bazı modern DB’ler bunu sunuyor ama hâlâ OLTP odaklı gelişim yetersiz
Martin Fowler’ın açıklamasına bakılabilir
Eskiden çalıştığım bir şirkette tüm sistemlerde soft delete uygulanıyordu
Hocamın da “iş dünyasında veri asla silinmez” dediğini hatırlıyorum
Depolama ucuz olduğu için veriler asla silinmemeli
Veritabanı olguların (fact) saklandığı yerdir
Bir kaydın oluşturulması yeni bir olgudur, silinmesi de başka bir olgudur
Ama satırı fiziksel olarak silersen olgu ortadan kaybolur
Çoğu durumda bu tür silme istenmez
Saklama maliyeti ve güvenlik riskleri hesaba katılmalı
Veriyi kalıcı olarak saklama kararı dikkatle verilmelidir
Bunun için verinin yaşam döngüsünü anlamak önemlidir
Firezone başlangıçta soft delete’i denetim günlüğü için kullandı ama migration sorunları yüzünden vazgeçti
Bunun yerine Postgres CDC(Change Data Capture) kullanarak olayları ayrı, yazma odaklı bir tabloya aktarma yaklaşımına geçti
Soft delete, kullanıcı kurtarma özelliği için yararlı ama denetim veya regülasyon uyumluluğu için uygun değil diye düşünüyorum
Soft delete alanı olan tabloların üstüne View oluşturup silinmiş satırları gizlemek temiz bir yaklaşım
Böylece uygulamanın silinmiş olup olmadığını düşünmesine gerek kalmaz
Uygulama yine aynı tablo üzerinde okuma/yazma/silme işlemlerini yapar
Şema kayması (schema drift) nasıl ele alınıyor diye soranlar var
Silme anındaki şemaya göre serileştirilmiş veriyi daha sonra geri yüklemek istersen, şema değişiklikleri sorun yaratıyor
Geri yükleme çoğunlukla silmeden sonraki birkaç gün içinde yapıldığı için şema değişikliklerinin etkisi sınırlı kalıyor
Eski arşivleri yeni modele migrate etmek karmaşık ve hata olasılığı yüksek bir işti
Sonuçta yaklaşım, sistemin nasıl kullanıldığına göre değişiyor