DevGuideTK
← Trilha Backend

Migrações de esquema

Como alterar tabelas em produção sem parar o sistema e sem perder dados.

As decisões sobre migrações são quatro: como escrevê-las, quando aplicá-las, como fazer cada tipo de mudança sem parar o sistema e como mudar dados em volume.

Como escrever

AbordagemComo funcionaVantagensCustos
SQL versionadoArquivos .sql numerados, aplicados em ordem (Flyway, golang-migrate, dbmate)Controle total do SQL; o que está no arquivo é o que rodaEscrever tudo à mão
Gerada pelo ORMO ORM compara o modelo com o banco e gera a migração (Alembic, EF Core, Prisma, Django)Rápida; acompanha o modeloO SQL gerado não sabe de travas, CONCURRENTLY ou volume
DeclarativaDescreve o estado desejado; a ferramenta calcula a diferença (Atlas, Skeema)Sem migrações acumuladasMudanças de dados e etapas finas ficam difíceis

A combinação mais comum é gerar com o ORM e revisar o SQL antes do commit. O gerador acerta a estrutura e não sabe de produção: ele cria o índice sem CONCURRENTLY, troca o tipo da coluna numa tabela de 50 milhões de linhas e apaga a coluna que o código antigo ainda lê.

Por linguagem, as ferramentas mais usadas:

LinguagemFerramentas
TypeScriptPrisma Migrate, Drizzle Kit, Knex, node-pg-migrate
PythonAlembic (SQLAlchemy), migrações do Django
Gogolang-migrate, goose, Atlas
JavaFlyway, Liquibase
C#EF Core Migrations, DbUp

Quando aplicar

MomentoComo funcionaCuidados
Etapa do deploy, antes do código novoO pipeline roda as migrações e só então libera a versão novaA migração precisa ser compatível com o código antigo, que ainda está no ar
Na inicialização da aplicaçãoCada instância aplica as migrações ao subirVárias instâncias sobem juntas e disputam; a ferramenta precisa de uma trava (Flyway e EF Core têm)
À mão, por uma pessoaAlguém roda o comandoSó para mudanças excepcionais, com registro

Aplicar como etapa separada do deploy é o mais previsível: uma migração demorada ou que falhou não deixa instâncias da aplicação presas na inicialização. E a regra de ouro vale para qualquer momento: cada migração funciona com a versão do código que está no ar e com a próxima.

Receitas para cada mudança

MudançaSem cuidadoSeguro
Acrescentar coluna—Sem padrão, ou com padrão constante; ambos instantâneos no PostgreSQL 11 ou mais novo
Coluna com padrão calculadoReescreve a tabelaAcrescentar sem padrão, preencher em lotes, depois definir o padrão
Tornar NOT NULLLê a tabela sob travaCHECK (col IS NOT NULL) NOT VALID, VALIDATE CONSTRAINT, depois SET NOT NULL
Criar índiceBloqueia escritasCREATE INDEX CONCURRENTLY, fora de transação
Chave estrangeiraLê as duas tabelas sob travaADD CONSTRAINT ... NOT VALID, depois VALIDATE CONSTRAINT
Trocar tipo (int para bigint)Reescreve a tabelaColuna nova, gravação dupla, cópia em lotes, troca e remoção da antiga
Renomear coluna ou tabelaQuebra o código antigoExpandir e contrair, em vários deploys
Remover colunaQuebra o código antigoPrimeiro um deploy que para de usar a coluna; depois a remoção

E em todas: lock_timeout curto, com nova tentativa. No teste do nível Conceito, a migração sem limite fez uma leitura simples esperar 5,3 segundos; com lock_timeout = '1s', a espera ficou em 0,7 segundo.

O lock_timeout combina com o statement_timeout, que limita a duração do comando inteiro. Um protege contra a espera pela trava; o outro, contra uma migração que demora mais que o previsto segurando a trava.

Mudar dados em volume

Preencher uma coluna nova em 50 milhões de linhas com um único UPDATE gera uma transação enorme: trava as linhas por muito tempo, infla a tabela e atrasa as réplicas. O caminho é em lotes:

UPDATE pedidos SET canal = 'site'
 WHERE id IN (SELECT id FROM pedidos WHERE canal IS NULL LIMIT 5000);
-- repetir até não sobrar nenhuma linha, com uma pausa entre os lotes
CuidadoPor quê
Lotes de alguns milhares de linhas, cada um na sua transaçãoTravas curtas e progresso salvo
Pausa entre os lotesAs réplicas acompanham, e a aplicação continua atendendo
RetomávelSe parar no meio, recomeça de onde estava (a condição canal IS NULL faz isso)
Fora da migração de esquemaMigrações de dados longas não devem prender o deploy; um job separado é mais seguro

Ferramentas de apoio

FerramentaO que faz
Verificadores de migração (squawk, strong_migrations)Leem o SQL e apontam operações perigosas: índice sem CONCURRENTLY, troca de tipo, NOT NULL direto
Ferramentas de alteração online (pg-osc, gh-ost e pt-online-schema-change no MySQL)Fazem a mudança numa cópia da tabela e trocam no fim, para mudanças que reescreveriam a tabela
Ensaio com dados reaisRodar a migração numa cópia do banco de produção para medir o tempo antes

Resumo da decisão

  1. Migrações versionadas no repositório, geradas ou escritas, sempre revisadas em SQL.
  2. Etapa separada do deploy, compatível com o código antigo e o novo.
  3. lock_timeout curto e nova tentativa em toda migração.
  4. CONCURRENTLY para índices e NOT VALID com validação para restrições.
  5. Expandir e contrair para renomear, trocar tipo e remover.
  6. Dados em lotes, retomáveis, fora da migração de esquema.
  7. Colunas explícitas nas consultas, em vez de SELECT *.

O próximo nível mostra o executor com lock_timeout e nova tentativa nas cinco linguagens, testado com a tabela ocupada.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Organizar as migrações do meu projeto

    Quero organizar as migrações de banco do meu projeto.
    
    Pergunte qual linguagem, ORM e banco eu uso, como é o deploy e quantas instâncias da aplicação rodam.
    
    Recomende, explicando o porquê:
    1. A ferramenta de migração.
    2. Gerar pelo ORM ou escrever em SQL, e como revisar.
    3. Quando aplicar: etapa do deploy ou inicialização da aplicação.
    4. Como lidar com várias instâncias subindo ao mesmo tempo.
    5. Um verificador automático de migrações perigosas na integração contínua.
  • Planejar uma mudança arriscada

    Preciso fazer uma mudança de esquema arriscada numa tabela grande em produção.
    
    Pergunte qual é a mudança, o banco e a versão, o tamanho da tabela, o volume de leituras e escritas e como é o deploy.
    
    Monte comigo um plano com:
    1. As etapas, cada uma compatível com o código no ar.
    2. O SQL de cada etapa, com lock_timeout e as receitas sem bloqueio.
    3. A migração de dados em lotes, se houver.
    4. Como medir o tempo antes, numa cópia.
    5. Como voltar atrás em cada etapa.
  • Migrar dados em volume

    Preciso preencher ou transformar dados em milhões de linhas, com o sistema no ar.
    
    Pergunte qual banco eu uso, o tamanho da tabela, se há réplicas e qual é a transformação.
    
    Explique:
    1. Por que um único UPDATE é perigoso.
    2. Como dividir em lotes retomáveis, e o tamanho do lote.
    3. Como não atrasar as réplicas nem pesar na aplicação.
    4. Onde rodar: migração, job separado ou script.
    5. Como conferir que terminou e que os dados estão certos.