CommunityRédaction et éditiongithub.com

alexbobkovv/db-migration-safe

Migrations SQL sans temps d'arrêt sous forme de compétence Agent : détection des risques de verrouillage, réécriture des DDL dangereuses, génération automatique de rollbacks, exécution contrôlée (Postgres d'abord, s'appuie sur squawk + eugene).

Qu'est-ce que db-migration-safe ?

Cette compétence transforme toute migration de schéma SQL (écrite à la main ou générée par un ORM) en une opération sans interruption de service. Elle analyse statiquement les fichiers DDL avec les outils externes `squawk` et `eugene` pour identifier les risques de verrous bloquants, puis réécrit automatiquement les instructions dangereuses en séquences multi-étapes (CREATE INDEX CONCURRENTLY, NOT VALID → VALIDATE CONSTRAINT, etc.). Elle génère également la migration inverse et un rollback de secours. Le workflow se déroule en trois phases : **PLAN** (analyse sans base), **VALIDATE** (vérification de la version réécrite) et **EXECUTE** (application réelle, strictement déclenchée par l'utilisateur). La base de référence est Postgres, où l'analyse est déterministe et complète. MySQL est supporté via des heuristiques InnoDB et un catalogage plus limité, clairement marqué comme une voie plus faible. La compétence nécessite l'installation préalable de `squawk`, `eugene` et éventuellement `gh-ost`/`pt-osc` ; elle ne les inclut pas.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add alexbobkovv/db-migration-safe

Installed? Explore more Rédaction et édition skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait db-migration-safe ?

Rendez les migrations de schéma SQL sans risque. Cette compétence détecte les dangers de verrouillage/blocage dans une migration, réécrit les DDL dangereuses en migrations multi-étapes sans temps d'arrêt, génère un rollback, et verrouille l'exécution réelle derrière un workflow planifier → valider → exécuter.

Postgres est le chemin fort et déterministe. MySQL est un hybride honnête (voir references/mysql-catalog.md) — matériellement plus faible, et étiqueté comme tel.

Cette compétence utilise des appels shell aux analyseurs externes (squawk, eugene) et aux outils OSC (gh-ost/pt-osc). Elle ne les inclut pas. S'ils manquent, installez-les selon references/tool-setup.md. L'analyse statique (squawk + eugene lint) ne nécessite pas de base de données ; eugene trace et EXECUTE nécessitent un Postgres.

Quand l'utiliser / quand ne PAS l'utiliser

À utiliser pour tout DDL : ALTER TABLE, CREATE INDEX, ADD/DROP COLUMN, changements de type, contraintes (NOT NULL, CHECK, UNIQUE, clés étrangères), renommages et backfills — que le SQL soit écrit à la main ou généré par un ORM. Ne pas utiliser pour les requêtes pures SELECT/DML de rapport, ou pour les moteurs de schéma déclaratifs qui gèrent leur propre étape d'application (ex. Atlas) — ceux-ci ont leur propre voie de sécurité.

Contrat de sécurité (lire d'abord)

  • PLAN et VALIDATE ne touchent jamais à la base de données de l'utilisateur. Ils sont uniquement de l'analyse et peuvent être exécutés librement.
  • eugene trace exécute la migration contre un Postgres éphémère jetable (ou un clone jetable) et annule par défaut. Il ne touche jamais à la production.
  • EXECUTE applique du vrai DDL à une vraie base de données. N'exécutez jamais EXECUTE sauf si l'utilisateur demande explicitement d'appliquer/exécuter/envoyer la migration. Faites d'abord un essai à blanc, enveloppez toujours dans lock_timeout, n'exécutez jamais CONCURRENTLY dans une transaction. EXECUTE est un guide procédural que l'utilisateur pilote — il n'y a pas de script d'auto-exécution.

Note sur l'auto-invocation : il s'agit d'une compétence unique, donc son frontmatter gère tout le fichier. PLAN/VALIDATE sont sûrs à déclencher automatiquement, donc l'invocation par le modèle est laissée activée. La phase EXECUTE est verrouillée comportementalement par la règle ci-dessus. Si vous préférez que toute la compétence soit déclenchée uniquement par l'utilisateur, ajoutez disable-model-invocation: true dans le frontmatter — cela nécessitera une invocation explicite même pour l'analyse.


PLAN

Exécutez l'analyseur statique sur le fichier de migration. Aucune base de données n'est requise.

python3 scripts/analyze.py chemin/vers/migration.sql --dialect postgres
# ajoutez --pg-version 13.0 pour filtrer les règles spécifiques à une version
# ajoutez --json pour une sortie lisible par machine (CI)

analyze.py exécute squawk et eugene lint, normalise les deux en un seul verdict, et se termine avec un code non nul si une constatation de niveau erreur est présente (ce qui en fait aussi une porte CI). Pour --dialect mysql, il applique les heuristiques InnoDB Online DDL intégrées à la place (aucun analyseur de verrouillage MySQL statique gratuit n'existe ; voir references/mysql-catalog.md).

Lisez le verdict fusionné :

  1. Constatations — chacune a une source (squawk/eugene/heuristic), un identifiant de règle, un niveau (error/warning), la ligne, le message et une solution de contournement.
  2. Interprétation — mappez les identifiants de règle à leur sens simple via references/squawk-rules.md et references/eugene-hints.md.
  3. Taille de la table — les analyseurs statiques ne peuvent pas distinguer 100 lignes de 100k. Sondez la taille réelle avec scripts/table_size.sql (nécessite un accès à la base) avant de décider si une opération « lente » est réellement un problème :
    psql "$DATABASE_URL" -v tablename=public.orders -f scripts/table_size.sql
    
  4. Passez à la trace lorsque le verdict implique une possible réécriture de table/index (E5, E10), une FK sans index de couverture (E15), ou tout verrou dont la durée réelle dépend du nombre de lignes. trace observe le mode de verrouillage réel et combien de temps il est maintenu :
    python3 scripts/trace.py chemin/vers/migration.sql            # serveur temporaire éphémère
    python3 scripts/trace.py chemin/vers/migration.sql --host db.staging --port 5432 \
        --user app --database app                              # clone jetable
    

Produisez ensuite trois artefacts pour l'utilisateur :

  • un RAPPORT DE RISQUE (le verdict, en langage clair),
  • une RÉÉCRITURE SÛRE (section suivante), et
  • un ROLLBACK généré (scripts/gen_rollback.py).

Réécritures sûres

Pour chaque constatation d'erreur/avertissement, remplacez l'instruction dangereuse par la réécriture sans temps d'arrêt cataloguée. La table complète des 11 opérations → réécritures se trouve dans references/postgres-catalog.md (MySQL : references/mysql-catalog.md). Les deux primitives Postgres derrière presque toutes les réécritures :

  • CONCURRENTLY — construire/supprimer des index sans verrou ACCESS EXCLUSIVE d'écriture (doit être exécuté en dehors d'une transaction).
  • NOT VALID → VALIDATE CONSTRAINT — ajouter une contrainte en deux phases afin que la validation coûteuse de toute la table prenne un verrou faible SHARE UPDATE EXCLUSIVE au lieu de bloquer les écritures.

Référence rapide (voir le catalogue pour le SQL exact multi-étapes et les nuances de version PG) :

DangereuxRéécriture sûre
CREATE INDEXCREATE INDEX CONCURRENTLY (hors txn)
ADD COLUMN ... NOT NULL [DEFAULT]colonne nullable → définir défaut → backfill par lots → NOT NULL via CHECK
SET NOT NULLADD CHECK (c IS NOT NULL) NOT VALID → VALIDATE → SET NOT NULL → drop 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 ... TYPEexpand-contract (nouvelle colonne → double écriture → backfill → échange → drop)
renommer colonne/tableexpand-contract, ou VIEW de compatibilité
backfill UPDATElots bornés dans des transactions courtes, limités

Écrivez les instructions réécrites dans un nouveau fichier (par ex. migration.safe.sql) afin qu'il puisse être revalidé.

Rollback

Générez la migration inverse à partir du DDL (réécrit) :

python3 scripts/gen_rollback.py migration.safe.sql > migration.rollback.sql

gen_rollback.py émet l'inverse de chaque opération cataloguée reconnue (ADD COLUMN→DROP COLUMN, CREATE INDEX CONCURRENTLY→DROP INDEX CONCURRENTLY, …). Vérifiez la sortie :

  • Les opérations irréversibles (DROP COLUMN, DROP TABLE, rétrécissement de type) ne peuvent pas être restaurées à partir du seul DDL. Le générateur les marque et émet le SQL de sauvegarde de table que la migration forward doit exécuter en premier (<table>_<col>_backup_<date>) plus une restauration au mieux. Il n'émet jamais d'inverse silencieusement erroné.
  • Les formes d'instruction inconnues produisent un commentaire explicite rollback manuel requis : <raison>, pas une supposition.

VALIDER

C'est la porte. Réexécutez l'analyseur sur la migration réécrite :

python3 scripts/analyze.py migration.safe.sql --dialect postgres

Ne procédez que lorsqu'elle passe avec zéro constatation de niveau erreur (les avertissements sont configurables via .squawk.toml / -- eugene: ignore). Si elle signale encore des erreurs, retournez à Réécritures sûres et bouclez. Réexécutez trace si la réécriture a changé les verrous pris.

EXÉCUTER (verrouillé — déclenché uniquement par l'utilisateur)

N'entrez pas dans cette phase sauf si l'utilisateur a explicitement demandé d'appliquer la migration.

Appliquez la migration validée avec des garde-fous de verrouillage. Faites d'abord un essai à blanc.

  1. Limitez chaque verrou. Enveloppez les instructions bloquantes pour qu'elles échouent rapidement au lieu de faire la queue derrière une longue transaction et de bloquer tout le trafic :
    SET lock_timeout = '5s';
    SET statement_timeout = '0';  -- ou une limite raisonnable pour l'opération spécifique
    
  2. CONCURRENTLY s'exécute en dehors d'une transaction. Ne mettez pas CREATE/DROP INDEX CONCURRENTLY dans un bloc BEGIN/COMMIT ou dans un exécuteur de migration transactionnel. Après une construction concurrente échouée, supprimez l'index INVALID restant avant de réessayer.
  3. Contraintes en deux phases. Exécutez l'étape NOT VALID, laissez-la valider, puis VALIDATE CONSTRAINT dans une instruction séparée.
  4. Backfills par lots. Mettez à jour par morceaux bornés dans des transactions courtes séparées, avec un court sommeil entre les lots pour laisser la réplication et l'autovacuum suivre :
    -- répéter jusqu'à 0 lignes affectées
    UPDATE t SET col = expr
    WHERE id IN (SELECT id FROM t WHERE col IS NULL ORDER BY id LIMIT 5000)
    
  5. Essai à blanc de la séquence entière contre l'éphémère/clone via scripts/trace.py une dernière fois, confirmez passed_all_checks, puis appliquez à la cible.

Pour MySQL, forcez ALGORITHM=…, LOCK=… afin que le serveur génère une erreur au lieu de se rabattre silencieusement sur une reconstruction COPY, et déléguez les grandes réécritures à gh-ost/pt-osc — voir references/mysql-catalog.md.


Index des références

  • references/postgres-catalog.md — les 11 opérations dangereuses → réécritures sûres (connaissances de base).
  • references/mysql-catalog.md — matrice InnoDB Online DDL + délégation OSC (voie plus faible).
  • references/squawk-rules.md — identifiants de règles squawk et ce que chacune détecte.
  • references/eugene-hints.md — eugene E1–E15 / W12–W14 et leurs solutions de contournement.
  • references/tool-setup.md — installation de squawk / eugene / gh-ost ; Postgres éphémère.

Scripts

  • scripts/analyze.py — orchestre squawk + eugene lint (ou heuristiques MySQL) → un seul verdict. Se termine avec un code non nul en cas de constatations de niveau erreur.
  • scripts/trace.py — exécute eugene trace (serveur temporaire éphémère ou clone) → rapport de verrouillage réel.
  • scripts/gen_rollback.py — génère la migration inverse ; signale les opérations irréversibles.
  • scripts/table_size.sql — sonde de taille pg_class.reltuples.

Tous les scripts sont en bibliothèque standard Python 3 uniquement — pas de pip install.

Skills associés