La difficulté de la suppression logique
(atlas9.dev)- La suppression logique basée sur une colonne
archived_atest souvent utilisée pour la récupération des données et la conformité réglementaire, mais avec le temps, la complexité et l’inefficacité augmentent - Cette approche complique les requêtes, les index, les migrations et la logique de restauration, et comme la plupart des données archivées ne sont jamais relues, elle impose une charge inutile sur la base de données
- Comme alternatives, sont proposées l’archivage basé sur les événements applicatifs, l’archivage basé sur des triggers et l’archivage basé sur le WAL (Change Data Capture)
- Chaque méthode diffère en termes de complexité opérationnelle, d’exigences d’infrastructure et de facilité de restauration ; en particulier, l’approche basée sur le WAL nécessite une intégration avec des systèmes externes comme Kafka
- Pour un nouveau projet, l’approche basée sur des triggers constitue le choix le plus équilibré en matière de simplicité et de maintenabilité
Les problèmes de la suppression logique
- En général, les données sont supprimées logiquement à l’aide d’un booléen
deletedou d’une colonne horodatéearchived_at- Cela permet de restaurer les données lorsqu’un client les a supprimées par erreur
- Certaines données doivent aussi être conservées pour des raisons réglementaires ou d’audit
- Cependant, la colonne
archived_atintroduit de la complexité dans les requêtes, les opérations et le code applicatif- La plupart des données archivées ne sont jamais relues
- À cause de problèmes de comportement d’API ou d’outils d’automatisation (comme Terraform), des millions de lignes inutiles peuvent s’accumuler
- Si aucun nettoyage des données archivées n’est mis en place, on observe une dégradation des performances lors des sauvegardes et restaurations de la base de données
- Il faut filtrer les données archivées dans les requêtes et les index, avec un risque de fuite de données
- Lors des migrations, il devient difficile de traiter les anciennes données ou de modifier les valeurs par défaut
- La logique de restauration devient complexe et, lorsqu’elle nécessite des appels à des systèmes externes, des bugs peuvent apparaître
- En conséquence, l’approche
archived_atsemble simple, mais son coût de maintenance à long terme est élevé
Archivage au niveau applicatif
- Lors d’une suppression, un événement est publié, envoyé vers SQS, puis un autre service archive les données dans S3
- Avantages
- Simplification de la base de données principale et du code applicatif
- Le nettoyage des ressources externes est traité de manière asynchrone, ce qui améliore les performances et la stabilité
- Les données peuvent être sérialisées en JSON et archivées dans une structure adaptée à l’application
- Inconvénients
- Un bug dans le code applicatif peut entraîner une perte des données archivées
- La complexité de l’infrastructure d’exploitation augmente, avec par exemple l’ajout d’une file de messages
- Les données archivées dans S3 nécessitent des outils de recherche et de restauration
Archivage basé sur des triggers
- Avant la suppression, un trigger copie la ligne dans une table
archivedistincte au format JSON- Exemple de table :
archive(id, table_name, record_id, data, archived_at, caused_by_table, caused_by_id)
- Exemple de table :
- Lors d’une suppression par clé étrangère (cascade), des variables de session (
archive.cause_table,archive.cause_id) sont utilisées pour tracer la cause de la suppression- Cela permet de savoir quel enregistrement parent a supprimé les données filles
- Avantages
- Les tables actives restent propres, sans colonne
archived_at - Le nettoyage de la table d’archive (
WHERE archived_at < NOW() - INTERVAL '90 days') est simple - L’efficacité des requêtes et des index est préservée, et les migrations sont simplifiées
- La taille des sauvegardes diminue
- Les tables actives restent propres, sans colonne
- La table d’archive peut être gérée dans un tablespace distinct ou via un partitionnement temporel
Archivage basé sur le WAL (Change Data Capture)
- Les logs WAL de PostgreSQL sont lus pour diffuser les événements de suppression vers des systèmes externes
- Outil représentatif : Debezium (intégré à Kafka)
- Exemple de chaîne :
PostgreSQL → Debezium → Kafka → Consumer → Archive Storage
- Alternatives légères
- pgstream : envoi direct du WAL vers un webhook ou une file de messages
- wal2json : sortie du WAL au format JSON
- pg_recvlogical : outil intégré de réplication logique de PostgreSQL
- Complexité opérationnelle
- Une architecture basée sur Kafka nécessite supervision, gestion des incidents et tuning
- Si le consumer prend du retard, les fichiers WAL s’accumulent → risque de manque d’espace disque
- Il est possible de limiter cela avec le paramètre
max_slot_wal_keep_sizede PostgreSQL 13+ - La surveillance du retard des slots de réplication et les alertes sont indispensables
- Avantages
- Toutes les modifications peuvent être capturées sans modifier le code applicatif
- Le flux peut être envoyé vers plusieurs destinations (S3, data warehouse, index de recherche)
- Aucune charge supplémentaire n’est ajoutée à la base de données principale
- Inconvénients
- Complexité opérationnelle et coût d’infrastructure élevés
- En cas de retard du consumer, il peut y avoir perte de données ou besoin de resynchronisation
- Lors d’un changement de schéma, une coordination entre la source et les consumers est nécessaire
L’idée d’une réplique qui ne traite pas les suppressions
- L’idée consiste à maintenir une réplique PostgreSQL qui ignore les requêtes DELETE
- Cela permettrait d’accumuler et conserver toutes les données non supprimées
- Les données archivées pourraient être interrogées directement
- Problèmes potentiels
- Impossible, potentiellement, de distinguer les informations de suppression
- Risque de conflit lors de l’application des migrations
- Hausse des coûts de stockage et d’exploitation
Conclusion
- Dans un nouveau projet, l’archivage basé sur des triggers est le choix le plus pratique
- Il est simple à mettre en place et permet de garder les tables actives propres
- Il facilite la consultation et la gestion des données archivées, sans infrastructure supplémentaire
- Si une infrastructure complexe existe déjà ou qu’un streaming vers plusieurs destinations est nécessaire, une approche basée sur le WAL est plus adaptée
4 commentaires
J’avais appris qu’avec une approche basée sur des triggers, ça mettait de la charge sur la base de données…? Et là, on recommande les triggers.
Si la surcharge générée par des triggers de ce niveau devient un problème, alors même sans triggers, la situation est déjà pleine de problèmes.
Comme toujours, la réglementation a un coût. Bon, de toute façon, c’est aux consommateurs de le payer.
Réactions sur Hacker News
Dans le domaine bancaire où je travaille, j’ai au contraire trouvé le soft delete avantageux
Avec une colonne
deleted_at, l’écriture des requêtes est claire, et les requêtes d’analyse comme les pages d’administration peuvent manipuler le même jeu de donnéesLes suppressions sont rares dans la plupart des cas, et les lignes en soft delete n’ont presque jamais causé de problèmes de performance
En plus, comme les relations restent intactes, la restauration (undo) est simple
Je préfère même aller plus loin et rendre les lignes totalement immutables, en ajoutant une nouvelle ligne à chaque mise à jour
Pour conserver un journal, je trouve pertinent d’utiliser des triggers DB afin d’enregistrer dans une table de réplication chaque opération INSERT/UPDATE/DELETE
Dans des tables que j’ai vues, où 50 à 70 % des lignes étaient en soft delete, la baisse de performances était bien réelle
Au final, le soft delete dépend du contexte, et une analyse préalable est nécessaire
Ce n’est pas nécessaire dans la plupart des cas, mais cela peut aider à économiser la RAM
La vraie solution, c’est l’Event Sourcing, où tous les changements doivent être enregistrés comme des événements
Les performances en pâtissent, mais on peut compenser avec des snapshots et de la synchronisation (sync)
Sa fonction de time travel permet d’interroger complètement les états passés
L’état le plus récent se trouvait sur la ligne avec le plus grand timestamp, et les états passés pouvaient être consultés via des filtres
Cette approche permet une gestion d’historique très robuste
Le plus gros piège du soft delete, c’est la complexité des requêtes
Au début, on pense qu’il suffit d’ajouter
WHERE deleted_at IS NULL, mais quelques mois plus tard, des données fantômes apparaissent dans les rapports à cause de filtres oubliésOn peut le résoudre avec des views, mais il faut malgré tout maintenir des patterns d’accès parallèles, et lorsqu’il faut consulter les données supprimées, on doit contourner l’abstraction
L’event sourcing est plus propre, mais comme la charge opérationnelle est élevée, la plupart choisissent une approche hybride
Le problème, c’est que beaucoup de SWE et d’ingénieurs BI ne sont pas vraiment à l’aise avec SQL et la conception de schémas
Un problème encore plus courant que le soft delete, c’est la gestion des Type 2 Slowly Changing Dimension
La plupart créent inutilement des audit tables et répètent des UPDATE/INSERT inefficaces
En réalité, une DB est un système vraiment magnifique, et c’est dommage qu’elle soit si peu respectée
Je pense que ce serait bien si le soft delete était fourni comme fonctionnalité native de la DB
Pouvoir l’activer au niveau d’une table et choisir une stratégie de suppression serait idéal
Mais beaucoup d’équipes finissent quand même par l’implémenter en SCD (Slowly Changing Dimension) à cause d’exigences spécifiques
D’après mon expérience, l’approche basée sur des triggers a été la plus fiable
La table d’archive doit rester en append-only, et la restauration doit être gérée dans la couche applicative
Une mise à jour doit être considérée comme un soft delete, et le trigger doit capturer l’état précédent
Le trigger doit absolument s’exécuter en phase BEFORE, et la logique doit rester simple
Le partitionnement mensuel est courant, et avec une forte charge d’écriture, il vaut mieux passer à un découpage journalier
J’aimerais voir les DB évoluer de stateful → stateless
Je préfère une architecture où tous les changements sont enregistrés comme des événements append-only, et où les données nécessaires sont exposées via des views
L’idéal serait que la DB gère automatiquement des materialized index
Certaines DB modernes offrent déjà ce type de fonctionnalité, mais il manque encore des avancées centrées sur l’OLTP
On peut se référer à l’explication de Martin Fowler
Dans une entreprise où j’ai travaillé auparavant, le soft delete était appliqué à tous les systèmes
Je me souviens aussi d’un professeur qui disait : « Dans le monde de l’entreprise, les données ne sont jamais supprimées »
Le stockage ne coûte pas cher, donc il ne faudrait jamais effacer les données
Une base de données est un endroit où l’on stocke des faits (facts)
La création d’un enregistrement est un nouveau fait, et sa suppression en est un autre
Mais si l’on efface physiquement une ligne, le fait disparaît
Dans la plupart des cas, ce type de suppression n’est pas souhaitable
Il faut prendre en compte le coût de conservation et le risque de sécurité
La décision de conserver les données pour toujours doit être prise avec prudence
Pour cela, il est important de comprendre le cycle de vie des données
Chez Firezone, on a d’abord utilisé le soft delete pour les logs d’audit, mais on y a renoncé à cause de problèmes de migration
À la place, l’équipe est passée à l’utilisation de Postgres CDC (Change Data Capture) pour exporter les événements vers une table séparée optimisée pour l’écriture
Le soft delete est utile pour la restauration côté utilisateur, mais à mon avis inadapté pour l’audit ou la conformité
Créer une view au-dessus d’une table qui contient un champ de soft delete, afin de masquer les lignes supprimées, est une approche propre
De cette manière, l’application n’a pas à se soucier de l’état de suppression
L’application continue malgré tout à lire, écrire et supprimer sur la même table
Certains demandent comment gérer la schema drift
Si l’on veut restaurer plus tard des données sérialisées selon le schéma au moment de la suppression, les changements de schéma posent problème
Dans la plupart des cas, ils sont restaurés dans les jours qui suivent leur suppression, donc l’impact des changements de schéma reste limité
Faire migrer d’anciennes archives vers un nouveau modèle était une tâche complexe et sujette aux erreurs
Au final, la stratégie à adopter dépend de la manière dont le système est utilisé