Um guia prático para migrações permanentes em bancos de dados
O termo migrações permanentes geralmente se refere a alterações de esquema ou dados que são aplicadas de forma definitiva e irreversível no banco. Na prática, significa uma mudança que você não precisa (e não quer) reverter com um rollback, porque ela já é o estado final desejado. A diferença entre migração permanente e migração reversível é mais importante do que a maioria dos desenvolvedores leva a sério.
O que são migrações permanentes e quando usá-las
Uma migração permanente é um arquivo de alteração de schema que modifica tabela, coluna, índice ou constraint de maneira que não admite desfazer sem perda de dados. Você as usa quando precisa adicionar uma coluna NOT NULL, remover uma coluna obsoleta, alterar o tipo de uma coluna existente ou reestruturar partições. O cenário mais comum é quando um sistema evolui e certas estruturas antigas não fazem mais sentido. O problema é que muitas equipes tratam todas as migrações como reversíveis por padrão, o que gera arquivos enormes com up e down contendo cada mudança duas vezes. Isso infla o repositório, aumenta o tempo de execução e cria oportunidades de erro. Migrações permanentes devem ser escritas apenas para mudanças que você realmente pretende manter.
A ferramenta padrão: DBMail / Flyway / Liquibase
Para migrações permanentes, eu recomendo o Flyway quando o projeto usa Java ou qualquer stack que se beneficie de versionamento strito de schema. O Flyway permite marcar migrações como permanentes usando o atributo appliedChecksum e a flag flyway.outOfOrder desativada, garantindo que cada script seja executado exatamente uma vez. Em projetos Python, o Alembic faz o mesmo com a diretiva down_revision e o conceito de "permanent migration" quando a migração não implementa um método down(). No PostgreSQL, migrações permanentes funcionam bem com scripts SQL brutos executados via psql -f ou integrados ao CI/CD. A chave é usar transações BEGIN; ... COMMIT; apenas quando a operação for segura dentro de uma transação. Alterações como ALTER TABLE ... ADD COLUMN com USING não podem ser abortadas facilmente em tabelas grandes, então às vezes é melhor executar sem transação e confiar no próprio mecanismo de recuperação do banco.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Pitfalls comuns que ninguém avisa
O erro mais frequente em migrações permanentes é subestimar o tempo de bloqueio. Quando você roda ALTER TABLE big_table ADD COLUMN new_column TEXT em uma tabela com dezenas de milhões de linhas, o PostgreSQL pode travar a escrita por minutos ou horas dependendo do lock_mode. A solução que eu sempre recomendo é usar o padrão "add column nullable, populate depois, alterar para NOT NULL". Isso quebra uma migração permanente em três etapas menores que causam muito menos impacto. Outro problema recorrente é a divergência entre o arquivo de migração e o estado real do banco. Se alguém aplica uma migração manualmente em produção e depois roda o migration tool novamente, você terá um erro de checksum ou duplicate migration. A correção é simples: verifique sempre o schema_migrations (ou flyway_schema_history) antes de rodar novas migrações, e nunca, jamais, edite um arquivo de migração que já foi aplicado em nenhum ambiente. Edite o nome, crie um novo arquivo, e pronto.
Um caso real que aprendi na marra
Em 2022, lidamos com uma migração permanente de uma coluna integer para bigint em uma tabela de transações com aproximadamente 80 milhões de linhas. O script original era um simples ALTER TABLE transactions ALTER COLUMN amount TYPE bigint. Rodamos em staging e funcionou em 4 minutos. Em produção, com o volume de escritas ativas, o banco entrou em deadlock recorrente e a operação travou o serviço por 23 minutos. O pior: o lock não era esperado porque o plano de execução mudou dependendo das estatísticas de uso naquele momento. A solução que funcionou foi substituir a abordagem direta por um padrão de colunas duplas: criamos a nova coluna amount_new bigint, populamos em lotes de 50 mil linhas usando um cursor, atualizamos as triggers e views para apontar para a nova coluna, e só então removemos a antiga. Isso reduziu o tempo de bloqueio de 23 minutos para cerca de 90 segundos de locks curtos, distribuídos ao longo de 12 minutos de processamento em background. O tempo total foi maior, mas a disponibilidade do serviço foi mantida.
Quando migrações permanentes são a escolha errada
Migrações permanentes não funcionam bem em cenários de múltiplos escritores concorrentes que dependem de schema estável, especialmente em bancos NoSQL ou em microserviços que compartilham tabela. Se você tem três serviços lendo e escrevendo a mesma tabela e um deles faz uma migração permanente que quebra a compatibilidade com os outros dois, o sistema vai falhar de forma intermitente e difícil de diagnosticar. Nesse caso, prefira migrations reversíveis ou use o padrão de feature flags para controlar a adoção gradual da mudança. Também não recomendo migrações permanentes quando o ambiente de homologação não espelha fielmente o de produção. Se o número de linhas, a distribuição de dados ou as restrições de foreign key são diferentes, o comportamento da migração pode variar drasticamente. Teste sempre com dados realistas ou use ferramentas de geração de dados sintéticos com a mesma cardinalidade e distribuição do ambiente produtivo.
Checklist antes de rodar migrações permanentes
Antes de aplicar qualquer migração permanente em produção, garanta que os seguintes pontos estão cobertos. O backup do schema atual está disponível e testado para restore. O script de migração foi executado com sucesso em um ambiente idêntico ao production, com dados em escala realista. O rollback manual foi documentado, mesmo que a migração seja considerada permanente. A janela de manutenção foi comunicada às equipes afetadas. Monitoramento de latência e lock foi configurado no banco durante a execução. E, finalmente, a equipe de suporte foi informada sobre possíveis erros temporários que podem aparecer nos logs após a aplicação. Migrações permanentes são uma ferramenta poderosa quando usadas com consciência dos seus limites. A maioria dos problemas que vejo em produção vem de pessoas que tratam migração como algo mecânico, sem considerar o impacto real no banco rodando sob carga. O conhecimento prático vem de ver essas migrações falharem e aprender a contornar cada falha.