CommunityEscrita e Ediçãogithub.com

db-migration-safe

Habilidade para migrações SQL seguras com tempo de inatividade zero: detecta riscos de bloqueio, reescreve DDL inseguro, gera rollbacks e controla a execução (foco em Postgres, utiliza squawk + eugene).

O que é db-migration-safe?

Esta habilidade de agente (skill) foi projetada para tornar migrações de esquema SQL seguras, com foco principal em Postgres, oferecendo suporte híbrido ao MySQL. Ela detecta automaticamente operações DDL que podem causar bloqueios ou falhas, reescreve comandos inseguros em sequências de múltiplas etapas com zero downtime, gera scripts de rollback reversíveis e impõe um fluxo de trabalho de três fases: PLAN (planejar), VALIDATE (validar) e EXECUTE (executar). O uso de ferramentas externas como `squawk`, `eugene` e `gh-ost`/`pt-osc` é externo, exigindo instalação manual. A análise estática (PLAN e VALIDATE) não toca no banco de dados de produção, enquanto a fase EXECUTE é estritamente controlada, com salvaguardas como `lock_timeout` e execução fora de transações, garantindo que o usuário explícito e intencionalmente autorize a aplicação das alterações.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add alexbobkovv/db-migration-safe

Installed? Explore more Escrita e Edição skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

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 trace executa 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 execute CONCURRENTLY dentro 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: true ao 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:

  1. Constatações — cada uma tem uma source (squawk/eugene/heuristic), um id de regra, um level (error/warning), a linha, a mensagem e uma solução alternativa.
  2. Interpretação — mapeie ids de regra para significado simples através de references/squawk-rules.md e references/eugene-hints.md.
  3. 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
    
  4. 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. trace observa 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 bloqueio ACCESS EXCLUSIVE de 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 fraco SHARE UPDATE EXCLUSIVE em 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):

InseguroReescrita segura
CREATE INDEXCREATE 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 NULLADD 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 UNIQUECREATE UNIQUE INDEX CONCURRENTLY → ADD CONSTRAINT ... USING INDEX
ALTER COLUMN ... TYPEexpandir-contratar (nova coluna → escrita dupla → backfill → troca → remover)
renomear coluna/tabelaexpandir-contratar, ou VIEW de compatibilidade
backfill UPDATElotes 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.

  1. 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
    
  2. CONCURRENTLY é executado fora de uma transação. Não coloque CREATE/DROP INDEX CONCURRENTLY em um bloco BEGIN/COMMIT ou em um executor de migração transacional. Após uma construção concorrente falha, remova o índice INVALID restante antes de tentar novamente.
  3. Restrições em duas fases. Execute a etapa NOT VALID, deixe-a confirmar, então VALIDATE CONSTRAINT em uma instrução separada.
  4. 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)
    
  5. Dry-run toda a sequência contra o ambiente efêmero/clone via scripts/trace.py uma última vez, confirme passed_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 — executa eugene 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 tamanho pg_class.reltuples.

Todos os scripts são apenas da biblioteca padrão Python 3 — sem pip install.

Habilidades Relacionadas