db-migration-safe は何をしますか?
SQLスキーママイグレーションを安全にします。このスキルは、マイグレーション内のロック/ブロッキングの危険性を検出し、安全でないDDLをダウンタイムゼロのマルチステップマイグレーションに書き換え、ロールバックを生成し、実際の実行を計画 → 検証 → 実行のワークフローで制御します。
Postgresは強力で決定論的なパスです。MySQLは正直なハイブリッド(references/mysql-catalog.md参照)であり、実質的に弱く、そのようにラベル付けされています。
このスキルは外部リンター(squawk、eugene)およびOSCツール(gh-ost/pt-osc)にシェルアウトします。これらをバンドルしてはいません。不足している場合は、references/tool-setup.mdに従ってインストールしてください。静的解析(squawk + eugene lint)はデータベース不要です。eugene traceおよびEXECUTEにはPostgresが必要です。
使用する場合/使用しない場合
使用する: DDL全般:ALTER TABLE、CREATE INDEX、ADD/DROP COLUMN、型変更、制約(NOT NULL、CHECK、UNIQUE、外部キー)、リネーム、バックフィル — 手書きSQLでもORM生成でも。使用しない: 純粋なSELECT/DMLレポートクエリ、または独自の適用ステップを持つ宣言型スキーマエンジン(例:Atlas) — これらは独自の安全パスを持っています。
安全契約(最初に読んでください)
- PLANおよびVALIDATEはユーザーのデータベースに一切触れません。これらは分析のみであり、自由に実行できます。
- **
eugene trace**は、一時的な使い捨てPostgres(または使い捨てクローン)に対してマイグレーションを実行し、デフォルトでロールバックします。本番環境には決して触れません。 - EXECUTEは実際のDDLを実際のデータベースに適用します。ユーザーが明示的にマイグレーションの適用/実行/出荷を依頼しない限り、EXECUTEを実行しないでください。 常に最初にドライランを行い、常に
lock_timeoutでラップし、トランザクション内でCONCURRENTLYを実行しないでください。EXECUTEはユーザーが実行する手順ガイダンスです — 自動実行スクリプトはありません。
自動呼び出しに関する注意:これは単一スキルであるため、そのフロントマターがファイル全体を管理します。PLAN/VALIDATEは自動トリガーしても安全なため、モデル呼び出しは有効のままにしています。EXECUTEフェーズは、上記のルールによって動作的にゲートされています。スキル全体をユーザートリガーのみにしたい場合は、フロントマターに
disable-model-invocation: trueを追加してください — その場合、分析にも明示的な呼び出しが必要になります。
PLAN
マイグレーションファイルに対して静的アナライザを実行します。データベースは不要です。
python3 scripts/analyze.py path/to/migration.sql --dialect postgres
# add --pg-version 13.0 to gate version-specific rules
# add --json for machine-readable output (CI)
analyze.pyはsquawkとeugene lintを実行し、両方を1つの判定結果に統合し、エラーレベルの発見がある場合は非ゼロで終了します(そのためCIゲートとしても機能します)。--dialect mysqlの場合、代わりに組み込みのInnoDB Online DDLヒューリスティックを適用します(無料の静的MySQLロックリンターは存在しません。references/mysql-catalog.md参照)。
統合された判定結果を読み取ります:
- 発見項目 — 各項目には
source(squawk/eugene/heuristic)、ルールID、level(error/warning)、行番号、メッセージ、回避策があります。 - 解釈 — ルールIDを
references/squawk-rules.mdおよびreferences/eugene-hints.mdを介して平易な意味にマッピングします。 - テーブルサイズ — 静的リンターは100行と10万行を区別できません。
scripts/table_size.sqlで実際のサイズをプローブします(DBアクセスが必要)。「遅い」操作が実際に問題かどうかを判断する前に:psql "$DATABASE_URL" -v tablename=public.orders -f scripts/table_size.sql - トレースにエスカレート — 判定結果にテーブル/インデックスの再書き込み(E5、E10)、カバリングインデックスなしのFK(E15)、または実際の期間が行数に依存するロックが含まれる場合。
traceは実際のロックモードとその保持時間を観測します:python3 scripts/trace.py path/to/migration.sql # 一時的な一時サーバー python3 scripts/trace.py path/to/migration.sql --host db.staging --port 5432 \ --user app --database app # 使い捨てクローン
次に、ユーザー向けに3つの成果物を生成します:
- リスクレポート(判定結果を平易な言葉で)、
- 安全な書き換え(次のセクション)、および
- 生成されたロールバック(
scripts/gen_rollback.py)。
安全な書き換え
すべてのエラー/警告発見項目について、安全でないステートメントをカタログ化されたダウンタイムゼロの書き換えに置き換えます。11の操作→書き換えの完全なテーブルはreferences/postgres-catalog.mdにあります(MySQL:references/mysql-catalog.md)。ほとんどの書き換えの背後にある2つのPostgresプリミティブ:
CONCURRENTLY—ACCESS EXCLUSIVE書き込みロックなしでインデックスを構築/削除します(トランザクション外で実行する必要があります)。NOT VALID→VALIDATE CONSTRAINT— 制約を2フェーズで追加し、コストのかかる全テーブル検証が弱いSHARE UPDATE EXCLUSIVEロックを取得するようにし、書き込みをブロックしないようにします。
クイックリファレンス(正確なマルチステップSQLとPGバージョンのニュアンスについてはカタログを参照):
| 安全でない | 安全な書き換え |
|---|---|
CREATE INDEX | CREATE INDEX CONCURRENTLY(トランザクション外) |
ADD COLUMN ... NOT NULL [DEFAULT] | NULL許容列 → デフォルト設定 → バッチバックフィル → CHECK経由でNOT NULL |
SET NOT NULL | ADD CHECK (c IS NOT NULL) NOT VALID → VALIDATE → SET NOT NULL → チェック削除 |
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 | expand-contract(新しい列 → 二重書き込み → バックフィル → スワップ → 削除) |
| 列/テーブルのリネーム | expand-contract、または互換性VIEW |
バックフィルUPDATE | 短いトランザクションでの境界付きバッチ、スロットル制御 |
書き換えられたステートメントを新しいファイル(例:migration.safe.sql)に書き込み、再検証できるようにします。
ロールバック
(書き換えられた)DDLから逆マイグレーションを生成します:
python3 scripts/gen_rollback.py migration.safe.sql > migration.rollback.sql
gen_rollback.pyは、認識されたすべてのカタログ化操作の逆を出力します(ADD COLUMN→DROP COLUMN、CREATE INDEX CONCURRENTLY→DROP INDEX CONCURRENTLY、…)。出力をレビューします:
- 不可逆操作(
DROP COLUMN、DROP TABLE、型の狭小化)はDDLだけでは復元できません。ジェネレーターはそれらをマークし、順方向マイグレーションが最初に実行しなければならないバックアップテーブルSQL(<table>_<col>_backup_<date>)とベストエフォートのリストアを出力します。決して黙って間違った逆を出力することはありません。 - 不明なステートメント形状に対しては、推測ではなく明示的な
manual rollback required: <reason>コメントを生成します。
VALIDATE
これがゲートです。書き換えられたマイグレーションに対してアナライザを再実行します:
python3 scripts/analyze.py migration.safe.sql --dialect postgres
エラーレベルの発見がゼロで合格した場合のみ先に進みます(警告は.squawk.toml / -- eugene: ignoreで設定可能)。それでもエラーがフラグされる場合は、安全な書き換えに戻ってループします。書き換えによって取得されるロックが変更された場合は、traceを再実行します。
EXECUTE(ゲートあり — ユーザートリガーのみ)
ユーザーが明示的にマイグレーションの適用を依頼しない限り、このフェーズに入らないでください。
ロックガードレール付きで検証済みマイグレーションを適用します。最初にドライランを実行します。
- すべてのロックに制限を設定。 ブロッキングステートメントをラップし、長時間トランザクションの背後でキューイングしてすべてのトラフィックを停止させるのではなく、すぐに失敗するようにします:
SET lock_timeout = '5s'; SET statement_timeout = '0'; -- または特定の操作に対する適切な上限 CONCURRENTLYはトランザクション外で実行。CREATE/DROP INDEX CONCURRENTLYをBEGIN/COMMITブロックやトランザクショナルマイグレーションランナーの中に入れないでください。同時実行ビルドが失敗した後は、再試行する前に残ったINVALIDインデックスを削除してください。- 2フェーズ制約。
NOT VALIDステップを実行し、コミットさせてから、別のステートメントでVALIDATE CONSTRAINTを実行します。 - バッチバックフィル。 個別の短いトランザクションで境界付きチャンクで更新し、バッチ間に短いスリープを入れてレプリケーションとautovacuumが追いつけるようにします:
-- 影響行が0になるまで繰り返す UPDATE t SET col = expr WHERE id IN (SELECT id FROM t WHERE col IS NULL ORDER BY id LIMIT 5000) - ドライラン — シーケンス全体を
scripts/trace.py経由で一時サーバー/クローンに対して最後にもう一度実行し、passed_all_checksを確認してから、ターゲットに適用します。
MySQLの場合、ALGORITHM=…, LOCK=…を強制して、サーバーが静かにCOPY再構築にフォールバックする代わりにエラーにするようにし、大規模な書き換えはgh-ost/pt-oscに委任します — references/mysql-catalog.mdを参照してください。
リファレンスインデックス
references/postgres-catalog.md— 11の安全でない操作→安全な書き換え(中核知識)。references/mysql-catalog.md— InnoDB Online DDLマトリックス + OSC委任(弱いパス)。references/squawk-rules.md— squawkルールIDとそれぞれが検出するもの。references/eugene-hints.md— eugene E1–E15 / W12–W14とその回避策。references/tool-setup.md— squawk / eugene / gh-ostのインストール;一時的なPostgres。
スクリプト
scripts/analyze.py— squawk + eugene lint(またはMySQLヒューリスティック)を統合し、1つの判定結果を出力します。エラーレベルの発見時に非ゼロで終了します。scripts/trace.py—eugene trace(一時サーバーまたはクローン)を実行し、実際のロックレポートを出力します。scripts/gen_rollback.py— 逆マイグレーションを生成します;不可逆操作をフラグします。scripts/table_size.sql—pg_class.reltuplesサイズプローブ。
すべてのスクリプトはPython 3標準ライブラリのみを使用 — pip installは不要です。