Claude Media
Claude Codeでpg_upgrade前に非互換を洗う — --checkとリリースノートの突き合わせ

Claude Codeでpg_upgrade前に非互換を洗う — --checkとリリースノートの突き合わせ

pg_upgrade --checkは新旧クラスターの互換性だけを見ます。検証用コピーで実行し、リリースノートの非互換項目を自分のDBに当ててから本番に進む手順です。

pg_upgrade --checkが見るのはクラスター同士の互換性です。アプリのSQLが返す結果や、インデックスの振る舞いの変化までは見てくれません。

そこで本番に触る前に、2つの調べ物を検証用コピーの上で済ませます。1つは--checkの出力を読むこと。もう1つは、移行先バージョンのリリースノートにある非互換項目を、自分のDBとコードに当てることです。どちらも「読んで突き合わせる」作業なので、Claude Codeに任せやすい部類です。

本記事はその突き合わせの組み方を、権限設定、CLAUDE.mdの断片、確認用SQL、判定表の順にまとめます。リストアしたダンプが戻るかの検証はpg_restoreのリストア演習の領分で、ここではバージョンを上げる前の影響調査だけを扱います。

pg_upgrade --checkが見るもの、見ないもの

pg_upgradeは、メジャーバージョン間でデータをダンプ・リストアせずに移すためのコマンドです。PostgreSQLのドキュメントによると、メジャーリリースではシステムテーブルの構造が変わる一方で、内部のデータ格納形式はめったに変わりません。そのため新しいシステムテーブルを作り、ユーザーデータのファイルを再利用する方式で速く移せます。

--checkは、データを変更せずクラスターの互換性だけを調べるオプションです。旧サーバーが起動したままでも実行でき、アップグレード後に手作業が要る調整も出力されます。リンク、クローン、copy-file-range、swapのいずれかのモードで移すつもりなら、--checkにも同じモード指定を付けます。モード固有の検査が有効になるためです。

次の3点は--checkの対象外です。1つ目と3つ目はドキュメントに書かれている内容で、2つ目はこの記事の説明です。

  • 外部モジュールのバイナリ互換性。pg_upgradeは新旧のクラスターがバイナリ互換かを確かめようとしますが、拡張機能などの外部モジュールについては検査できないと書かれています
  • アプリケーションのクエリが返す結果。システムカタログとデータファイルの移行手順であり、SQLの意味の変化は対象外です
  • 論理レプリケーションを使うクラスターの手順。ドキュメントは別の節に委ねています

また、次のreg系の型を使った列を含むDBは、pg_upgradeでアップグレードできません。

regcollation / regconfig / regdictionary / regnamespace / regoper / regoperator / regproc / regprocedure

regclass、regrole、regtypeは移せます。この一覧はSQLで機械的に探せるので、Claudeに最初に当てさせる材料になります。

本番に触らず、検証用コピーを用意する

--checkだけなら旧クラスターは変更されません。ただし、リハーサルで本物のpg_upgradeまで流すなら、旧クラスターを壊さない土台が要ります。ドキュメントは次の選択肢を挙げています。

方法旧クラスターへの影響補足
コピーモード(既定)旧クラスターへの影響変更されない補足時間とディスクを使う
--clone旧クラスターへの影響変更されない補足対応するファイルシステムが前提。LinuxのBtrfsとXFS(reflink付き)、macOSのAPFS
--link旧クラスターへの影響新クラスター起動後は使えない補足同じファイルシステム上で高速。起動後は旧側をバックアップから戻す
--swap旧クラスターへの影響転送開始後に破壊的に変更される補足関係の多いクラスターで最速になりうる
旧クラスターのコピーを作る旧クラスターへの影響コピーだけが変わる補足rsyncで稼働中にダーティコピーを取り、停止後にrsync --checksumで差分を詰める

ファイルシステムのスナップショットやコピーオンライトのコピーが使えるなら、それでも旧クラスターのバックアップが取れます。ただしスナップショットは、全体を同時に取るか、サーバーを止めている間に取る必要があります。

もう1つ、ドキュメントには「デプロイのテストには、旧クラスターのスキーマだけのコピーを作り、ダミーデータを入れてからアップグレードする」という案内があります。pg_upgradeの移行手順はスキーマに依存し、ユーザーデータには依存しないためです。本番のデータをClaudeの作業場所に置きたくないときは、この方法が使えます。

Claudeに渡す前提を決める

検証用コピーはプロジェクトの作業ディレクトリの下に置きます。pg_upgradeはカレントディレクトリへの書き込み権限が必要で、Unixソケットも既定でカレントディレクトリに作るためです。Claude Codeのsandboxは、作業ディレクトリへの書き込みを既定で許可します。

settings.jsonの権限

Claude Codeのドキュメントは、Claudeに合否が返るチェックを持たせると、自分で直して再実行する閉ループになると説明しています。ここでのチェックはpg_upgrade --checkの終了コードです。その実行を毎回の承認なしにするため、次のようにallowへ載せます。

{
  "permissions": {
    "allow": ["Bash(pg_upgrade --check *)"]
  }
}

引数の先頭に--checkを置いた形だけが通ります。--checkを付けないpg_upgradeは、allowに載っていないので、既定のモードでは承認を求められます。承認画面で-dと-Dの指す先が検証用コピーであることを、毎回確かめる運用です。本番のデータディレクトリを指す引数が見えたら、そこで止められます。

落とし穴

--checkは「旧サーバーを止めずに実行できる」だけで、本番を指してよい理由にはならない

ドキュメントのとおり、--checkは旧サーバーが動いたままでも実行できます。ただし旧サーバーが稼働中のとき、新旧のポート番号は別にする必要があります。検証用コピーを対象にしておけば、この点を気にせず済み、本番の接続情報をClaudeに渡す理由もなくなります。

CLAUDE.mdの断片

Claude Codeのベストプラクティスは、CLAUDE.mdには「リポジトリを読んでも分からないこと」だけを書くよう勧めています。この作業で書くのは、チームの決めごとです。

# PostgreSQLメジャーアップの影響調査
- 対象は rehearsal/pg14-copy だけ。本番の接続情報は持たない
- Claude が自分で実行する pg_upgrade は --check を先頭に付ける。--check を外した本実行は、承認画面で -d/-D の指す先を人が確かめてから通す。-b -B -d -D は常に明示する
- 調査結果は rehearsal/report.md に表で残す
  列: 項目 / 根拠コマンド / 出力の抜粋 / 判定(要対応・影響なし・未確認)
- 判定が「影響なし」でも、根拠コマンドの出力を省略しない

最後の1行が効きます。ベストプラクティスは、Claudeに完了を報告させるとき「実行したコマンドとその出力」を求めるよう勧めています。「全部確認しました」だけでは、見ていない項目があっても区別がつかないからです。

リリースノートの非互換項目を、自分のDBに当てる

PostgreSQLのリリースノートには、各バージョンの「Migration to Version N」節があります。前のリリースとの互換性に影響する変更だけを集めた節です。PostgreSQL 18の例では、次の項目が並んでいます。

  • initdbの既定でデータチェックサムが有効になった。pg_upgradeは新旧のチェックサム設定の一致を要求するため、旧クラスターが無効なら--no-data-checksumsで新クラスターを作る
  • 全文検索が、libc固定ではなくクラスターの既定の照合順序プロバイダーで設定ファイルと辞書を読むようになった。ICUなどを既定にしているクラスターは、pg_upgradeのあと全文検索とpg_trgmに関わるインデックスの再作成が推奨されている
  • 主キーと外部キーの関係で、決定的な照合順序か同一の非決定的な照合順序を使うことが必須になった。満たさないとpg_dumpのリストア(pg_upgradeも使う)が失敗するため、スキーマの修正が先に要る
  • MD5パスワード認証が非推奨になり、MD5パスワードを設定するとロール作成時に警告が出る
  • VACUUMとANALYZEが継承の子テーブルも処理するようになった(従来の挙動はONLYオプション)
  • AFTERトリガーが、イベントをキューに積んだ時点のロールで実行されるようになった
  • COPY FROMが、CSV読み込み中の\.をファイル終端として扱わなくなった

この一覧は移行先のバージョンごとに別物です。14から18へ上げるなら、15・16・17・18の4つ分の節を順に見ます。節の内容は、手作業で検索してClaudeに貼るより、ファイルとしてリポジトリに置いておくほうが追跡できます。

節ごとに、DBへ問い合わせる

項目ごとの該当の有無は、検証用コピーに対するSQLで調べます。次はPostgreSQL 18への移行を想定した例です。公式の手順ではなく、上の項目に合わせて組んだ確認用のクエリです。

-- 1. 旧クラスターのチェックサム設定(新側の initdb の指定に合わせる)
SHOW data_checksums;
 
-- 2. 使っている拡張機能とバージョン(新側の利用可能なバージョンと突き合わせる)
SELECT extname, extversion FROM pg_extension ORDER BY 1;
 
-- 3. pg_upgrade が移せない reg* 型の列
SELECT a.attrelid::regclass AS tbl, a.attname, t.typname
FROM pg_attribute a
JOIN pg_type t ON t.oid = a.atttypid
WHERE a.attnum > 0 AND NOT a.attisdropped
  AND t.typname IN ('regcollation','regconfig','regdictionary','regnamespace',
                    'regoper','regoperator','regproc','regprocedure');
 
-- 4. 非決定的な照合順序を使っているか
SELECT collname FROM pg_collation WHERE NOT collisdeterministic;
 
-- 5. 全文検索と pg_trgm に関わる索引
SELECT schemaname, indexname FROM pg_indexes
WHERE indexdef ~* '(to_tsvector|gin_trgm_ops|gist_trgm_ops)';
 
-- 6. データベースごとの既定の照合順序プロバイダー
SELECT datname, datlocprovider FROM pg_database;

結果が空なら、その項目は「該当なし」と根拠付きで書けます。結果が出た行は、手作業の対象です。pg_upgrade --checkが事前に止めてくれる項目(reg*型など)と、--checkでは通ってしまい、あとで効いてくる項目(全文検索のインデックスなど)を、分けて表に残すのが肝になります。

Claudeへの依頼の組み方

依頼は、作業を3つの段に分けます。

手順

影響調査の進め方

  1. 1

    --checkを流して出力を保存する

    検証用コピーでpg_upgrade --checkを実行し、出力をファイルに残させます。失敗した項目は、メッセージの該当行とともに表へ移します。

  2. 2

    リリースノートの節を項目に分解する

    リポジトリに置いた「Migration to Version N」の節を読み、項目ごとに「何が変わったか」「確認するSQLかコード検索」を並べさせます。plan modeで依頼すれば、ファイルを編集せずに計画だけを出させられます。計画の使い方はplan modeで見積もり根拠を作る手順にあります。

  3. 3

    DBとコードに当てて判定する

    各項目をSQLで調べ、アプリのコードはgrepや検索で当てさせます。表の「判定」列は、要対応・影響なし・未確認の3値に限ります。

アプリ側の確認は、ORMやSQL文字列を直接書いている箇所が対象です。COPY FROMでCSVを読む処理や、AFTERトリガーを使うテーブルは、コード検索で拾えます。変更前後の差分から呼び出し側の影響を洗う考え方は、OpenAPIのoasdiffで破壊的変更を検出する手順と同じ型です。

検証用コピーで本物のpg_upgradeまで流す

--checkが通ったら、検証用コピーでpg_upgradeを実行します。ここで見るのは、成功するかどうかに加えて、事後に必要な作業の量です。

pg_upgrade \
  -b /usr/lib/postgresql/14/bin -B /usr/lib/postgresql/18/bin \
  -d rehearsal/pg14-copy -D rehearsal/pg18-new \
  --clone

--checkを外したこのコマンドはallowに載っていないので、承認画面で-dと-Dの指す先が検証用コピーであることを確かめてから通します。パスは環境によって違うので、例として読んでください。新サーバーのバイナリと、initdbで初期化した新クラスターのディレクトリが先に必要です。initdbの指定は旧クラスターと互換にします。ドキュメントは、拡張機能の共有オブジェクトファイル(.so)も新側に入れておくよう求めていますが、CREATE EXTENSIONは新クラスターで実行しません。旧クラスターの定義が引き継がれ、重複するためです。

本実行のあとに見るのは、次の3点です。

  1. pg_upgradeが出力した警告と、生成された後処理用のSQLスクリプト。拡張機能の更新が必要なら、その旨とスクリプトが出ます
  2. 統計情報。pg_upgradeは最適化の統計の大半を引き継ぎますが、CREATE STATISTICSで作った拡張統計などは移りません。終了時に案内されるvacuumdbのコマンドで再生成します
  3. 失敗したときの旧クラスターの状態。--linkや--swapを使うと、新クラスターを起動したあとは旧クラスターに戻れない場合があります

後処理用のSQLスクリプトは、データベースごとに作られます。ドキュメントには、スキーマが同じクラスターなら後処理の手順も同じになる、とあります。つまりリハーサルで出たスクリプトの内容は、本番の手順書の素材になります。ただし再構築スクリプトが参照するテーブルは、スクリプトの完了までアクセスしてはいけません。誤った結果や性能低下の恐れがあるためです。この制約は、本番の停止時間の見積もりに入れます。

判定表の書かせ方と、見落としの防ぎ方

最終的な成果物は、rehearsal/report.mdの表です。例を示します。

項目根拠コマンド出力の抜粋判定
データチェックサム根拠コマンドSHOW data_checksums;出力の抜粋off判定要対応(initdb --no-data-checksumsで新側を作る)
reg*型の列根拠コマンド上記SQLの3出力の抜粋0行判定影響なし
非決定的な照合順序根拠コマンド上記SQLの4出力の抜粋0行判定影響なし
拡張機能のバージョン根拠コマンド上記SQLの2出力の抜粋3件判定未確認(新側で利用可能なバージョンを調べる)

「未確認」を許す列にしておく点が大事です。判定が「影響なし」と「要対応」の2値だと、調べきれなかった項目も「影響なし」へ寄ってしまいます。未確認が残る間は、本番に進まないという運用にすれば、見落としが表に出ます。

Claudeの自己申告だけに頼らない方法もあります。ベストプラクティスは、/code-reviewのように、会話を見ていないサブエージェントに差分を見せる方法を挙げています。判定表なら、表だけを渡し、「根拠コマンドの出力が判定を支えているか」を別のセッションで確かめさせます。

検証用コピーで分からないこと

リハーサルは万能ではありません。本番との差が出る点を、あらかじめ表に別枠で書きます。

  • 所要時間。コピーが小さければ、本番のファイル数やリレーション数での時間は測れません。--jobsや--swapの効き方は、データ量とファイルシステムに左右されます
  • 拡張機能のバイナリ互換。pg_upgradeが検査できない領域で、本番のOSパッケージと同じバージョンを入れた環境で動かして初めて確かめられます
  • スタンバイサーバー。リンクモードでrsyncを使う手順は、ドキュメントに専用の手順があります。ストリーミングレプリケーションを組んでいるなら、コピーではなく別途の予行が要ります

最後に、移行前の依存を洗うときに他の領域で効く考え方を挙げます。ライブラリのCVEを呼び出し箇所まで仕分ける複数リポジトリのCVE影響調査や、無停止かどうかをロックレベルで見るPostgresのマイグレーションレビューも、「公開された変更を自分の資産に当てる」型の作業です。

Claudeに任せるのは「読んで、自分のDBに当て、根拠を残す」ところまでです。本番で実行するかどうかの判断は、表の未確認が0件になってから人間が下します。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/best-practices
  2. postgresql.org/docs/current/pgupgrade.html
  3. postgresql.org/docs/18/release-18.html
この記事を共有:XはてブLinkedIn