Um cliente abre um chamado pedindo a exclusão da conta. O suporte marca deleted_at na tabela accounts, o login para de funcionar e o ticket é fechado. Três semanas depois o mesmo e-mail aparece num relatório de churn, num backup restaurado em staging e num índice de busca que ninguém lembrou de reprocessar. O dado não foi excluído — foi escondido da interface.
Exclusão de conta em SaaS multi-tenant é um problema de inventário, não de comando SQL. Quem trata como DELETE FROM descobre o tamanho real do problema quando a primeira solicitação formal chega com prazo para resposta. O checklist abaixo cobre a implementação em quatro camadas: mapeamento, decisão por tipo de dado, propagação para sistemas satélites e prova de execução.
O que “excluir a conta” significa em cada camada
A confusão começa porque três operações diferentes carregam o mesmo nome. Desativar a conta bloqueia autenticação e mantém tudo. Anonimizar quebra o vínculo entre o registro e a pessoa, preservando o registro. Remover apaga a linha. Cada uma resolve um problema distinto e nenhuma resolve os três.
Na prática, uma exclusão de conta real é uma combinação: alguns dados removidos, outros anonimizados, alguns preservados por obrigação legal ou contratual. Uma nota fiscal emitida não pode ser apagada porque o cliente pediu — mas o nome do usuário que a gerou pode ser substituído por um identificador opaco. Uma linha de log de auditoria que registra “usuário X alterou permissão de Y” perde sentido se o campo virar nulo, mas continua auditável se virar um hash estável.
A decisão precisa ser tomada por categoria de dado, documentada, e refletida no código. Sem isso, cada engenheiro que encosta no fluxo aplica um critério próprio.
Há ainda um efeito colateral típico de multi-tenant: o dado de um tenant referencia o dado de outro. Um usuário convidado que participa de dois workspaces, um recurso compartilhado entre organizações, um comentário feito por alguém de fora. Remover o tenant A sem checar essas bordas quebra a integridade do tenant B — ou, pior, vaza para o B a informação de que o A existiu. A regra prática é tratar identidade de usuário e associação a tenant como entidades separadas: a associação morre com o tenant, a identidade só morre quando não sobra nenhuma associação.
Mapeie o dado antes de escrever o primeiro DELETE
O inventário é a parte cara e a que costuma ser pulada. Num SaaS multi-tenant maduro, o dado de um tenant costuma estar em mais lugares do que o schema sugere:
- Tabelas com
tenant_idexplícito — as fáceis, encontráveis por consulta ao catálogo do banco. - Tabelas ligadas por chave estrangeira indireta — anexos, comentários e eventos que referenciam um recurso que referencia o tenant.
- Object storage — uploads, exportações geradas, PDFs, avatares. Costumam estar em prefixos por tenant, mas nem sempre.
- Índices de busca e caches — Elasticsearch, Redis, cache de aplicação. Não seguem o ciclo de vida do banco.
- Filas e dead-letter queues — mensagens não processadas carregam payload com dado pessoal.
- Sistemas de terceiros — provedor de e-mail transacional, ferramenta de suporte, analytics de produto, gateway de pagamento, data warehouse.
- Logs de aplicação — o lugar onde dado pessoal vaza por acidente com mais frequência.
O mapeamento vira artefato versionado no repositório, não uma planilha. Um arquivo declarativo listando cada destino, a classe de dado que ele guarda e a ação esperada permite escrever um teste que falha quando alguém cria uma tabela nova com tenant_id e esquece de registrá-la.
SELECT table_name FROM information_schema.columns WHERE column_name IN ('tenant_id','account_id') AND table_schema='public' ORDER BY table_name;
Compare o resultado com o manifesto declarado e falhe o build na diferença.
Soft delete, anonimização ou remoção física: o critério
A escolha depende de duas perguntas: o dado identifica uma pessoa e existe obrigação de retenção sobre ele.
| Tipo de dado | Ação | Por quê |
|---|---|---|
| Credenciais, tokens, chaves de API | Remoção física imediata | Nenhum valor de retenção, risco alto se vazarem |
| Conteúdo produzido pelo tenant (documentos, registros de negócio) | Remoção após janela de arrependimento | Pertence ao cliente, sem valor para o provedor |
| Registros fiscais e contratuais | Preservar, anonimizar campos pessoais acessórios | Retenção obrigatória por prazo legal |
| Log de auditoria de segurança | Preservar com identificador pseudonimizado | Precisa manter rastreabilidade de eventos |
| Métricas agregadas de uso | Preservar se já não forem identificáveis | Agregado sem chave de reidentificação deixa de ser dado pessoal |
| Backups | Expirar pelo ciclo normal de retenção | Edição cirúrgica de backup quebra a integridade da cópia |
A janela de arrependimento merece atenção. Trinta dias entre a solicitação e a remoção definitiva reduz drasticamente o volume de restaurações de emergência, e é curta o bastante para caber num prazo de resposta razoável. O erro comum é implementar a janela e não implementar o job que executa a remoção quando ela termina — a conta fica em pending_deletion para sempre.
Sobre soft delete: ele é uma ferramenta de reversibilidade operacional, não uma forma de exclusão. Se a coluna deleted_at é o único mecanismo, a exclusão não aconteceu. Vale usá-la como estado intermediário e ter um processo separado que consome esse estado.
Backups, réplicas e logs: onde o dado sobrevive à exclusão
Backup é a exceção legítima. Tentar apagar uma linha de dentro de um dump comprimido de dezenas de gigabytes é caro, arriscado e invalida a garantia de restauração. A posição defensável é declarar o prazo de retenção de backup, mantê-lo curto o suficiente para ser justificável, e registrar que dados excluídos persistem nas cópias até expirarem naturalmente — com o compromisso de reaplicar a exclusão caso alguma restauração traga o registro de volta.
Esse compromisso precisa virar código. Um procedimento de restauração que não reexecuta a fila de exclusões pendentes é um procedimento que ressuscita contas removidas. Vale manter uma tabela de exclusões confirmadas fora do ciclo de backup principal, ou pelo menos um log append-only de identificadores excluídos que o pós-restore consulta.
Réplicas de leitura seguem o primário e não exigem tratamento separado. Já um data warehouse alimentado por CDC ou por carga noturna é um sistema independente: precisa receber o evento de exclusão explicitamente. O mesmo vale para o índice de busca, que quase sempre é atualizado por evento e quase nunca tem o evento de exclusão testado.
Logs de aplicação são o ponto cego. Se o pipeline registra corpo de requisição, headers ou payload de erro, há dado pessoal ali. A saída realista não é apagar log — é reduzir a retenção da camada que contém payload e mascarar campos sensíveis na origem, antes de escrever.
Vale medir o custo antes de prometer prazo. Num SaaS com object storage volumoso, a remoção de arquivos de um tenant grande pode levar horas e competir por throughput com o tráfego de produção. Rodar isso de forma síncrona dentro de uma requisição HTTP não funciona; rodar sem limite de taxa degrada o serviço de todo mundo. O job precisa de controle de concorrência e de janela preferencial, e o prazo comunicado ao cliente precisa acomodar o pior caso, não o caso médio.
Checklist de execução
A sequência abaixo assume um SaaS multi-tenant com banco relacional, object storage, índice de busca e integrações de terceiros. Cada item é verificável.
- Solicitação registrada com identificador do tenant, quem pediu, data e canal. Sem esse registro não há como provar prazo.
- Autenticação reforçada do pedido — exclusão disparada por e-mail sem confirmação é vetor de ataque. Exija ação autenticada de um administrador do tenant ou confirmação por segundo canal.
- Conta movida para estado de exclusão pendente, com acesso bloqueado, integrações desativadas e webhooks suspensos.
- Cancelamento da assinatura no gateway, para não gerar cobrança sobre uma conta em remoção.
- Job agendado para o fim da janela, com identificador idempotente. Reexecução do job não pode falhar nem duplicar efeitos.
- Execução por manifesto: o job percorre o inventário declarado, não uma lista hardcoded. Cada destino devolve confirmação.
- Propagação para terceiros com tratamento de falha explícito — provedor fora do ar não pode fazer o job inteiro abortar sem retomada.
- Invalidação de cache e reindexação do índice de busca, com verificação de que a consulta pelo tenant devolve vazio.
- Registro de conclusão em log append-only, fora do ciclo de backup padrão, com carimbo de tempo e lista de destinos confirmados.
- Verificação automatizada que roda depois do job e consulta cada destino procurando resíduo. Falha aqui abre incidente.
Dos dez itens, os dois que mais faltam em implementações reais são a idempotência do job e a verificação posterior. Sem idempotência, a primeira falha parcial deixa o sistema num estado que ninguém sabe reconciliar. Sem verificação, a equipe descobre o resíduo pela reclamação do cliente.
Comece pelo manifesto e pelo teste de cobertura que compara o manifesto com o catálogo do banco. É o item mais barato de construir e o único que impede a regressão silenciosa quando o schema crescer.