Migração de banco sem tela de manutenção: entendendo o padrão expand-and-contract

Uma alteração de banco parece simples enquanto cabe numa linha de SQL. Renomear uma coluna, adicionar uma restrição ou mudar o formato de um dado vira outro problema quando duas versões da aplicação podem rodar ao mesmo tempo. Durante um deploy gradual, o código antigo ainda espera o schema antigo enquanto o novo já tenta usar o próximo.

O padrão expand-and-contract, também chamado de parallel change, troca a virada única por etapas compatíveis. Primeiro o sistema aprende o formato novo sem perder o antigo. Depois os dados e consumidores migram. Só quando ninguém depende da estrutura anterior ela é removida.

O deploy perigoso é o tudo ou nada

Imagine uma tabela customers com a coluna name. O novo produto quer separar given_name e family_name. Renomear ou apagar name no mesmo deploy que altera o código cria uma janela frágil. Se uma instância antiga receber tráfego, ela quebra. Se for necessário rollback, a versão anterior encontra um banco que não entende mais.

A compatibilidade temporária é o que preserva o rollback. Enquanto o schema aceita os dois contratos, a equipe consegue recuar a aplicação sem precisar desfazer uma migração destrutiva às pressas.

1. Expandir

Na expansão, adicione a nova estrutura de forma compatível. Colunas novas costumam começar aceitando NULL ou um valor seguro, porque os registros existentes ainda não foram migrados. O código antigo continua usando name.

ALTER TABLE customers ADD COLUMN given_name text;
ALTER TABLE customers ADD COLUMN family_name text;

O impacto operacional do comando depende do banco, da versão, do tamanho da tabela e da alteração. Algumas operações exigem locks ou reescrita. Verifique o plano na documentação do seu SGBD e ensaie com um volume próximo ao real; “é só um ALTER TABLE” já derrubou deploy suficiente para merecer respeito.

2. Migrar

A próxima versão passa a escrever nos campos antigos e novos, ou usa uma camada de compatibilidade centralizada. Depois um backfill copia registros históricos em lotes pequenos. Métricas acompanham quantos registros ainda não possuem o formato novo e se as duas representações divergem.

Dual write parece trivial, mas pode falhar pela metade. Se duas gravações independentes forem feitas fora da mesma transação, uma pode confirmar e a outra não. Quando possível, mantenha a atualização atômica no banco. Se a migração atravessa serviços ou armazenamentos diferentes, trate reconciliação e idempotência como partes do desenho.

Quando o backfill termina, o código novo começa a ler as colunas novas, ainda mantendo a escrita compatível por um período. Essa fase de observação permite comparar resultados, testar rollback e encontrar consumidores esquecidos, como jobs, relatórios e integrações.

3. Contrair

A contração acontece somente depois que telemetria e busca no código mostram que ninguém lê ou escreve o campo antigo. Primeiro remova o caminho antigo da aplicação. Em outro deploy, retire a coluna. Restrições como NOT NULL podem ser aplicadas quando os dados já foram validados.

Essa distância entre “parou de usar” e “apagou” parece lenta, mas é o preço barato. A alternativa é descobrir um consumidor oculto depois que a informação já não existe.

O que observar em cada fase

  • Tempo de lock e crescimento de latência durante mudanças de schema.
  • Taxa e duração do backfill, com pausa possível em caso de pressão.
  • Divergências entre representações antigas e novas.
  • Versões da aplicação ainda acessando o contrato antigo.
  • Plano de rollback válido antes de cada passo destrutivo.

Zero downtime não significa risco zero

Sistemas enormes usam snapshots, replicação e troca de tráfego para mover dados sem interrupção perceptível. Um CRUD comum não precisa copiar a arquitetura da Stripe, mas pode aproveitar o princípio: separar preparação, cópia, validação e corte torna a mudança observável e reversível.

O padrão também serve para APIs, eventos e mudanças de formato. Sempre que produtores e consumidores não atualizam no mesmo instante, expandir o contrato antes de remover o antigo reduz coordenação e sustos.

A tela de manutenção continua válida quando o contexto aceita parada e o custo de engenharia não se justifica. Expand-and-contract entra quando disponibilidade, múltiplas instâncias ou rollback importam. Ele não faz a migração desaparecer; transforma uma aposta grande em uma sequência de passos verificáveis.

Fontes