1

Como Reverter Migrations com Segurança Sem Perder Dados em Produção

O problema real: migrations não são simétricas

Uma migration que adiciona uma coluna é trivial de reverter: basta dropar a coluna. Uma migration que dropa uma coluna, renomeia uma tabela ou altera o tipo de um campo não tem caminho de volta automático. Os dados que existiam antes da migration desaparecem no momento em que ela roda.

A maioria dos ORMs gera o método down() como espelho do up(), e isso cria uma falsa sensação de segurança. O down() reverte a estrutura do schema, não os dados. Se você dropou a coluna legacy_email e precisa reverter, o down() recria a coluna vazia. Os 200 mil emails que estavam lá sumiram.

Esse post trata de como planejar migrations que permitem rollback seguro, como executar reversões quando o plano falhou, e quando a resposta correta é não reverter.

Se você ainda não tem um fluxo maduro de migrations, vale ler o post sobre versionamento de schema com TypeORM e o guia de migrations seguras com Prisma antes de continuar.

Classificação de risco: nem toda migration precisa do mesmo cuidado

Antes de decidir a estratégia de rollback, classifique a migration:

Tipo de operaçãoRisco de perda de dadosReversível automaticamente?Estratégia recomendada
Adicionar coluna nullableNenhumSim (drop column)Rollback direto
Adicionar índiceNenhumSim (drop index)Rollback direto
Remover colunaAltoNão (dados perdidos)Backup da coluna antes
Renomear coluna/tabelaMédioParcial (renomear de volta)Deploy em fases
Alterar tipo de colunaAltoParcial (cast pode falhar)Coluna shadow + backfill
Dropar tabelaCríticoNãoDump da tabela antes
Migrar dados entre tabelasAltoNão (depende da lógica)Migration reversível com tabela auxiliar

A regra é: se a migration destrói informação, o rollback precisa de uma etapa extra antes da execução. Não depois.

A técnica de coluna shadow para alterações destrutivas

Renomear uma coluna ou alterar seu tipo parece simples. Na prática, qualquer deploy que muda o nome de uma coluna quebra queries da versão anterior da aplicação que ainda estão rodando. Se você usa estratégias de deploy como blue-green ou rolling updates, a versão antiga e a nova coexistem por alguns segundos ou minutos.

A solução é o deploy em três fases com coluna shadow:

Fase 1: adicionar a coluna nova sem remover a antiga

-- Fase 1: cria a nova coluna e copia os dados existentes
-- POR QUE nullable: a aplicação antiga ainda escreve na coluna original,
-- e a nova coluna não pode ter constraint NOT NULL antes do backfill completar
ALTER TABLE users ADD COLUMN display_name VARCHAR(255);

UPDATE users SET display_name = username WHERE display_name IS NULL;

Fase 2: aplicação escreve nas duas colunas (dual-write)

// src/repositories/user.repository.ts
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

async function updateUsername(userId: string, newName: string) {
  // POR QUE dual-write: garante que ambas as colunas ficam sincronizadas
  // durante o período de transição entre deploys
  await prisma.user.update({
    where: { id: userId },
    data: {
      username: newName,
      displayName: newName,
    },
  });
}

Fase 3: remover a coluna antiga (só depois que nenhuma versão antiga está rodando)

-- Fase 3: só execute quando TODAS as instâncias rodam a versão nova
-- POR QUE esperar: se uma instância antiga ainda lê "username",
-- dropar a coluna causa erro 500 nessa instância
ALTER TABLE users DROP COLUMN username;

Se algo der errado na fase 2 ou 3, o rollback é trivial: a coluna original ainda existe com os dados intactos. Você reverte o deploy da aplicação e a coluna shadow fica órfã até o próximo ciclo


Leia o artigo completo em https://www.vivodecodigo.com.br/backend/reverter-migrations-seguranca-sem-perder-dados-producao

Carregando publicação patrocinada...