CommunitySchreiben & Editierengithub.com

db-migration-safe

Ausfallsichere SQL-Migrationen als Agenten-Fähigkeit (Skill): Erkenne Lock-Gefahren, schreibe unsicheres DDL in mehrstufige Migrationen um, generiere Rollbacks und steuere die Ausführung über einen Plan → Prüfen → Ausführen-Workflow (primär Postgres, verwendet squawk + eugene).

Was ist db-migration-safe?

Diese Fähigkeit (Skill) macht SQL-Schema-Migrationen sicher, indem sie Sperr- und Blockierrisiken in Migrationsdateien erkennt, unsicheres DDL in ausfallsichere mehrschrittige Migrationen umschreibt, automatisch Rollbacks generiert und die Ausführung über einen expliziten 'Plan → Prüfen → Ausführen'-Workflow steuert. PostgreSQL wird vollständig unterstützt; MySQL wird als Hybrid mit eingeschränkteren Sicherheitsgarantien behandelt (siehe `references/mysql-catalog.md`). Der Skill ruft externe Linter (squawk, eugene) und Tools (gh-ost/pt-osc) auf – diese müssen separat installiert werden. Die Analyse (PLAN, VALIDATE) benötigt keine Datenbank; `eugene trace` verwendet eine flüchtige Wegwerf-Postgres-Instanz. Die Ausführung (EXECUTE) wird durch Verhaltensregeln geschützt: `lock_timeout`, `CONCURRENTLY` außerhalb von Transaktionen, zweiphasige Constraints und batched Backfills. Der Skill eignet sich für jedes DDL (ALTER TABLE, CREATE INDEX, Constraints usw.) und sollte nicht für reine SELECT/DML-Reporting-Queries oder deklarative Schema-Engines wie Atlas verwendet werden.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add alexbobkovv/db-migration-safe

Installed? Explore more Schreiben & Editieren skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Was macht db-migration-safe?

SQL-Schema-Migrationen sicher machen. Diese Fähigkeit erkennt Sperr-/Blockierrisiken in einer Migration, schreibt unsicheres DDL in ausfallsichere mehrschrittige Migrationen um, generiert ein Rollback und steuert die tatsächliche Ausführung über einen Plan → Prüfen → Ausführen-Workflow.

Postgres ist der starke, deterministische Pfad. MySQL ist ein ehrlicher Hybrid (siehe references/mysql-catalog.md) — materiell schwächer und als solcher gekennzeichnet.

Diese Fähigkeit greift auf externe Linter (squawk, eugene) und OSC-Tools (gh-ost/pt-osc) zurück. Sie bindet sie nicht ein. Fehlen sie, installiere sie gemäß references/tool-setup.md. Statische Analyse (squawk + eugene lint) benötigt keine DB; eugene trace und EXECUTE benötigen Postgres.

Wann verwenden / wann NICHT verwenden

Verwende für jedes DDL: ALTER TABLE, CREATE INDEX, ADD/DROP COLUMN, Typänderungen, Constraints (NOT NULL, CHECK, UNIQUE, Fremdschlüssel), Umbenennungen und Backfills — ob handgeschriebenes SQL oder von einem ORM generiert. Nicht für reine SELECT/DML- Reporting-Queries oder für deklarative Schema-Engines, die ihren eigenen Anwendungsschritt besitzen (z.B. Atlas) — diese haben ihren eigenen Sicherheitspfad.

Sicherheitsvertrag (zuerst lesen)

  • PLAN und VALIDATE berühren nie die Datenbank des Benutzers. Sie sind reine Analyse und können frei ausgeführt werden.
  • eugene trace führt die Migration gegen eine flüchtige Wegwerf-Postgres (oder einen Wegwerf-Klon) aus und führt standardmäßig einen Rollback durch. Es berührt niemals die Produktion.
  • EXECUTE wendet echtes DDL auf eine echte Datenbank an. Führe EXECUTE niemals aus, es sei denn, der Benutzer bittet explizit darum, die Migration anzuwenden / auszuführen / zu versenden. Führe immer zuerst einen Trockenlauf durch, umschließe immer mit lock_timeout, führe CONCURRENTLY niemals innerhalb einer Transaktion aus. EXECUTE ist eine verfahrenslenkende Anleitung, die der Benutzer steuert – es gibt kein Auto-Ausführ-Skript.

Hinweis zur Autoinvocation: Dies ist eine einzelne Fähigkeit, daher regelt ihr Frontmatter die gesamte Datei. PLAN/VALIDATE sind sicher als Autotrigger, daher ist die Modell-Invocation aktiviert. Die EXECUTE-Phase wird verhaltensorientiert durch die obige Regel geschützt. Falls du die gesamte Fähigkeit nur benutzergesteuert ausführen möchtest, füge disable-model-invocation: true zum Frontmatter hinzu – dann ist auch für die Analyse eine explizite Aufforderung erforderlich.


PLAN

Führe den statischen Analyse auf der Migrationsdatei aus. Keine Datenbank erforderlich.

python3 scripts/analyze.py path/to/migration.sql --dialect postgres
# füge --pg-version 13.0 hinzu, um versionsspezifische Regeln zu aktivieren
# füge --json für maschinenlesbare Ausgabe (CI) hinzu

analyze.py führt squawk und eugene lint aus, normalisiert beide zu einem Urteil und beendet sich mit einem Exit-Code ungleich Null, wenn ein Fehler-Finding vorliegt (dient daher auch als CI-Gate). Für --dialect mysql werden stattdessen die integrierten InnoDB Online-DDL- Heuristiken angewendet (es gibt keinen kostenlosen statischen MySQL-Lock-Linter; siehe references/mysql-catalog.md).

Lese das zusammengeführte Urteil:

  1. Findings — jedes hat eine source (squawk/eugene/heuristic), eine Regel-ID, ein level (error/warning), die Zeile, die Meldung und einen Workaround.
  2. Interpretation — ordne Regel-IDs über references/squawk-rules.md und references/eugene-hints.md einer verständlichen Bedeutung zu.
  3. Tabellengröße — statische Linter können 100 Zeilen nicht von 100k unterscheiden. Überprüfe die tatsächliche Größe mit scripts/table_size.sql (benötigt DB-Zugriff), bevor du entscheidest, ob eine „langsame“ Operation tatsächlich ein Problem darstellt:
    psql "$DATABASE_URL" -v tablename=public.orders -f scripts/table_size.sql
    
  4. Eskalation zum Trace, wenn das Urteil eine mögliche Tabellen-/Index-Neuschreibung (E5, E10), einen FK ohne Abdeckungsindex (E15) oder einen Lock betrifft, dessen tatsächliche Dauer von der Zeilenanzahl abhängt. trace beobachtet den tatsächlichen Lock-Modus und wie lange er gehalten wird:
    python3 scripts/trace.py path/to/migration.sql            # flüchtiger Temp-Server
    python3 scripts/trace.py path/to/migration.sql --host db.staging --port 5432 \
        --user app --database app                              # Wegwerf-Klon
    

Erstelle dann drei Artefakte für den Benutzer:

  • einen RISIKOBERICHT (das Urteil, in verständlicher Sprache),
  • eine SICHERE NEUSCHREIBUNG (nächster Abschnitt) und
  • ein generiertes ROLLBACK (scripts/gen_rollback.py).

Sichere Neuschreibungen

Ersetze für jedes Fehler-/Warnungs-Finding die unsichere Anweisung durch die katalogisierte ausfallsichere Neuschreibung. Die vollständige Tabelle der 11 Operationen → Neuschreibungen befindet sich in references/postgres-catalog.md (MySQL: references/mysql-catalog.md). Die beiden Postgres-Primitive, die hinter fast jeder Neuschreibung stehen:

  • CONCURRENTLY — erstelle/lösche Indizes ohne einen ACCESS EXCLUSIVE-Schreib-Lock (muss außerhalb einer Transaktion ausgeführt werden).
  • NOT VALID → VALIDATE CONSTRAINT — füge eine Einschränkung in zwei Phasen hinzu, sodass die teure vollständige Tabellenvalidierung nur einen schwachen SHARE UPDATE EXCLUSIVE-Lock benötigt anstatt Schreibvorgänge zu blockieren.

Kurzreferenz (das genaue mehrschrittige SQL und die PG-Versionsnuancen findest du im Katalog):

UnsicherSichere Neuschreibung
CREATE INDEXCREATE INDEX CONCURRENTLY (außerhalb einer Txn)
ADD COLUMN ... NOT NULL [DEFAULT]nullable Spalte → Standard setzen → Batch-Backfill → NOT NULL per CHECK
SET NOT NULLADD CHECK (c IS NOT NULL) NOT VALID → VALIDATE → SET NOT NULL → Check löschen
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 (neue Spalte → Dual-Write → Backfill → Tausch → Löschen)
Spalten-/TabellenumbenennungExpand-Contract oder Kompatibilitäts-VIEW
Backfill UPDATEBegrenzte Batches in kurzen Transaktionen, gedrosselt

Schreibe die neugeschriebenen Anweisungen in eine neue Datei (z.B. migration.safe.sql), damit sie erneut validiert werden kann.

Rollback

Generiere die umgekehrte Migration aus dem (neugeschriebenen) DDL:

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

gen_rollback.py gibt die Umkehrung jeder erkannten katalogisierten Operation aus (ADD COLUMN→DROP COLUMN, CREATE INDEX CONCURRENTLY→DROP INDEX CONCURRENTLY, …). Überprüfe die Ausgabe:

  • Nicht umkehrbare Operationen (DROP COLUMN, DROP TABLE, Typeinschränkung) können nicht allein aus DDL wiederhergestellt werden. Der Generator markiert sie und gibt das Backup-Tabellen-SQL aus, das die Vorwärts-Migration zuerst ausführen muss (<tabelle>_<spalte>_backup_<datum>), plus eine bestmögliche Wiederherstellung. Er gibt niemals eine stillschweigend falsche Umkehrung aus.
  • Unbekannte Anweisungsformen erzeugen einen expliziten Kommentar manual rollback required: <grund>, keine Vermutung.

VALIDATE

Dies ist das Gate. Führe die Analyse erneut auf der neugeschriebenen Migration aus:

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

Fahre nur fort, wenn sie mit null Fehler-Level-Findings durchläuft (Warnungen sind über .squawk.toml / -- eugene: ignore konfigurierbar). Wenn immer noch Fehler gemeldet werden, kehre zu Sichere Neuschreibungen zurück und wiederhole den Vorgang. Führe trace erneut aus, wenn die Neuschreibung geändert hat, welche Locks verwendet werden.

EXECUTE (geschützt — nur benutzergesteuert)

Beginne diese Phase nur, wenn der Benutzer explizit darum gebeten hat, die Migration anzuwenden.

Wende die validierte Migration mit Locking-Schutzmechanismen an. Führe zuerst einen Trockenlauf durch.

  1. Begrenze jeden Lock. Umschließe blockierende Anweisungen, sodass sie schnell fehlschlagen, anstatt sich hinter einer langen Transaktion anzustellen und den gesamten Verkehr zu blockieren:
    SET lock_timeout = '5s';
    SET statement_timeout = '0';  -- oder eine sinnvolle Obergrenze für die spezifische Operation
    
  2. CONCURRENTLY läuft außerhalb einer Transaktion. Setze CREATE/DROP INDEX CONCURRENTLY nicht in einen BEGIN/COMMIT-Block oder einen transaktionalen Migrations-Runner. Lösche nach einem fehlgeschlagenen Concurrent-Build den übriggebliebenen INVALID-Index, bevor du es erneut versuchst.
  3. Zweiphasige Constraints. Führe den NOT VALID-Schritt aus, lass ihn committen, und führe dann VALIDATE CONSTRAINT in einer separaten Anweisung aus.
  4. Batch-Backfills. Aktualisiere in begrenzten Batches in separaten kurzen Transaktionen, mit einer kurzen Pause zwischen den Batches, damit Replikation und Autovacuum mithalten können:
    -- wiederholen, bis 0 Zeilen betroffen
    UPDATE t SET col = expr
    WHERE id IN (SELECT id FROM t WHERE col IS NULL ORDER BY id LIMIT 5000)
    
  5. Führe einen Trockenlauf der gesamten Sequenz gegen den flüchtigen/Klon über scripts/trace.py ein letztes Mal durch, bestätige passed_all_checks, und wende dann auf das Ziel an.

Für MySQL erzwinge ALGORITHM=…, LOCK=…, damit der Server einen Fehler ausgibt, anstatt stillschweigend auf einen COPY-Rebuild zurückzufallen, und delegiere große Neuschreibungen an gh-ost/pt-osc — siehe references/mysql-catalog.md.


Referenzindex

  • references/postgres-catalog.md — die 11 unsicheren Operationen → sichere Neuschreibungen (Kernwissen).
  • references/mysql-catalog.md — InnoDB Online-DDL-Matrix + OSC-Delegation (schwächerer Pfad).
  • references/squawk-rules.md — Squawk-Regel-IDs und was jede abfängt.
  • references/eugene-hints.md — Eugene E1–E15 / W12–W14 und ihre Workarounds.
  • references/tool-setup.md — Installation von squawk / eugene / gh-ost; flüchtige Postgres.

Skripte

  • scripts/analyze.py — orchestriert squawk + eugene lint (oder MySQL-Heuristiken) → ein Urteil. Exit-Code ungleich Null bei Fehler-Level-Findings.
  • scripts/trace.py — führt eugene trace aus (flüchtiger Temp-Server oder Klon) → echter Lock-Bericht.
  • scripts/gen_rollback.py — generiert die umgekehrte Migration; kennzeichnet nicht umkehrbare Operationen.
  • scripts/table_size.sql — pg_class.reltuples-Größenprüfung.

Alle Skripte verwenden nur die Python 3-Standardbibliothek — kein pip install.

Verwandte Skills