- A exclusão lógica baseada na coluna
archived_at é usada com frequência para recuperação de dados e conformidade regulatória, mas com o tempo a complexidade e a ineficiência aumentam
- Essa abordagem torna consultas, índices, migrações e a lógica de restauração mais complexos e, como a maior parte dos dados arquivados nunca é lida novamente, acaba causando carga desnecessária no banco de dados
- Como alternativas, são apresentados o arquivamento baseado em eventos da aplicação, o arquivamento baseado em triggers e o arquivamento baseado em WAL (Change Data Capture)
- Cada abordagem difere em complexidade operacional, requisitos de infraestrutura e facilidade de restauração; em especial, a baseada em WAL exige integração com sistemas externos como Kafka
- Em projetos novos, a abordagem baseada em triggers é a escolha mais equilibrada em termos de simplicidade e manutenibilidade
Problemas da exclusão lógica
- Em geral, os dados são excluídos logicamente usando uma coluna booleana
deleted ou uma coluna de timestamp archived_at
- Isso permite recuperar dados quando o cliente os exclui por engano
- Também pode ser necessário manter os dados por motivos regulatórios ou de auditoria
- Porém, a coluna
archived_at introduz complexidade em consultas, operações e código da aplicação
- A maior parte dos dados arquivados nunca volta a ser lida
- Devido a problemas no comportamento da API ou ferramentas de automação (como Terraform), milhões de linhas desnecessárias podem se acumular
- Se não houver uma rotina configurada para limpar os dados arquivados, haverá queda de desempenho em backup e restauração do banco de dados
- É preciso filtrar os dados arquivados em consultas e índices, e existe risco de vazamento de dados
- Em migrações, é difícil lidar com dados antigos ou alterar valores padrão
- A lógica de restauração se torna complexa e, quando são necessárias chamadas a sistemas externos, podem surgir bugs
- Como resultado, embora o método
archived_at pareça simples, o custo de manutenção no longo prazo é alto
Arquivamento em nível de aplicação
- Ao excluir, a aplicação publica um evento, que é enviado ao SQS para que outro serviço arquive os dados no S3
- Vantagens
- Simplifica o banco de dados principal e o código da aplicação
- Permite limpar recursos externos de forma assíncrona, melhorando desempenho e confiabilidade
- Os dados podem ser serializados em JSON e arquivados em uma estrutura amigável para a aplicação
- Desvantagens
- Bugs no código da aplicação podem causar perda de dados arquivados
- Aumenta a complexidade operacional da infraestrutura, como filas de mensagens
- Os dados arquivados no S3 exigem ferramentas de busca e restauração
Arquivamento baseado em triggers
- Antes da exclusão, um trigger copia a linha em formato JSON para uma tabela de archive separada
- Exemplo de tabela:
archive(id, table_name, record_id, data, archived_at, caused_by_table, caused_by_id)
- Em exclusões por chave estrangeira (cascade), usam-se variáveis de sessão (
archive.cause_table, archive.cause_id) para rastrear a causa da exclusão
- Assim, é possível consultar qual registro pai excluiu os dados filhos
- Vantagens
- As tabelas ativas permanecem limpas, sem necessidade da coluna
archived_at
- A limpeza da tabela de archive (
WHERE archived_at < NOW() - INTERVAL '90 days') é simples
- Mantém a eficiência de consultas e índices, e simplifica migrações
- Reduz o tamanho dos backups
- A tabela de archive pode ser gerenciada com tablespace separado ou particionamento por tempo
Arquivamento baseado em WAL (Change Data Capture)
- Os logs WAL do PostgreSQL são lidos para transmitir eventos de exclusão a sistemas externos
- Ferramenta representativa: Debezium (integrado ao Kafka)
- Exemplo de fluxo:
PostgreSQL → Debezium → Kafka → Consumer → Archive Storage
- Alternativas mais leves
- pgstream: envia o WAL diretamente para webhooks ou filas de mensagens
- wal2json: produz o WAL em JSON
- pg_recvlogical: ferramenta nativa de replicação lógica do PostgreSQL
- Complexidade operacional
- Soluções baseadas em Kafka exigem monitoramento, resposta a falhas e tuning
- Se o consumer atrasar, pode haver acúmulo de arquivos WAL → risco de falta de espaço em disco
- Isso pode ser limitado com a configuração
max_slot_wal_keep_size do PostgreSQL 13+
- É essencial monitorar o atraso do slot de replicação e configurar alertas
- Vantagens
- Permite capturar todas as mudanças sem modificar o código da aplicação
- É possível transmitir para vários destinos (S3, data warehouse, índice de busca)
- Não adiciona carga extra ao banco de dados principal
- Desvantagens
- A complexidade operacional e o custo de infraestrutura são altos
- Se o consumer atrasar, pode haver perda de dados ou necessidade de resincronização
- Mudanças de esquema exigem coordenação entre a origem e o consumer
Ideia de uma réplica que não processa exclusões
- É apresentada a ideia de manter uma réplica do PostgreSQL que ignora queries DELETE
- Isso permitiria acumular e preservar todos os dados não excluídos
- Os dados arquivados poderiam ser consultados diretamente
- Problemas potenciais
- Pode não ser possível distinguir informações de exclusão
- Há risco de conflito ao aplicar migrações
- Aumentam os custos de armazenamento e operação
Conclusão
- Em projetos novos, o arquivamento baseado em triggers é a escolha mais prática
- A configuração é simples e mantém as tabelas ativas limpas
- Facilita a consulta e a gestão dos dados arquivados sem necessidade de infraestrutura separada
- Quando já existe uma infraestrutura complexa ou é necessário fazer streaming para vários destinos, a abordagem baseada em WAL é mais adequada
4 comentários
Se for baseado em trigger, eu aprendi que isso gera carga no banco de dados...? Recomendar trigger então...
Se a carga gerada por um gatilho desse nível vira um problema, então mesmo sem o gatilho a situação já está cheia de problemas.
No fim das contas, regulação sempre tem custo. Bom, de qualquer forma, é uma conta que os consumidores acabam pagando.
Comentários do Hacker News
No domínio bancário em que trabalho, na verdade sinto que o soft delete é vantajoso
Se houver uma coluna
deleted_at, fica claro como escrever as queries, e tanto as queries analíticas quanto as páginas de administração podem lidar com o mesmo conjunto de dadosNa maioria dos casos, exclusões são raras, e quase nunca vi linhas com soft delete causarem problemas de desempenho
Além disso, como os relacionamentos permanecem intactos, a recuperação (undo) também é simples
Eu iria além e prefiro tornar a linha totalmente imutável (immutable), adicionando uma nova linha a cada atualização
Para manter logs, acho boa a abordagem de usar triggers no DB para registrar INSERT/UPDATE/DELETE em uma tabela de réplica
Em tabelas que vi com 50~70% das linhas em soft delete, a queda de desempenho era nítida
No fim, soft delete depende do contexto, e é preciso análise prévia
Na maioria dos casos isso não é necessário, mas pode ajudar a economizar RAM
A solução real é Event Sourcing, registrando todas as mudanças como eventos
O desempenho piora, mas isso pode ser compensado com snapshots e sincronização (sync)
Com o recurso de time travel, dá para consultar completamente estados passados
O estado mais recente ficava na linha com o maior timestamp, e os estados anteriores podiam ser consultados com filtros
Esse método permite um gerenciamento de histórico muito forte
A maior armadilha do soft delete é a complexidade das queries
No começo parece que basta adicionar
WHERE deleted_at IS NULL, mas alguns meses depois dados fantasmas aparecem em relatórios por causa de filtros esquecidosDá para resolver com views, mas no fim ainda é preciso manter padrões de acesso paralelos, e ao consultar dados excluídos é necessário contornar a abstração
Event sourcing é mais limpo, mas como traz carga operacional alta, a maioria acaba escolhendo uma abordagem híbrida
O problema é que muitos engenheiros de software e BI não estão acostumados com SQL e desenho de schema
Um problema ainda mais comum que soft delete é lidar com Type 2 Slowly Changing Dimension
A maioria acaba criando audit tables desnecessárias e repetindo UPDATE/INSERT de forma ineficiente
Na verdade, DBs são sistemas realmente belos, e é uma pena que não recebam o devido respeito
Acho que seria bom se o soft delete fosse oferecido como recurso nativo do DB
Seria ideal poder ativá-lo por tabela e escolher a estratégia de tratamento da exclusão
Mas muitas equipes acabam implementando no estilo SCD (Slowly Changing Dimension) por causa de requisitos customizados
Pela minha experiência, a abordagem baseada em triggers foi a mais estável
A tabela de arquivo deve ser mantida como append-only, e a restauração deve ser tratada na camada da aplicação
Atualizações são tratadas como soft delete, e a trigger captura o estado anterior
A trigger deve rodar obrigatoriamente no momento BEFORE, e a lógica deve ser simples
Particionamento mensal é o mais comum, e com muita carga de escrita é melhor dividir por dia
Eu gostaria que os DBs evoluíssem de stateful → stateless
Prefiro uma estrutura que registre todas as mudanças como eventos append-only e expresse os dados necessários por meio de views
Seria ideal se o DB gerenciasse automaticamente materialized indexes
Alguns DBs modernos já oferecem isso, mas ainda falta evolução com foco em OLTP
Vale consultar a explicação de Martin Fowler
Numa empresa em que trabalhei antes, aplicávamos soft delete em todos os sistemas
Também me lembro de um professor dizer que “no mundo dos negócios, os dados nunca são realmente apagados”
Armazenamento é barato, então dados nunca deveriam ser apagados
O banco de dados é um lugar para armazenar fatos (facts)
Criar um registro é um novo fato, e excluir também é outro fato
Mas se você apaga fisicamente uma linha, o fato desaparece
Na maioria dos casos, esse tipo de exclusão não é desejável
É preciso considerar custo de retenção e risco de segurança
A decisão de preservar dados permanentemente deve ser tomada com cuidado
Para isso, é importante entender o ciclo de vida dos dados
Na Firezone, no começo usaram soft delete para logs de auditoria, mas desistiram por causa de problemas de migração
Em vez disso, migraram para uma abordagem que usa Postgres CDC (Change Data Capture) para enviar eventos para uma tabela separada otimizada para escrita
Acho que soft delete é útil para recuperação pelo usuário, mas inadequado para auditoria ou conformidade regulatória
Criar uma view sobre uma tabela com campo de soft delete, escondendo as linhas excluídas, é uma abordagem limpa
Assim, a aplicação não precisa se preocupar com o status de exclusão
A aplicação continua fazendo leitura/escrita/exclusão na mesma tabela
Há quem pergunte como lidar com schema drift
Se você quiser restaurar depois dados serializados com o schema da época da exclusão, mudanças de schema viram um problema
Na maioria dos casos, a restauração acontece poucos dias após a exclusão, então o impacto de mudanças de schema é pequeno
Migrar arquivos antigos para o novo modelo era uma tarefa complexa e sujeita a erros
No fim, a estratégia de abordagem varia conforme a forma de uso do sistema