- Para la recuperación de datos y el cumplimiento normativo, se usa con frecuencia el borrado lógico basado en una columna
archived_at, pero con el tiempo aumentan la complejidad y la ineficiencia
- Este enfoque vuelve más complejos las consultas, índices, migraciones y la lógica de restauración, y como la mayoría de los datos archivados no se vuelven a leer, termina provocando carga innecesaria en la base de datos
- Como alternativas, se proponen el archivado basado en eventos de la aplicación, el archivado basado en triggers y el archivado basado en WAL (Change Data Capture)
- Cada enfoque difiere en complejidad operativa, requisitos de infraestructura y facilidad de restauración; en particular, el basado en WAL requiere integración con sistemas externos como Kafka
- En un proyecto nuevo, el enfoque basado en triggers es la opción más equilibrada en términos de simplicidad y mantenibilidad
Problemas del borrado lógico
- Por lo general, los datos se eliminan de forma lógica usando una columna booleana
deleted o una columna de marca de tiempo archived_at
- Permite recuperarlos cuando un cliente borra datos por error
- En algunos casos también es necesario conservarlos por motivos regulatorios o de auditoría
- Sin embargo, la columna
archived_at genera complejidad en las consultas, la operación y el código de la aplicación
- La mayoría de los datos archivados no se vuelven a leer
- Por problemas en el comportamiento de la API o herramientas de automatización como Terraform, pueden acumularse millones de filas innecesarias
- Si no se configura una tarea de limpieza de datos archivados, se produce una caída de rendimiento en los respaldos y restauraciones de la base de datos
- Es necesario filtrar los datos archivados en consultas e índices, y existe riesgo de filtración de datos
- Durante las migraciones, es difícil manejar datos antiguos o modificar valores predeterminados
- La lógica de restauración se vuelve compleja y, cuando requiere llamadas a sistemas externos, pueden aparecer errores
- En consecuencia, el enfoque con
archived_at parece simple, pero a largo plazo tiene un alto costo de mantenimiento
Archivado a nivel de aplicación
- Al eliminar, se publica un evento y se envía a SQS para que otro servicio lo archive en S3
- Ventajas
- Simplifica la base de datos principal y el código de la aplicación
- Mejora el rendimiento y la estabilidad al manejar de forma asíncrona la limpieza de recursos externos
- Puede archivarse serializado en JSON con una estructura amigable para la aplicación
- Desventajas
- Por errores en el código de la aplicación, puede haber pérdida de datos archivados
- Aumenta la complejidad operativa de la infraestructura, como las colas de mensajes
- Los datos archivados en S3 requieren herramientas de búsqueda y restauración
Archivado basado en triggers
- Antes de eliminar, un trigger copia la fila en una tabla
archive separada en formato JSON
- Tabla de ejemplo:
archive(id, table_name, record_id, data, archived_at, caused_by_table, caused_by_id)
- En eliminaciones por clave foránea (cascade), se usan variables de sesión (
archive.cause_table, archive.cause_id) para rastrear la causa de la eliminación
- Permite consultar qué registro padre eliminó los datos hijos
- Ventajas
- Las tablas activas se mantienen limpias, sin necesidad de la columna
archived_at
- La limpieza de la tabla de archivado (
WHERE archived_at < NOW() - INTERVAL '90 days') es sencilla
- Se mantiene la eficiencia de consultas e índices, y se simplifican las migraciones
- Reduce el tamaño de los respaldos
- La tabla de archivado puede administrarse con un tablespace separado o particionado por tiempo
Archivado basado en WAL (Change Data Capture)
- Se leen los logs WAL de PostgreSQL y se transmiten los eventos de eliminación a un sistema externo
- Herramienta representativa: Debezium (integrado con Kafka)
- Ejemplo de ruta:
PostgreSQL → Debezium → Kafka → Consumer → Archive Storage
- Alternativas más livianas
- pgstream: envía WAL directamente a un webhook o cola de mensajes
- wal2json: emite el WAL en JSON
- pg_recvlogical: herramienta integrada de replicación lógica de PostgreSQL
- Complejidad operativa
- Un entorno basado en Kafka requiere monitoreo, respuesta ante fallos y tuning
- Si el consumer se retrasa, puede haber acumulación de archivos WAL → riesgo de falta de espacio en disco
- Puede limitarse con la configuración
max_slot_wal_keep_size de PostgreSQL 13+
- Es indispensable monitorear el retraso del replication slot y configurar alertas
- Ventajas
- Permite capturar todos los cambios sin modificar el código de la aplicación
- Puede hacer streaming hacia distintos destinos (S3, data warehouse, índice de búsqueda)
- No añade carga extra a la base de datos principal
- Desventajas
- Tiene alta complejidad operativa y costo de infraestructura
- Si el consumer se retrasa, puede haber pérdida de datos o necesidad de resincronización
- Los cambios de esquema requieren coordinación entre la fuente y el consumer
Idea de una réplica que no procese eliminaciones
- Se plantea la idea de mantener una réplica de PostgreSQL que ignore las consultas DELETE
- Permitirá acumular y conservar todos los datos no eliminados
- Los datos archivados podrían consultarse directamente
- Problemas potenciales
- Puede no ser posible distinguir la información eliminada
- Existe riesgo de conflicto al aplicar migraciones
- Aumentan los costos de almacenamiento y operación
Conclusión
- En proyectos nuevos, el archivado basado en triggers es la opción más práctica
- Su configuración es simple y mantiene limpias las tablas activas
- Permite consultar y administrar los datos archivados con facilidad, sin infraestructura adicional
- Si ya existe una infraestructura compleja o se necesita streaming hacia múltiples destinos, el enfoque basado en WAL es adecuado
4 comentarios
Había aprendido que si es basado en triggers genera carga en la BD... ¿y aun así recomiendan triggers?
Si la carga generada por un trigger de ese nivel es un problema, entonces incluso sin el trigger la situación ya está llena de problemas.
Como siempre, la regulación tiene un costo. Bueno, de todos modos es una parte que terminarán pagando los consumidores.
Opiniones de Hacker News
En el dominio bancario donde trabajo, más bien siento que el soft delete sí conviene
Si tienes una columna
deleted_at, escribir consultas es más claro, y tanto las consultas analíticas como el panel de administración pueden trabajar sobre el mismo conjunto de datosBorrar suele ser algo poco frecuente en la mayoría de los casos, y casi nunca he visto que las filas con soft delete causen problemas de rendimiento
Además, como las relaciones se mantienen intactas, también es fácil restaurar (undo)
Yo incluso prefiero ir más allá y hacer que las filas sean completamente inmutables (immutable), agregando una fila nueva cada vez que hay una actualización
Si quieres dejar un log, me parece buena idea usar triggers de la DB para registrar en una tabla espejo cada INSERT/UPDATE/DELETE
En tablas que he visto donde entre 50 y 70% estaba en soft delete, la degradación de rendimiento sí era clara
Al final, el soft delete depende del contexto y requiere análisis previo
En la mayoría de los casos no hace falta, pero puede ayudar a ahorrar RAM
La solución real es Event Sourcing, registrando todos los cambios como eventos
El rendimiento empeora, pero se puede compensar con snapshots y sincronización (sync)
Con la función de time travel puedes consultar por completo estados pasados
El estado más reciente estaba en la fila con el timestamp más alto, y los estados pasados se podían consultar con filtros
Este enfoque permite una gestión de historial muy potente
La mayor trampa del soft delete es la complejidad de las consultas
Al principio uno piensa que basta con agregar
WHERE deleted_at IS NULL, pero unos meses después aparecen datos fantasma en los reportes por filtros omitidosSe puede resolver con views, pero al final igual tienes que mantener patrones de acceso paralelos, y cuando necesitas consultar datos borrados terminas saltándote la abstracción
Event sourcing es más limpio, pero como la carga operativa es alta, la mayoría termina eligiendo un enfoque híbrido
El problema es que muchos SWE e ingenieros de BI no están familiarizados con SQL ni con el diseño de esquemas
Un problema todavía más común que el soft delete es manejar Type 2 Slowly Changing Dimension
La mayoría crea audit tables innecesarias y repite UPDATE/INSERT de forma ineficiente
La verdad es que las DB son sistemas realmente hermosos, y da pena que no reciban el respeto que merecen
Creo que sería bueno que el soft delete viniera como una función integrada de la DB
Sería ideal poder activarlo por tabla y elegir la estrategia de eliminación
Pero muchos equipos terminan implementándolo con enfoque SCD (Slowly Changing Dimension) por requisitos personalizados
En mi experiencia, el enfoque basado en triggers ha sido el más estable
La tabla de archivo debe mantenerse como append-only, y la restauración debe manejarse en la capa de aplicación
Las actualizaciones se consideran soft delete, y el trigger debe capturar el estado anterior
El trigger tiene que ejecutarse obligatoriamente en el momento BEFORE, y la lógica debe ser simple
Las particiones por mes son lo normal, y si hay mucha carga de escritura conviene dividir por día
Me gustaría que las DB evolucionaran de stateful → stateless
Prefiero una estructura donde todos los cambios se registren como eventos append-only, y los datos necesarios se expresen mediante views
Sería ideal que la DB administrara automáticamente un materialized index
Algunas DB modernas ya ofrecen algo así, pero todavía falta desarrollo orientado a OLTP
Vale la pena revisar la explicación de Martin Fowler
En una empresa donde estuve antes aplicaban soft delete en todos los sistemas
Recuerdo que un profesor decía: “en el mundo de los negocios, los datos nunca se borran”
El almacenamiento es barato, así que no deberíamos borrar datos nunca
La base de datos es un lugar para almacenar hechos (facts)
Crear un registro es un hecho nuevo, y eliminarlo es otro hecho más
Pero si borras físicamente una fila, ese hecho desaparece
En la mayoría de los casos, ese tipo de eliminación no es deseable
Hay que considerar el costo de mantenimiento y los riesgos de seguridad
La decisión de conservar los datos permanentemente debe tomarse con cuidado
Para eso es importante entender el ciclo de vida de los datos
En Firezone al principio usaban soft delete para logs de auditoría, pero lo abandonaron por problemas de migración
En su lugar, cambiaron a un enfoque que usa Postgres CDC (Change Data Capture) para enviar eventos a una tabla separada optimizada para escritura
Creo que el soft delete sí es útil para la recuperación por parte del usuario, pero no es adecuado para auditoría o cumplimiento normativo
Crear una view sobre una tabla con campo de soft delete para ocultar las filas eliminadas es una solución limpia
Así, la aplicación no tiene que preocuparse por si algo fue eliminado o no
La aplicación sigue haciendo lectura/escritura/eliminación sobre la misma tabla
También está la pregunta de cómo manejar el schema drift
Si luego quieres restaurar datos serializados con el esquema que existía al momento de la eliminación, los cambios de esquema se vuelven un problema
La mayoría de las restauraciones ocurren dentro de unos días después del borrado, así que el impacto de cambios de esquema suele ser bajo
Migrar archivos antiguos a un modelo nuevo era una tarea compleja y propensa a errores
Al final, la estrategia depende de cómo se use realmente el sistema