Database Migrations no Node.js permitem evoluir o schema do banco de forma versionada, reproduzível e auditável. Cada migration descreve uma mudança, como criar tabela, adicionar coluna, índice ou constraint.
Em produção, uma migration precisa considerar locks, volume de dados, compatibilidade entre versões da aplicação e rollback operacional. Um comando que funciona em uma base vazia pode bloquear uma tabela grande por minutos.
Estrutura básica
export async function up(db) {
await db.query(`
CREATE TABLE pedidos (
id uuid PRIMARY KEY,
cliente_id uuid NOT NULL,
status text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
)
`);
}
export async function down(db) {
await db.query('DROP TABLE pedidos');
}A ferramenta registra quais migrations já foram aplicadas.
Migration imutável
Depois que uma migration chega a um ambiente compartilhado, não edite o arquivo para alterar o passado. Crie uma nova migration corretiva. Isso mantém histórico consistente entre desenvolvimento, staging e produção.
Expand and Contract
Para mudanças incompatíveis, use etapas:
- adicionar nova coluna ou tabela sem remover a antiga;
- publicar aplicação que escreve nos dois formatos ou suporta ambos;
- migrar dados existentes;
- trocar leituras para o novo formato;
- parar escrita antiga;
- remover estrutura antiga em deploy posterior.
Essa técnica permite deploy sem exigir atualização simultânea de todas as instâncias.
Adicionar coluna
ALTER TABLE clientes
ADD COLUMN locale text;Primeiro aceite NULL. Preencha em lotes, valide e só depois adicione NOT NULL.
Backfill em lotes
UPDATE clientes
SET locale = 'pt-BR'
WHERE id IN (
SELECT id
FROM clientes
WHERE locale IS NULL
ORDER BY id
LIMIT 5000
);Execute lotes com pausa e métricas para não saturar banco e replicas.
Constraints seguras
No PostgreSQL, uma constraint pode ser adicionada como NOT VALID e validada depois.
ALTER TABLE pedidos
ADD CONSTRAINT status_valido
CHECK (status IN ('rascunho', 'confirmado', 'cancelado'))
NOT VALID;
ALTER TABLE pedidos
VALIDATE CONSTRAINT status_valido;Isso separa criação da validação dos dados existentes.
Índices concorrentes
CREATE INDEX CONCURRENTLY idx_pedidos_cliente
ON pedidos (cliente_id);Essa operação não pode ser executada dentro de uma transação comum. A ferramenta de migrations precisa suportar migrations não transacionais.
Renomear coluna
Renomear diretamente quebra aplicações antigas. Prefira adicionar nova coluna, manter compatibilidade e remover a anterior depois.
Alterar tipo
Mudanças de tipo podem reescrever a tabela. Para bases grandes, crie nova coluna, faça backfill, sincronize escritas e troque gradualmente.
Migrations e deploy
Execute migrations em uma etapa controlada, não em todas as réplicas da aplicação ao mesmo tempo. Use um job único com lock ou mecanismo da plataforma.
Lock de migration
A ferramenta deve impedir duas execuções concorrentes. Advisory locks do PostgreSQL são uma opção.
SELECT pg_advisory_lock(927364);
Garanta liberação mesmo em falha.
Transação
Migrations DDL podem usar transação quando suportado. Se qualquer comando falhar, a mudança inteira é revertida. Operações concorrentes e backfills longos podem exigir outra estratégia.
Timeouts
SET lock_timeout = '3s';
SET statement_timeout = '15min';Falhar rapidamente é melhor que bloquear produção indefinidamente.
Compatibilidade reversa
Durante um rolling deploy, versões antiga e nova coexistem. A migration deve permitir que ambas funcionem até a conclusão da troca.
Rollback
Nem toda migration possui rollback seguro. Remover coluna destrói dados; um down automático pode piorar o incidente. Documente estratégia de recuperação, backup e forward fix.
Dados e schema
Separe alterações rápidas de schema de backfills pesados. Um worker controlado pode migrar dados com checkpoint, retry e limitação de taxa.
Seeds
Seeds criam dados iniciais ou de desenvolvimento. Não misture seeds mutáveis com migrations de schema. Dados obrigatórios de produção podem usar migrations idempotentes específicas.
Testes
- aplicar todas as migrations em banco vazio;
- atualizar a partir de snapshot antigo;
- executar duas vezes sem duplicação;
- testar rollback quando seguro;
- verificar aplicação antiga e nova;
- medir locks e duração;
- validar constraints e índices;
- testar interrupção durante backfill.
CI
No CI, crie banco limpo, aplique migrations e rode testes de integração. Também compare schema esperado e detecte arquivos fora de ordem.
Observabilidade
Registre migration, início, fim, duração, linhas afetadas e erro. Em mudanças longas, publique progresso do backfill e carga do banco.
Segurança
A credencial de migration pode exigir permissões maiores que a aplicação. Use segredo separado, acesso restrito e auditoria.
Erros comuns
- editar migration já aplicada;
- adicionar
NOT NULLem tabela grande sem preparação; - criar índice bloqueante em horário crítico;
- renomear coluna em um único deploy;
- rodar migration em todas as instâncias;
- sem lock timeout;
- backfill gigante em uma transação;
- rollback destrutivo automático;
- não testar rolling deploy.
Checklist
- mudança é compatível com versões antigas;
- locks foram analisados;
- índices usam estratégia adequada;
- backfill possui lotes e checkpoint;
- migrations são imutáveis;
- execução é única;
- há plano de recuperação;
- métricas e logs estão disponíveis.
Conclusão
Database Migrations no Node.js transformam mudanças de banco em um processo previsível. O principal desafio não é executar SQL, mas manter compatibilidade e reduzir impacto operacional.
Use expand and contract, índices concorrentes, constraints graduais e backfills em lotes. Combine com Transações PostgreSQL no Node.js e testes de integração.
Consulte a documentação ALTER TABLE do PostgreSQL e a documentação CREATE INDEX.


