23 pontos por GN⁺ 2026-01-23 | 4 comentários | Compartilhar no WhatsApp
  • 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

 
love7peace 2026-01-23

Se for baseado em trigger, eu aprendi que isso gera carga no banco de dados...? Recomendar trigger então...

 
nemorize 2026-01-24

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.

 
cherrycoder 2026-01-23

No fim das contas, regulação sempre tem custo. Bom, de qualquer forma, é uma conta que os consumidores acabam pagando.

 
GN⁺ 2026-01-23
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 dados
    Na 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

    • Você tem razão, mas eu diria que isso só vale quando exclusões são raras
      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
    • No Postgres, também dá para configurar índices apenas para os dados que não estão em soft delete
      Na maioria dos casos isso não é necessário, mas pode ajudar a economizar RAM
    • Em bancos, soft delete é apenas uma solução temporária para mascarar a falta de auditabilidade (auditability)
      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)
    • Para operar o DB com uma estrutura imutável, vale considerar sistemas como Datomic
      Com o recurso de time travel, dá para consultar completamente estados passados
    • Quando trabalhei numa seguradora no passado, cada tabela também era operada como um log append-only
      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 esquecidos
    Dá 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

    • Views são ferramentas poderosas o suficiente
      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

    • Na prática, sistemas como Iceberg, Delta Lake, BigQuery já oferecem recurso de time travel
      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

  • 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

    • Fazer exclusão definitiva é limitar por conta própria a futura capacidade de análise de dados
      Armazenamento é barato, então dados nunca deveriam ser apagados
    • Mas é curioso que o professor não tenha dito nada sobre alterações
  • 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

    • Mas se os dados forem um ativo com risco de vazamento, talvez exclusões em massa sejam necessárias
      É preciso considerar custo de retenção e risco de segurança
    • Se o DB não for imutável, a própria alteração já cria um fato desaparecido
      A decisão de preservar dados permanentemente deve ser tomada com cuidado
    • Pessoalmente, acho que um armazenamento de dados deveria suportar apenas duas operações: consulta e inserção
      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

    • Em projetos simples, é mais eficiente auditar a própria chamada de API do que mudanças no DB
  • 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

    • Com RLS (Row Level Security) do Postgres, também dá para ocultar automaticamente as linhas em soft delete
      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

    • Pela minha experiência, objetos arquivados quase nunca são acessados
      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