はじめに
RDS for PostgreSQL を 17 系から 18 系へメジャーバージョンアップする機会がありました。
PostgreSQL 18 では pg_upgrade の挙動が大きく変わり、これまでアップグレード直後に必須だった「全テーブルへのフル ANALYZE」が原則不要になっています。一方で、すべての統計情報が引き継がれるわけではなく、移行後に何を確認し何を実行すべきかは事前に整理しておく必要があります。
この記事では、
- PG18 で何が変わったのか(統計情報の引き継ぎ仕様)
- 引き継がれない統計情報と、その事前確認方法
- アップグレード後に実行すべきコマンド(
ANALYZEとvacuumdbの違い) - 型変更(
ALTER TABLE ... TYPE ...)を伴う場合の実践手順とハマりどころ
を、実際の移行作業で確認した内容をベースにまとめます。
PostgreSQL 18 からの統計情報に関する仕様変更
PostgreSQL では、オプティマイザ(SQL の実行計画を作成する機能)が最適な経路を選択するために、正確な「統計情報」が不可欠です。この統計情報がアップグレードでどう扱われるかが、18 で改善されました。
pg_upgrade の挙動の変化
-
PG 17 以前: メジャーバージョンアップ(
pg_upgrade)を行うと、統計情報はすべてリセットされていました。そのため、アップグレード直後にデータベース全域へフルスクラッチでANALYZEを実行する必要があり、これが完了するまでプランナが不正確な計画を立てるリスクがありました。 -
PG 18 以降: 基本的な統計情報が新しいクラスタへ自動的に引き継がれるように改善されました。これにより、アップグレード後の
ANALYZEにかかる時間とリスクを大幅に削減できます。
--no-statisticsオプションが指定されていなければ、pg_upgradeはほとんどのオプティマイザ統計情報を古いクラスタから新しいクラスタに転送します。
(PostgreSQL 18 ドキュメント: pg_upgrade)
試しにバージョンアップ後に以下クエリで統計情報の引き継ぎ状況を確認してみました。
SELECT
relname AS table_name,
reltuples AS estimated_rows
FROM
pg_class
WHERE
relkind = 'r' -- 通常のテーブルに限定
AND relnamespace = 'public'::regnamespace;
以下ドキュメントによると、reltuples が -1 でなければ VACUUM や ANALYZE が実行されていると言えそうでした。実際に -1 であるテーブルはありませんでしたので、無事に引き継がれているということが分かりました。
引き継がれない(転送されない)3つの統計情報
基本統計は引き継がれますが、以下の3つは仕様上引き継がれません。それぞれ「実害があるか」が異なる点がポイントです。
-
CREATE STATISTICSで明示的に作成された統計情報(拡張統計情報)- 仕様:複数列の相関関係などを定義した「構造(スキーマ)」は引き継がれますが、計算された「実データ」は空になります。
- 対応:構造は残っているため、再作成のコマンド(
CREATE STATISTICS)の実行は不要です。ANALYZEを実行するだけでデータが再計算され、復旧します。
-
拡張機能(Extension)により追加された独自統計情報
- 仕様:
pg_stat_statements(SQL の実行履歴)や PostGIS などが独自に保持する統計データは完全にリセットされます。 - 対応:過去の監視データとして運用上必要かどうかの確認のみ。DB の動作自体には影響しません(稼働後にゼロから蓄積されます)。
- 仕様:
-
累積統計システムによって収集された統計情報
- 仕様:テーブルのスキャン回数や更新・削除行数などの稼働履歴(
pg_stat_user_tables等で確認可能)はリセットされます。 - 対応:プランナの実行計画には使われない(主にオートバキュームの発火判定などに使用される)ため、リセットされても実行計画は劣化しません。
- 仕様:テーブルのスキャン回数や更新・削除行数などの稼働履歴(
引き継がれない統計情報の事前確認法
「移行後に追加対応が必要か」を判断するため、バージョンアップ前に以下の SQL で確認します。ポイントは、ANALYZE を打つかどうかの判断ではなく、標準の ANALYZE 以外の追加対応(拡張統計の再生成や監視データのリセット周知)が要るかの切り分けである点です。
① 拡張統計情報の確認
SELECT
stxname AS 統計情報名,
stxowner::regrole AS 所有者,
stxrelid::regclass AS 対象テーブル
FROM
pg_statistic_ext;
→ 結果が 0 rows であれば、拡張統計情報に起因するプランナの精度低下リスクはゼロです。
② インストールされている拡張機能(Extension)の確認
SELECT
extname AS 拡張機能名,
extversion AS バージョン
FROM
pg_extension;
→ plpgsql(標準の手続き言語。統計は持たない)のみであれば懸念はありません。pg_stat_statements 等が存在する場合は、過去の監視データがリセットされる旨を運用担当者に共有しておきます。
尚、今回の対象においてはいずれも拡張対応がされていなかったので、特に懸念なしと判断できました。
SQL ANALYZE と vacuumdb コマンドの違い
バージョンアップ後の統計の最適化には、SQL の ANALYZE ではなく、CLI ツールの vacuumdb を使うのがベストプラクティスです。発行している処理の中身(DB への ANALYZE)は同じですが、実行の「賢さ」が異なります。
まず
vacuumdb --all --analyze-in-stages --missing-stats-onlyを使用して、統計情報がないリレーションに最小限の統計情報を高速に生成します。次にvacuumdb --all --analyze-onlyを使用して、すべてのリレーションで統計を更新します。どちらも--jobsで高速化できます。
(PostgreSQL 18 ドキュメント: pg_upgrade)
| 比較項目 | SQL: ANALYZE;
|
CLI: vacuumdb
|
|---|---|---|
| 処理方式 | 直列処理(1 テーブルずつ順番に実行) | 並列処理(--jobs で複数テーブル同時実行) |
| 実行方法 | 最初からフル精度で実行(時間がかかる) | 段階的に精度を上げて実行可能(--analyze-in-stages) |
| 対象制御 | 全テーブル対象(テーブル指定時を除く) | 欠損している統計のみに絞り込み可能(--missing-stats-only、PG18〜) |
単純な ANALYZE は完了までクエリが不適切な計画で実行されるリスクの時間が長引きますが、vacuumdb を適切に使うことでダウンタイムを極限まで短縮できます。
vacuumdb コマンドの主要オプション
アップグレード時のリカバリに利用するオプション群です。
-
--all: サーバー内の全データベースを対象に実行する。 -
--jobs <並列数>: 指定したプロセス数で並列実行する。CPU リソースに余裕があれば4などに設定して処理時間を短縮。 -
--analyze-in-stages: 最初は極端に少ないサンプル数で超高速に粗い統計を作り、即座にプランナの暴走を防ぐ。その後、中精度 → フル精度と 3段階でパスを重ねて仕上げる。 -
--analyze-only:VACUUM(不要領域の回収)は行わず、ANALYZE(統計の更新)のみを実行する。 -
--missing-stats-only(PG18 新機能) : 既に基本統計が引き継がれているテーブルをスキップし、統計が空になっているテーブル / 列だけを狙い撃ちする。
--analyze-in-stages と --missing-stats-only を併用すると、1 段階目で粗い統計(サンプル数最小)が入った時点でそのカラムは「統計あり」と判定され、2 〜 3 段階目がスキップされます。結果として粗い精度のまま残るため、最短復旧を狙う場合は「--analyze-in-stages で先行 → --analyze-only で仕上げ」の 2 段構えにします。
バージョンアップ + 型変更を伴う場合の手順
今回の対応では、あるサービスの DB で ID 列の bigint 化(integer から bigint への型変更)作業が同時にありました。型変更が統計情報に与える影響と、その手順を記載します。
型変更(ALTER TABLE ... ALTER COLUMN ... TYPE ...)には次の特性があります。
-
ACCESS EXCLUSIVEロックを取得するため、ANALYZEにより取得されるSHARE UPDATE EXCLUSIVEロックと競合(ロック待ち)を起こす。 - 型変更をすると、そのテーブルの該当列の統計はリセットされる(
pg_upgradeの統計転送とは無関係に失われる)。
つまり、pg_upgrade で統計が引き継がれても、その後に型変更したテーブルは別途 ANALYZE が必要になります。ダウンタイムの要件に合わせて以下のどちらかを実施します。
パターン A: 手堅くシンプルな手順(推奨)
ダウンタイムに余裕がある場合。
- RDS のメジャーバージョンアップ(17 → 18)
- 型変更の実行(ID の bigint 化など)
- 対象テーブルの統計をフル精度で
ANALYZE
$ vacuumdb -h <エンドポイント> -U <ユーザー> --analyze-only \
-d <DB 名> -t <対象テーブルA> -t <対象テーブルB> -j 4
パターン B: 最短でのサービス復旧を優先する手順
他テーブルへのアクセスを 1 秒でも早く復旧させたい場合。
- RDS のメジャーバージョンアップ
- 型変更の実行(この間、該当テーブルはロックされる)
- 高速
ANALYZEを先行実行(粗い統計で稼働可能状態にする)
$ vacuumdb -h <エンドポイント> -U <ユーザー> --analyze-in-stages \
-d <DB 名> -t <対象テーブルA> -t <対象テーブルB> -j 4
- 対象テーブルの統計をフル精度で
ANALYZE(仕上げ)
$ vacuumdb -h <エンドポイント> -U <ユーザー> --analyze-only \
-d <DB 名> -t <対象テーブルA> -t <対象テーブルB> -j 4
いずれの場合も、統計が壊れたのは型変更したテーブルだけなので、全 DB を対象にせず -t で対象テーブルを絞り込むのが効率的です。なお、ID 列を外部キーで参照している子テーブルも併せて型変更した場合は、そのテーブルも -t の対象に加えてください。
vacuumdb 実行後には ANALYZE が適用されたことを確認するため以下クエリを実行し、 last_analyze のタイミングが実行タイミングと合致するか確認します。
SELECT * FROM pg_stat_all_tables where relname ='table_name';
まとめ
- PG18 から
pg_upgradeは基本統計を新クラスタへ転送するようになり、アップグレード後の全テーブルフルANALYZEは原則不要になった。 - ただし「拡張統計(
CREATE STATISTICS)」「拡張独自統計」「累積統計」の 3つは引き継がれない。事前にpg_statistic_extとpg_extensionを確認しておけば、追加対応の要否を判断できる。 - 拡張統計があっても、構造は残るため
ANALYZEで復旧する(CREATE STATISTICSの打ち直しは不要)。 - 統計の再生成には
vacuumdbを使うと並列化・段階実行・欠損絞り込みができ、ダウンタイムを短縮できる。 -
型変更(
ALTER ... TYPE)を伴う場合は別軸の注意が必要。型変更したテーブルは統計がリセットされるため、pg_upgradeの転送とは関係なく、対象テーブルへのANALYZEが必須になる。
PG18 のおかげで、これまで「アップグレード後の重いフル ANALYZE」が当たり前だった運用は実質卒業できます。一方で、型変更のような別作業が絡むと依然として ANALYZE が必要になる点は、見落としやすいので注意してください。