Die Schwierigkeit von Soft Deletes
(atlas9.dev)- Für Datenwiederherstellung und Compliance wird häufig Soft Delete auf Basis einer
archived_at-Spalte verwendet, doch mit der Zeit nehmen Komplexität und Ineffizienz zu - Dieser Ansatz macht Abfragen, Indizes, Migrationen und Restore-Logik komplexer, und da die meisten archivierten Daten nie wieder gelesen werden, entsteht unnötige Last für die Datenbank
- Als Alternativen werden anwendungsereignisbasierte Archivierung, triggerbasierte Archivierung und WAL-(Change-Data-Capture)-basierte Archivierung vorgestellt
- Die einzelnen Ansätze unterscheiden sich bei betrieblicher Komplexität, Infrastruktur-Anforderungen und Einfachheit der Wiederherstellung; insbesondere der WAL-basierte Ansatz erfordert Integration mit externen Systemen wie Kafka
- Für neue Projekte ist der triggerbasierte Ansatz die am besten ausbalancierte Wahl in Bezug auf Einfachheit und Wartbarkeit
Probleme von Soft Deletes
- Üblicherweise werden Daten logisch gelöscht, indem eine boolesche Spalte
deletedoder eine Zeitstempelspaltearchived_atverwendet wird- Daten können wiederhergestellt werden, wenn ein Kunde sie versehentlich gelöscht hat
- In manchen Fällen ist eine Aufbewahrung aus regulatorischen oder Audit-Zwecken erforderlich
- Eine
archived_at-Spalte verursacht jedoch Komplexität in Abfragen, Betrieb und Anwendungscode- Die meisten archivierten Daten werden nie wieder gelesen
- Durch Probleme im API-Verhalten oder Automatisierungs-Tools wie Terraform können sich Millionen unnötiger Zeilen ansammeln
- Wenn keine Bereinigungsjobs für archivierte Daten eingerichtet sind, kommt es zu Leistungseinbußen bei Datenbank-Backups und -Restores
- In Abfragen und Indizes müssen archivierte Daten herausgefiltert werden, und es besteht ein Risiko von Datenlecks
- Bei Migrationen ist es schwierig, alte Daten zu verarbeiten oder Standardwerte zu ändern
- Die Restore-Logik wird komplexer, und wenn Aufrufe externer Systeme nötig sind, können Bugs entstehen
- Insgesamt wirkt der
archived_at-Ansatz zwar einfach, verursacht aber langfristig hohe Wartungskosten
Archivierung auf Anwendungsebene
- Beim Löschen wird ein Event veröffentlicht, das an SQS gesendet wird, damit ein anderer Service die Daten in S3 archiviert
- Vorteile
- Vereinfachung der primären Datenbank und des Anwendungscodes
- Bereinigung externer Ressourcen kann asynchron erfolgen, was Leistung und Stabilität verbessert
- Durch Serialisierung als JSON können Daten in einer anwendungsfreundlichen Struktur archiviert werden
- Nachteile
- Durch Bugs im Anwendungscode kann es zu Verlust archivierter Daten kommen
- Betriebliche Infrastruktur-Komplexität durch Message Queues und Ähnliches nimmt zu
- Für in S3 archivierte Daten werden Werkzeuge für Suche und Wiederherstellung benötigt
Triggerbasierte Archivierung
- Ein Trigger vor dem Löschen kopiert die Zeile als JSON in eine separate Archive-Tabelle
- Beispiel-Tabelle:
archive(id, table_name, record_id, data, archived_at, caused_by_table, caused_by_id)
- Beispiel-Tabelle:
- Bei Löschungen per Foreign-Key-Cascade werden zur Nachverfolgung der Löschursache Session-Variablen (
archive.cause_table,archive.cause_id) verwendet- So lässt sich abfragen, welcher übergeordnete Datensatz untergeordnete Daten gelöscht hat
- Vorteile
- Live-Tabellen bleiben sauber, eine
archived_at-Spalte ist nicht nötig - Die Bereinigung der Archivtabelle (
WHERE archived_at < NOW() - INTERVAL '90 days') ist einfach - Effiziente Abfragen und Indizes bleiben erhalten, Migrationen werden vereinfacht
- Geringere Backup-Größe
- Live-Tabellen bleiben sauber, eine
- Die Archivtabelle kann über einen separaten Tablespace oder zeitbasierte Partitionierung verwaltet werden
WAL-(Change-Data-Capture)-basierte Archivierung
- Die WAL-Logs von PostgreSQL werden gelesen, um Delete-Events in externe Systeme zu streamen
- Typisches Tool: Debezium (integriert mit Kafka)
- Beispielpfad:
PostgreSQL → Debezium → Kafka → Consumer → Archive Storage
- Leichtgewichtigere Alternativen
- pgstream: sendet WAL direkt an Webhooks oder Message Queues
- wal2json: gibt WAL als JSON aus
- pg_recvlogical: integriertes Tool für logische Replikation in PostgreSQL
- Betriebliche Komplexität
- Kafka-basierte Setups erfordern Monitoring, Incident-Handling und Tuning
- Wenn der Consumer verzögert ist, können sich WAL-Dateien ansammeln → Risiko von zu wenig Festplattenspeicher
- Mit der Einstellung
max_slot_wal_keep_sizein PostgreSQL 13+ lässt sich das begrenzen - Monitoring und Alerts für Replikationsslot-Lag sind essenziell
- Vorteile
- Alle Änderungen lassen sich ohne Änderungen am Anwendungscode erfassen
- Streaming zu verschiedenen Zielen (S3, Data Warehouse, Suchindex) ist möglich
- Keine zusätzliche Last auf der primären Datenbank
- Nachteile
- Hohe betriebliche Komplexität und Infrastrukturkosten
- Bei Consumer-Lag kann Datenverlust auftreten oder eine erneute Synchronisierung nötig werden
- Bei Schemaänderungen ist Abstimmung zwischen Quelle und Consumer erforderlich
Idee eines Replikats, das Löschungen nicht verarbeitet
- Es wird die Idee vorgeschlagen, ein PostgreSQL-Replikat zu betreiben, das DELETE-Abfragen ignoriert
- So könnten alle nicht gelöschten Daten kumulativ archiviert werden
- Archivierte Daten wären direkt abfragbar
- Mögliche Probleme
- Es könnte unmöglich sein, Löschinformationen eindeutig zu unterscheiden
- Bei der Anwendung von Migrationen besteht Konfliktgefahr
- Speicherbedarf und Betriebskosten steigen
Fazit
- In neuen Projekten ist die triggerbasierte Archivierung die praktikabelste Wahl
- Sie ist einfach einzurichten und hält Live-Tabellen sauber
- Auch ohne separate Infrastruktur lassen sich Archivdaten leicht einsehen und verwalten
- Wenn bereits komplexe Infrastruktur vorhanden ist oder Streaming zu mehreren Zielen benötigt wird, ist ein WAL-basierter Ansatz geeignet
4 Kommentare
Soweit ich gelernt habe, führt ein triggerbasierter Ansatz zu zusätzlicher Last auf der DB ...? Dass man Trigger empfiehlt, überrascht mich daher.
Wenn die Last, die durch Trigger in diesem Ausmaß entsteht, zum Problem wird, ist die Situation auch ohne Trigger bereits voller Probleme.
Regulierung ist eben immer mit Kosten verbunden. Aber letztlich müssen die Verbraucher sie ohnehin tragen.
Hacker-News-Kommentare
In der Bankendomäne, in der ich arbeite, hatte ich eher das Gefühl, dass Soft Delete vorteilhaft ist
Wenn es eine Spalte
deleted_atgibt, sind Abfragen klar formuliert, und auch Analyse-Queries oder Admin-Seiten können mit demselben Datensatz arbeitenLöschungen sind in den meisten Fällen selten, und es kam fast nie vor, dass per Soft Delete gelöschte Zeilen Performance-Probleme verursacht haben
Außerdem bleiben Beziehungen unverändert erhalten, wodurch auch die Wiederherstellung (Undo) einfach ist
Ich gehe sogar noch weiter und bevorzuge es, Zeilen vollständig unveränderlich (immutable) zu machen und bei Updates neue Zeilen hinzuzufügen
Wenn man Logs hinterlassen will, halte ich einen Ansatz für gut, bei dem man per DB-Trigger bei INSERT/UPDATE/DELETE Einträge in eine Replikat-Tabelle schreibt
In Tabellen, die ich gesehen habe und bei denen 50–70 % per Soft Delete gelöscht waren, gab es eindeutig Performance-Einbußen
Letztlich hängt Soft Delete vom Kontext ab, und eine vorherige Analyse ist nötig
In den meisten Fällen ist das nicht nötig, kann aber beim RAM-Sparen helfen
Die eigentliche Lösung ist Event Sourcing, bei dem alle Änderungen als Events aufgezeichnet werden müssen
Die Performance ist schlechter, lässt sich aber mit Snapshots und Synchronisation (Sync) kompensieren
Mit der Time-Travel-Funktion lässt sich der frühere Zustand vollständig abfragen
Der aktuelle Zustand lag in der Zeile mit dem größten Zeitstempel, und vergangene Zustände konnten per Filter abgefragt werden
Dieser Ansatz ermöglicht eine starke Verlaufskontrolle
Die größte Falle bei Soft Delete ist die Komplexität der Abfragen
Anfangs denkt man, man müsse nur
WHERE deleted_at IS NULLhinzufügen, aber nach ein paar Monaten tauchen wegen vergessener Filter Geisterdaten in Reports aufMan kann das mit Views lösen, aber am Ende muss man trotzdem parallele Zugriffsmuster beibehalten, und wenn gelöschte Daten abgefragt werden sollen, muss die Abstraktion umgangen werden
Event Sourcing ist sauberer, bringt aber eine hohe Betriebslast mit sich, weshalb die meisten einen hybriden Ansatz wählen
Das Problem ist, dass viele SWE- und BI-Ingenieure mit SQL und Schemadesign nicht vertraut sind
Ein noch häufigeres Problem als Soft Delete ist der Umgang mit Type 2 Slowly Changing Dimension
Die meisten erstellen unnötigerweise Audit-Tabellen und wiederholen ineffiziente UPDATEs/INSERTs
Datenbanken sind eigentlich wunderschöne Systeme, aber es ist schade, dass sie so wenig Respekt bekommen
Ich fände es gut, wenn Soft Delete als eingebaute DB-Funktion bereitgestellt würde
Ideal wäre, wenn man es auf Tabellenebene aktivieren und eine Strategie für die Löschbehandlung wählen könnte
Viele Teams implementieren es wegen individueller Anforderungen am Ende aber doch mit SCD (Slowly Changing Dimension)
Meiner Erfahrung nach war ein triggerbasierter Ansatz am stabilsten
Archivtabellen sollten append-only bleiben, und die Wiederherstellung sollte in der Anwendungsschicht behandelt werden
Updates werden als Soft Delete behandelt, und der Trigger erfasst den vorherigen Zustand
Trigger müssen unbedingt im BEFORE-Zeitpunkt ausgeführt werden, und die Logik sollte einfach bleiben
Partitionen werden typischerweise monatlich angelegt, bei hoher Schreiblast sollte man sie täglich aufteilen
Ich wünsche mir, dass sich DBs von stateful → stateless weiterentwickeln
Ich bevorzuge eine Struktur, bei der alle Änderungen als Append-only-Events aufgezeichnet und die benötigten Daten über Views dargestellt werden
Ideal wäre, wenn die DB materialisierte Indizes automatisch verwalten würde
Einige moderne DBs bieten so etwas bereits, aber bisher fehlt noch Entwicklung speziell für OLTP
Siehe Martin Fowlers Erklärung
In einem früheren Unternehmen, in dem ich gearbeitet habe, wurde Soft Delete in allen Systemen eingesetzt
Ich erinnere mich auch daran, dass ein Professor sagte: „In der Geschäftswelt werden Daten niemals gelöscht“
Speicher ist billig, deshalb sollte man Daten niemals löschen
Datenbanken sind Orte, an denen Fakten gespeichert werden
Das Erstellen eines Datensatzes ist ein neuer Fakt, und das Löschen ist ein weiterer Fakt
Wenn man eine Zeile aber physisch löscht, verschwindet der Fakt
In den meisten Fällen ist eine solche Löschung nicht wünschenswert
Man muss Aufbewahrungskosten und Sicherheitsrisiken berücksichtigen
Die Entscheidung, Daten dauerhaft aufzubewahren, sollte sorgfältig getroffen werden
Dafür ist es wichtig, den Lebenszyklus der Daten zu verstehen
Bei Firezone wurde Soft Delete anfangs für Audit-Logs verwendet, dann aber wegen Migrationsproblemen aufgegeben
Stattdessen wurde auf einen Ansatz umgestellt, bei dem mit Postgres CDC (Change Data Capture) Events in eine separate schreiboptimierte Tabelle ausgelagert werden
Ich denke, Soft Delete ist für Benutzer-Wiederherstellungsfunktionen nützlich, aber für Audit oder Compliance ungeeignet
Es ist sauber, über Tabellen mit Soft-Delete-Feld eine View zu legen, die gelöschte Zeilen ausblendet
So muss sich die Anwendung nicht um den Löschstatus kümmern
Die Anwendung liest, schreibt und löscht weiterhin auf derselben Tabelle
Es gibt die Frage, wie man mit Schema Drift umgeht
Wenn man Daten, die beim Löschen im damaligen Schema serialisiert wurden, später wiederherstellen will, werden Schemaänderungen zum Problem
Meist werden sie nur innerhalb weniger Tage nach dem Löschen wiederhergestellt, sodass Schemaänderungen wenig Einfluss haben
Alte Archive auf ein neues Modell zu migrieren, war eine komplexe und fehleranfällige Aufgabe
Letztlich hängt die passende Strategie davon ab, wie das System genutzt wird