Community研究&データ分析github.com

PostgreSQL マイグレーション

ペアの up/down ファイル、モダンな型と制約、可逆的な変更、非ブロッキングインデックス、マイグレーター要件。

PostgreSQL マイグレーション とは?

このスキルは、PostgreSQL データベースの安全で効率的なマイグレーション戦略を定義します。主な特徴は、各マイグレーションが up/down のペアで構成され、down が up を完全にロールバックすることです。PostgreSQL 13 以降の `GENERATED ALWAYS AS IDENTITY`、`text` + `CHECK`、`timestamptz`、`jsonb` などのモダンな型と制約を推奨し、`CHECK` による長さ制限のベストプラクティスを提供します。特に本番環境のライブテーブルに対し、`CREATE INDEX CONCURRENTLY` (CIC) を使用して書き込みをブロックせずにインデックスを作成する方法を詳述し、トランザクション内で CIC を実行できない制約、失敗時の INVALID インデックス処理、`indcheckxmin` によるインデックス不使用問題の診断と解決法もカバーします。また、マイグレーターに要求すべき機能(トランザクション分離、並行実行ロック、個別マイグレーションの非トランザクションモードなど)も明示しています。

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/scary-production/claude-plugins/tree/HEAD/plugins/go-postgres/skills/postgres-migrations

Installed? Explore more 研究&データ分析 skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

PostgreSQL マイグレーション は何をしますか?

最重要ポイント. 各マイグレーションは up/down のペアであり、down は up を完全にロールバックします。マイグレーションは自身のトランザクション内で実行します。例外は1つだけ: すでにデータがあり書き込みが行われているテーブルへのインデックス作成 — CREATE INDEX CONCURRENTLY をトランザクションなしの別マイグレーションで、ファイルに1コマンドだけ記述します。

アサーションの検証は CHECKS.md を参照。

ファイル形式

migrations/
├── 20250524120000_create_users.up.sql
├── 20250524120000_create_users.down.sql
├── 20250525090000_add_users_phone.up.sql
└── 20250525090000_add_users_phone.down.sql
  • プレフィックスは UTC 時刻 YYYYMMDDHHmmss、名前は snake_case で簡潔な説明。
  • *.up.sql が適用、*.down.sql がロールバック。両方とも必須。
  • 適用済みのマイグレーションは編集せず、新しいものを作成すること。

型と制約

代わりにこちらを使う理由
bigserialbigint GENERATED ALWAYS AS IDENTITYSQL 標準; インポート時に ID を挿入する必要がある場合は BY DEFAULT を使用
varchar(n)text NOT NULL CHECK (length(x) <= n)制限はデータルールであり、変更が容易
timestamptimestamptz「壁時計」ではなく、時点を保存
integer フラグboolean
jsonjsonbバイナリ形式、インデックス、演算子
拡張機能の uuiduuid DEFAULT gen_random_uuid()PostgreSQL 13 以降に組み込み

テキストカラムの長さは、明らかに長いテキスト(説明、コメント)を除き、常に CHECK で制限すること。最大長が不明な場合は、ユーザーに確認すること。

データ制約
emailCHECK (length(email) <= 320)
名前CHECK (length(name) <= 255)
電話番号CHECK (length(phone) <= 50)
slug、コードCHECK (length(code) <= 100)
URLCHECK (length(url) <= 2048)
説明制限なし、または CHECK (length(bio) <= 10000)

テーブルテンプレート:

CREATE TABLE users (
    id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name       text        NOT NULL CHECK (length(name) <= 255),
    email      text        NOT NULL CHECK (length(email) <= 320),
    is_active  boolean     NOT NULL DEFAULT true,
    metadata   jsonb,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now()
);

CREATE UNIQUE INDEX ix_users_email ON users (email);

ロールバック

updown
CREATE TABLE tDROP TABLE IF EXISTS t
ALTER TABLE t ADD COLUMN cALTER TABLE t DROP COLUMN IF EXISTS c
CREATE INDEX ixDROP INDEX IF EXISTS ix
CREATE INDEX CONCURRENTLY ixDROP INDEX CONCURRENTLY IF EXISTS ix — こちらもトランザクションなし
CREATE TYPE t AS ENUM (...)DROP TYPE IF EXISTS t
ALTER TYPE t ADD VALUE 'v'PostgreSQL に DROP VALUE はない — 型を再作成するか、空の down にする
ALTER TABLE t ALTER COLUMN c SET NOT NULLALTER TABLE t ALTER COLUMN c DROP NOT NULL
ALTER TABLE t ADD CONSTRAINT cALTER TABLE t DROP CONSTRAINT IF EXISTS c

down に IF EXISTS を付けることで、ロールバックがべき等になります。

典型的な変更

デフォルト値付きカラム. 定数 DEFAULT の場合、テーブルは再書き込みされず(PostgreSQL 11+)、コマンドは瞬時に完了します。volatile なデフォルト(gen_random_uuid()、clock_timestamp())の場合、テーブル全体が排他ロックのもとで再書き込みされます。大規模なライブテーブルでは、デフォルトなしでカラムを追加し、バッチで埋めてください。

ALTER TABLE users ADD COLUMN role text NOT NULL DEFAULT 'user' CHECK (length(role) <= 50);

外部キー — 参照側のカラムにはすぐにインデックスを作成してください。PostgreSQL は自動で作成せず、インデックスがないと親の削除や JOIN がシーケンシャルスキャンになります。

CREATE TABLE orders (
    id      bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    user_id bigint NOT NULL REFERENCES users (id),
    total   numeric NOT NULL
);
CREATE INDEX ix_orders_user_id ON orders (user_id);

Enum への値追加. ALTER TYPE ... ADD VALUE はトランザクション内で動作しますが、新しい値はコミットまで使用できません(55P04 unsafe use of new value)。マイグレーターがトランザクション内でマイグレーションを実行する場合、値の追加とその使用(UPDATE、DEFAULT、CHECK)は 異なる マイグレーションで行ってください。

ALTER TYPE payment_method ADD VALUE 'bank_transfer';

down: DROP VALUE はありません。値に問題がなければ、コメント付きで down を空にしておきます。問題があれば、型を再作成(新しい型、ALTER COLUMN ... TYPE ... USING、古い型の削除)します。

ライブテーブルへのインデックス

すでにデータがあり書き込みが行われているテーブルでは、CREATE INDEX CONCURRENTLY (CIC) を使用します。通常の CREATE INDEX はビルド中ずっと書き込みをブロックします。新しいテーブルと同時に同じマイグレーションでインデックスを作成する場合は、通常の CREATE INDEX を使用します。データベースのサイズは関係ありません: 長時間実行中のトランザクションは、どのサイズでも新しいインデックスを壊します(後述)。

up:

CREATE INDEX CONCURRENTLY ix_orders_customer_id ON orders (customer_id);

down:

DROP INDEX CONCURRENTLY IF EXISTS ix_orders_customer_id;
  • トランザクションなしの個別マイグレーション、ファイルに1コマンドのみ。 トランザクションブロック内では CIC は禁止されています(「トランザクションブロック内では実行できません」)。1つのクエリとして送信された複数のコマンドは、PostgreSQL によって暗黙のトランザクションとして実行され、CIC は同じエラーで失敗します。
  • マイグレーターがトランザクションなしのマイグレーションをサポートしていない場合、CIC を通常のマイグレーションに入れないでください。 ユーザーにそのモードが必要であることを伝えるか(「マイグレーターに求めること」セクション参照)、CIC を別途実行し、マイグレーションを手動で記録してください。
  • CIC は、古いスナップショットを持つすべてのトランザクションの終了を待ちます。 他のテーブルを読んでいるだけのトランザクションでも同様です。マイグレーションに時間がかかる可能性があります。
  • 適用後、インデックスを確認してください。 CIC はトランザクション的ではありません: 障害が発生すると INVALID インデックスが残り、マイグレーション自体はそれを示しません。
SELECT c.relname, i.indisvalid, i.indcheckxmin
FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid
WHERE c.relname = 'ix_orders_customer_id';
  • indisvalid = false — DROP INDEX CONCURRENTLY IF EXISTS で削除して再作成するか、REINDEX INDEX CONCURRENTLY を実行します。
  • 失敗した CIC マイグレーションの再実行。 IF NOT EXISTS がない場合、INVALID インデックスに対して「リレーションは既に存在します」エラーで失敗します。IF NOT EXISTS があるとさらに悪い: コマンドは INVALID インデックスを黙ってスキップし、マイグレーションは「成功」します。したがって、CIC には IF NOT EXISTS を記述しないでください: 最初に無効なインデックスを削除してから、再実行します。

インデックスはあるのに、実行計画が Seq Scan

インデックスは有効なのに、プランナが使用しない場合 — indcheckxmin を確認してください(上のクエリ)。true の場合: インデックス作成前に開始されたトランザクションが生存している間、クエリはインデックスを使用しません。原因は UPDATE: テーブルに、インデックスが参照しない HOT バージョンチェーンが残っています。INSERT だけでは発生しません。インデックスを再作成したり、統計情報に原因を求めたりしないでください。古いトランザクションを特定します:

SELECT pid, xact_start, backend_xmin, state, left(query, 60) AS query
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
ORDER BY xact_start;

古いトランザクションの完了を待つか、終了させます(pg_terminate_backend(pid)) — ユーザーとの事前調整が必要です。リリース前に再現するには: 1つのセッションで repeatable read の開かれたトランザクションでテーブルを読み取り、別のセッションで行を UPDATE して CREATE INDEX を実行します。indcheckxmin が true になります。

マイグレーターに求めること

  • 各マイグレーションは自身のトランザクション、適用ログへの書き込みも含む: 失敗した場合は完全にロールバックされ、すでに適用済みのものはそのまま残ります。
  • 並行実行のロック(例: advisory lock): 同時に起動したレプリカが同じものを2回適用しないようにします。
  • 個別マイグレーションのトランザクションなしモード — ファイル内のマーク(例: 最初の行に -- migrate:no-transaction)。そのようなファイルはトランザクションを経ずに、1コマンドとして送信されます。代償: 適用とログへの書き込みはアトミックではなくなります。これらの間にプロセスがクラッシュした場合、再実行は「already exists」で失敗します。indisvalid を確認し、有効なインデックスがあれば、手動でログにマイグレーションを記録します。
  • データベースに接続せずにペアファイルを作成する機能。

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

scary-production/claude-plugins

Работа с PostgreSQL в Go через pgxpool (pgx v5) и avito-tech/go-transaction-manager: пул и устойчивый старт, структуры с тегами db и CollectRows, репозиторий через DefaultTrOrDB, транзакции через Do, propagation и savepoint, повтор транзакции при 40001/40P01/55P03, защита от SQL-инъекций, классификация ошибок и ON CONFLICT, batch и CopyFrom. Конкурентная запись и блокировки — скилл postgres-concurrency. PostgreSQL in Go via pgxpool and go-transaction-manager: pool, repositories, transactions, retries, errors, bulk loads.

scary-production/claude-plugins

Конкурентная запись в PostgreSQL без потерянных обновлений и write skew: условие в UPDATE, SELECT FOR NO KEY UPDATE против FOR UPDATE, exclusion-ограничения против пересечения интервалов, serializable и повтор при 40001/40P01/55P03, очередь задач на SKIP LOCKED, lock_timeout и NOWAIT, advisory-блокировки, разбор зависаний. Примеры — Go (pgx и go-transaction-manager), правила — про сам PostgreSQL. PostgreSQL concurrency: lost updates, write skew, row locks, exclusion constraints, isolation and retries, SKIP LOCKED queues, advisory locks.

scary-production/claude-plugins

Как найти, что тормозит в PostgreSQL, и доказать, что правка помогла. Профиль по pg_stat_statements, сравнение вариантов по числу страниц (BUFFERS), а не по секундам, разбор плана от места, где оценка строк впервые разошлась с фактом. N+1 из Go-цикла сворачиваем в один запрос. Правим условия, FK, статистику и память, а не боремся с планировщиком. Postgres query tuning: profile first, compare by buffers, fix row estimates, remove N+1.

scary-production/claude-plugins

Показывает стили строки состояния на примере или включает один из них (full, compact, bars, minimal) в настройках пользователя или проекта.

関連スキル