ALTER TABLE em tabela de milhões de linhas segura a produção: locks, fila de queries, timeout em cascata. O padrão expand-contract divide toda mudança de schema em fases compatíveis com versões antigas e novas do código convivendo. A regra vem antes das fases: mudança de schema e código dependente nunca entram no mesmo release. Código velho precisa sobreviver ao schema novo, e código novo precisa sobreviver ao schema velho durante toda a transição.
Fase 1: expand
Só mudanças aditivas entram nesta fase. Adicione coluna nullable, crie tabela nova, construa índices com CREATE INDEX CONCURRENTLY para não travar escritas no Postgres. O backfill dos dados existentes roda em lotes paginados por chave primária, 5 mil rows por lote; transação longa gera replication lag e bloat, então lote pequeno com pausa entre eles mantém o cluster saudável. Build de índice que falha deixa invalid index para trás; a limpeza exige DROP INDEX CONCURRENTLY antes da nova tentativa.
Fase 2: transição com dual-write
O código novo escreve nos dois formatos durante a convivência: cada operação grava na estrutura antiga e na nova. O caminho de leitura migra aos poucos atrás de feature flag, tabela por tabela. Um consistency checker roda à noite comparando as duas representações e reporta divergência enquanto dá tempo de corrigir. A fase dura dias ou semanas, o bastante para cobrir rollbacks, workers atrasados e cache velho.
Fase 3: contract
Telemetria provou zero leitura na estrutura antiga? Remova colunas deprecadas e caminhos de escrita velhos. Drop de constraint pede passos escalonados entre deploys: primeiro sai o código que lê, depois a constraint, cada etapa num deploy próprio com rollback trivial.
Quais operações pedem coreografia própria?
- Mudança de tipo de coluna: coluna nova, dual-write, backfill, switch de leitura, drop da original
- Rename de coluna: mesma sequência, com alias via view cobrindo a janela de transição
- Unique constraint nova: dedupe dos dados antes, índice único CONCURRENTLY depois
Ferramentas que seguram a operação
Squawk faz lint de migrations SQL no CI e aponta operações que travam tabela. Apps Rails instalam strong_migrations, que bloqueia mudanças perigosas em runtime e sugere a alternativa segura. MySQL em escala pesada roda gh-ost ou pt-online-schema-change: ambos copiam a tabela com triggers e fazem swap atômico no final. Feche o ciclo rodando migrations no CI contra snapshot de tamanho produtivo; ALTER que passa em dev com 100 rows explode em staging com 50 milhões.
Curtiu o conteúdo?
Construo produtos web e soluções com IA do jeito certo — arquitetura sólida, código sustentável e entrega real.
Vamos conversar