Privacidade Por Equipe DevBras

Capa do artigo: A conta foi apagada, o backup voltou e a pessoa reapareceu: como evitar

A conta foi apagada, o backup voltou e a pessoa reapareceu: como evitar

Registre exclusões fora do banco que você restaura, reaplique tudo depois de cada restauração e diga à pessoa por quanto tempo os dados ainda ficam nas cópias de segurança.

Na terça, uma cliente pede a exclusão da conta numa loja online, e o sistema apaga tudo. Na quinta, uma migração corrompe a tabela de pedidos e o time restaura o backup de segunda à noite. Na sexta, a cliente recebe a newsletter. A conta dela voltou junto com o backup, porque a exclusão aconteceu depois da cópia. A resposta curta: guarde um registro de exclusões fora do banco que você restaura e reaplique esse registro depois de toda restauração, antes de liberar o sistema. E diga à pessoa, com número, por quanto tempo os dados dela ainda existem nas cópias de segurança até elas expirarem.

Backup existe para recuperar de desastre. Ele não pode virar uma forma de desfazer um pedido de exclusão.

Por que o backup traz de volta o que você apagou

Um backup é uma foto do banco num instante. Tudo o que foi apagado depois desse instante ainda está na foto. Restaurar é voltar no tempo, e isso inclui voltar antes da exclusão.

A armadilha mais comum é guardar a lista de exclusões numa tabela do mesmo banco. Parece correto, até a primeira restauração: a tabela de exclusões também volta para segunda à noite e perde o pedido de terça. O registro precisa morar num lugar que não volta no tempo junto com o banco principal, como um banco separado, um serviço de armazenamento com histórico próprio ou um arquivo de log somente de inclusão replicado fora do servidor.

O mesmo vale para cópias derivadas: réplicas de leitura, data warehouse, exportações para ferramenta de e-mail, planilhas. Se uma delas é alimentada a partir do banco restaurado, ela também recebe a pessoa de volta.

O registro de exclusões

O registro guarda o mínimo necessário para refazer a exclusão: o identificador interno da conta e a data. Nada de nome ou e-mail, porque o objetivo é justamente não manter dado pessoal. O identificador interno, sozinho, não diz quem a pessoa é depois que a conta some.

-- banco separado: controle
CREATE TABLE exclusoes (
  usuario_id     BIGINT UNSIGNED NOT NULL PRIMARY KEY,
  solicitada_em  DATETIME NOT NULL,
  executada_em   DATETIME NULL
);

Esse banco tem backup próprio, com frequência maior que a do banco principal ou com replicação contínua, e nunca é restaurado junto com ele.

Como fazer certo passo a passo

  1. Escreva a função de exclusão como idempotente. Rodar duas vezes para a mesma conta tem o mesmo efeito que rodar uma. Isso permite reaplicar sem medo.
  2. No pedido, grave primeiro no registro e depois execute a exclusão. Se a exclusão falhar, o registro garante que ela será tentada de novo.
  3. Cubra as cópias derivadas. A função também remove a pessoa da ferramenta de e-mail, do índice de busca e de onde mais o dado for copiado.
  4. Inclua a reaplicação no roteiro de restauração. Restaurar, reaplicar exclusões, conferir e só então liberar o tráfego.
  5. Defina a retenção dos backups e escreva o número no aviso de exclusão.
  6. Teste o roteiro no ensaio de restauração, com uma conta de teste apagada depois do backup.

A exclusão e a reaplicação em Node.js

const principal = require('./db-principal'); // mysql2/promise
const controle = require('./db-controle');   // mysql2/promise, outro servidor

async function excluirConta(usuarioId) {
  const conn = await principal.getConnection();
  try {
    await conn.beginTransaction();
    await conn.execute('DELETE FROM sessoes WHERE usuario_id = ?', [usuarioId]);
    await conn.execute('DELETE FROM enderecos WHERE usuario_id = ?', [usuarioId]);
    await conn.execute(
      'UPDATE pedidos SET usuario_id = NULL, nome_entrega = NULL, endereco_entrega = NULL WHERE usuario_id = ?',
      [usuarioId]
    );
    await conn.execute('DELETE FROM usuarios WHERE id = ?', [usuarioId]);
    await conn.commit();
  } catch (err) {
    await conn.rollback();
    throw err;
  } finally {
    conn.release();
  }
  await removerDaFerramentaDeEmail(usuarioId);
}

async function pedirExclusao(usuarioId) {
  await controle.execute(
    'INSERT INTO exclusoes (usuario_id, solicitada_em) VALUES (?, NOW()) ON DUPLICATE KEY UPDATE usuario_id = usuario_id',
    [usuarioId]
  );
  await excluirConta(usuarioId);
  await controle.execute('UPDATE exclusoes SET executada_em = NOW() WHERE usuario_id = ?', [usuarioId]);
}

async function reaplicarExclusoes() {
  const [linhas] = await controle.execute('SELECT usuario_id FROM exclusoes ORDER BY solicitada_em');
  for (const { usuario_id } of linhas) {
    await excluirConta(usuario_id);
  }
  return linhas.length;
}

Os pedidos não são apagados, só desvinculados: a loja precisa manter o registro da venda por obrigação fiscal, mas não precisa saber quem comprou depois que a conta deixou de existir. Qual dado fica por obrigação legal é uma decisão a confirmar com o contador e o jurídico, não com o time de desenvolvimento sozinho. Isto não substitui a orientação de um profissional habilitado.

A reaplicarExclusoes percorre o registro inteiro, não só os pedidos posteriores ao backup. Como a exclusão é idempotente, contas que já não existem passam sem efeito, e você não depende de saber o horário exato da cópia restaurada. Se o registro crescer muito, filtre por solicitada_em a partir de alguns dias antes da data do backup.

O que dizer para a pessoa

A mensagem de confirmação precisa ser honesta: "Sua conta e seus dados foram apagados do sistema. Cópias de segurança são mantidas por até 35 dias para recuperação de desastres e, nesse período, não são usadas para nenhuma outra finalidade. Depois disso, elas expiram." O número tem de ser o da sua política real de retenção de backup. Se você não sabe qual é, essa é a primeira tarefa; o post dado com prazo de guarda ajuda a definir.

Para sistemas com exigência mais alta, existe a técnica de criptografar os dados de cada pessoa com uma chave própria e, na exclusão, destruir a chave. Os dados continuam no backup, mas ficam ilegíveis. É mais complexo de operar e exige gestão cuidadosa de chaves, então só compensa quando o prazo de retenção de backup não é aceitável.

Erros comuns

  • Registro de exclusões no mesmo banco que é restaurado. Ele volta no tempo junto e esquece exatamente as exclusões que você precisava reaplicar.
  • Restaurar e liberar o tráfego antes de reaplicar. Nesse intervalo, a newsletter sai e a conta apagada aceita login.
  • Exclusão que não é idempotente. Reaplicar dá erro na segunda execução e o roteiro para no meio.
  • Esquecer as cópias derivadas. A pessoa some do banco e continua na ferramenta de e-mail.
  • Usar o backup para "recuperar a lista". Restaurar contas apagadas para uma campanha contorna o pedido da pessoa.
  • Prometer exclusão imediata de tudo. Os backups continuam existindo, e a promessa fica falsa.

Checklist

  • O registro de exclusões fica num banco ou serviço que não é restaurado junto com o principal.
  • A função de exclusão é idempotente e cobre as cópias derivadas.
  • O roteiro de restauração inclui reaplicar exclusões antes de liberar o tráfego.
  • A retenção de backup tem um número definido e esse número está no aviso à pessoa.
  • O ensaio de restauração testa uma conta apagada depois do backup.
  • Backups não são usados para outra finalidade além de recuperação.

Faça a pergunta no próximo ensaio: se você restaurar o backup de ontem, a conta apagada hoje volta?

Avalie este artigo

Referência: https://www.gov.br/anpd/pt-br