O que db-migration-safe faz?
Torne migrações de esquema SQL seguras. Esta habilidade detecta riscos de bloqueio/locking em uma migração, reescreve DDL inseguro em migrações de múltiplas etapas com tempo de inatividade zero, gera um rollback e controla a execução em um fluxo de trabalho planejar → validar → executar.
Postgres é o caminho forte e determinístico. MySQL é um híbrido honesto (veja
references/mysql-catalog.md) — materialmente mais fraco, e rotulado como tal.
Esta habilidade terceiriza para linters externos (squawk, eugene) e ferramentas OSC
(gh-ost/pt-osc). Ela não os inclui. Se estiverem faltando, instale conforme
references/tool-setup.md. A análise estática (squawk + eugene lint) não precisa de banco de dados;
eugene trace e EXECUTE precisam de um Postgres.
Quando usar / quando NÃO usar
Use para qualquer DDL: ALTER TABLE, CREATE INDEX, ADD/DROP COLUMN, alterações de tipo,
restrições (NOT NULL, CHECK, UNIQUE, chaves estrangeiras), renomeações e backfills —
seja SQL escrito à mão ou gerado por um ORM. Não use para consultas puras de SELECT/DML
relatórios, ou para mecanismos de esquema declarativos que possuem sua própria etapa de aplicação
(ex. Atlas) — eles têm seu próprio caminho de segurança.
Contrato de segurança (leia primeiro)
- PLAN e VALIDATE nunca tocam no banco de dados do usuário. São apenas análise e podem ser executados livremente.
eugene traceexecuta a migração contra um Postgres temporário descartável (ou um clone descartável) e reverte por padrão. Nunca toca na produção.- EXECUTE aplica DDL real a um banco de dados real. Nunca execute EXECUTE a menos que o usuário
explicitamente peça para aplicar / executar / enviar a migração. Sempre faça um dry-run primeiro, sempre
envolva em
lock_timeout, nunca executeCONCURRENTLYdentro de uma transação. EXECUTE é orientação procedural que o usuário conduz — não há script de execução automática.
Nota sobre auto-invocação: esta é uma habilidade única, então seu frontmatter governa todo o arquivo. PLAN/VALIDATE são seguros para acionamento automático, portanto a invocação do modelo é deixada ativada. A fase EXECUTE é controlada comportamentalmente pela regra acima. Se preferir tornar toda a habilidade acionada apenas pelo usuário, adicione
disable-model-invocation: trueao frontmatter — exigirá invocação explícita também para análise.
PLAN
Execute o analisador estático no arquivo de migração. Nenhum banco de dados é necessário.
python3 scripts/analyze.py caminho/para/migracao.sql --dialect postgres
# adicione --pg-version 13.0 para filtrar regras específicas de versão
# adicione --json para saída legível por máquina (CI)
analyze.py executa squawk e eugene lint, normaliza ambos em um único veredito e
sai com código diferente de zero se houver alguma constatação de nível de erro (portanto, serve como um gate de CI).
Para --dialect mysql ele aplica as heurísticas internas do InnoDB Online DDL (não existe
linter de bloqueio estático gratuito para MySQL; veja references/mysql-catalog.md).
Leia o veredito mesclado:
- Constatações — cada uma tem uma
source(squawk/eugene/heuristic), um id de regra, umlevel(error/warning), a linha, a mensagem e uma solução alternativa. - Interpretação — mapeie ids de regra para significado simples através de
references/squawk-rules.mdereferences/eugene-hints.md. - Tamanho da tabela — linters estáticos não podem distinguir 100 linhas de 100k. Meça o tamanho real com
scripts/table_size.sql(precisa de acesso ao banco de dados) antes de decidir se uma operação "lenta" é realmente um problema:psql "$DATABASE_URL" -v tablename=public.orders -f scripts/table_size.sql - Escalar para trace quando o veredito envolver uma possível reescrita de tabela/índice (E5,
E10), uma FK sem um índice de cobertura (E15), ou qualquer bloqueio cuja duração real dependa
da contagem de linhas.
traceobserva o modo de bloqueio real e por quanto tempo é mantido:python3 scripts/trace.py caminho/para/migracao.sql # servidor temporário efêmero python3 scripts/trace.py caminho/para/migracao.sql --host db.staging --port 5432 \ --user app --database app # clone descartável
Em seguida, produza três artefatos para o usuário:
- um RELATÓRIO DE RISCO (o veredito, em linguagem simples),
- uma REESCRITA SEGURA (próxima seção), e
- um ROLLBACK gerado (
scripts/gen_rollback.py).
Reescrevendo de forma segura
Para cada constatação de erro/aviso, substitua a instrução insegura pela reescrita catalogada sem tempo de inatividade. A tabela completa de 11 operações → reescritas está em
references/postgres-catalog.md (MySQL: references/mysql-catalog.md). As duas primitivas
Postgres por trás de quase todas as reescritas:
CONCURRENTLY— cria/remove índices sem um bloqueioACCESS EXCLUSIVEde escrita (deve ser executado fora de uma transação).NOT VALID→VALIDATE CONSTRAINT— adiciona uma restrição em duas fases para que a validação completa da tabela (custosa) utilize um bloqueio fracoSHARE UPDATE EXCLUSIVEem vez de bloquear escritas.
Referência rápida (veja o catálogo para o SQL exato de múltiplas etapas e nuances de versão PG):
| Inseguro | Reescrita segura |
|---|---|
CREATE INDEX | CREATE INDEX CONCURRENTLY (fora da transação) |
ADD COLUMN ... NOT NULL [DEFAULT] | coluna anulável → definir padrão → backfill em lotes → NOT NULL via CHECK |
SET NOT NULL | ADD CHECK (c IS NOT NULL) NOT VALID → VALIDATE → SET NOT NULL → remover check |
ADD FOREIGN KEY | ... NOT VALID → VALIDATE CONSTRAINT |
ADD CHECK | ... CHECK (...) NOT VALID → VALIDATE CONSTRAINT |
ADD UNIQUE | CREATE UNIQUE INDEX CONCURRENTLY → ADD CONSTRAINT ... USING INDEX |
ALTER COLUMN ... TYPE | expandir-contratar (nova coluna → escrita dupla → backfill → troca → remover) |
| renomear coluna/tabela | expandir-contratar, ou VIEW de compatibilidade |
backfill UPDATE | lotes limitados em transações curtas, com throttling |
Escreva as instruções reescritas em um novo arquivo (ex. migration.safe.sql) para que possa ser
revalidado.
Rollback
Gere a migração reversa a partir do DDL (reescrito):
python3 scripts/gen_rollback.py migration.safe.sql > migration.rollback.sql
gen_rollback.py emite o inverso de cada operação catalogada reconhecida
(ADD COLUMN→DROP COLUMN, CREATE INDEX CONCURRENTLY→DROP INDEX CONCURRENTLY, …).
Revise a saída:
- Operações irreversíveis (
DROP COLUMN,DROP TABLE, estreitamento de tipo) não podem ser restauradas apenas com DDL. O gerador as marca e emite o SQL de backup de tabela que a migração forward deve executar primeiro (<tabela>_<coluna>_backup_<data>) além de uma restauração de melhor esforço. Nunca emite uma inversão silenciosamente incorreta. - Formatos de instrução desconhecidos produzem um comentário explícito
rollback manual necessário: <motivo>, não um palpite.
VALIDATE
Este é o gate. Execute novamente o analisador na migração reescrita:
python3 scripts/analyze.py migration.safe.sql --dialect postgres
Só prossiga quando passar com zero constatações de nível de erro (avisos são
configuráveis via .squawk.toml / -- eugene: ignore). Se ainda sinalizar erros, volte
para Reescritas Seguras e repita. Execute trace novamente se a reescrita alterou quais bloqueios são adquiridos.
EXECUTE (controlado — apenas acionado pelo usuário)
Não entre nesta fase a menos que o usuário explicitamente tenha pedido para aplicar a migração.
Aplique a migração validada com salvaguardas de bloqueio. Faça um dry-run primeiro.
- Limite cada bloqueio. Envolva instruções bloqueantes para que falhem rapidamente em vez de entrar em fila
atrás de uma transação longa e travar todo o tráfego:
SET lock_timeout = '5s'; SET statement_timeout = '0'; -- ou um limite sensato para a operação específica CONCURRENTLYé executado fora de uma transação. Não coloqueCREATE/DROP INDEX CONCURRENTLYem um blocoBEGIN/COMMITou em um executor de migração transacional. Após uma construção concorrente falha, remova o índiceINVALIDrestante antes de tentar novamente.- Restrições em duas fases. Execute a etapa
NOT VALID, deixe-a confirmar, entãoVALIDATE CONSTRAINTem uma instrução separada. - Backfills em lotes. Atualize em blocos limitados em transações curtas separadas, com um
pequeno intervalo entre os lotes para permitir que replicação e autovacuum acompanhem:
-- repita até que 0 linhas sejam afetadas UPDATE t SET col = expr WHERE id IN (SELECT id FROM t WHERE col IS NULL ORDER BY id LIMIT 5000) - Dry-run toda a sequência contra o ambiente efêmero/clone via
scripts/trace.pyuma última vez, confirmepassed_all_checks, então aplique ao destino.
Para MySQL, force ALGORITHM=…, LOCK=… para que o servidor emita erro em vez de cair silenciosamente
em uma reconstrução COPY, e delegue grandes reescritas a gh-ost/pt-osc — veja
references/mysql-catalog.md.
Índice de referências
references/postgres-catalog.md— as 11 operações inseguras → reescritas seguras (conhecimento central).references/mysql-catalog.md— matriz InnoDB Online DDL + delegação OSC (caminho mais fraco).references/squawk-rules.md— ids de regras do squawk e o que cada uma captura.references/eugene-hints.md— eugene E1–E15 / W12–W14 e suas soluções alternativas.references/tool-setup.md— instalação do squawk / eugene / gh-ost; Postgres efêmero.
Scripts
scripts/analyze.py— orquestra squawk + eugene lint (ou heurísticas MySQL) → um veredito. Sai com código diferente de zero em constatações de nível de erro.scripts/trace.py— executaeugene trace(servidor temporário efêmero ou clone) → relatório de bloqueio real.scripts/gen_rollback.py— gera a migração reversa; sinaliza operações irreversíveis.scripts/table_size.sql— sonda de tamanhopg_class.reltuples.
Todos os scripts são apenas da biblioteca padrão Python 3 — sem pip install.