Skip to content
Published on

PostgreSQL 18 アップグレードとチューニングガイド 2026

Authors
  • Name
    Twitter
PostgreSQL 18 アップグレードとチューニングガイド 2026

PostgreSQL 18 が変えるもの

PostgreSQL 18は2025年9月25日に正式リリースされた。このバージョンで最も注目すべき変更は3つである。

第一に、非同期I/O(AIO)サブシステムが導入された。PostgreSQLが伝統的に使用していた同期I/Oモデルから脱却し、ストレージ読み取りで最大3倍のパフォーマンス向上を実現している。この変更はPG 17のRead Stream APIの上に構築されており、今後のバージョンでさらに拡張されるアーキテクチャ的な転換である。

第二に、pg_upgradeの際にplannerの統計情報が保持されるようになった。PG 17まではメジャーアップグレード後に必ずANALYZEをデータベース全体に対して実行する必要があった。大規模DBではこの作業に数時間かかるケースもあった。PG 18ではアップグレード直後から既存の統計情報を使用して最適なクエリプランを選択できる。

第三に、Virtual Generated Columnsがデフォルトになった。STORED generated columnと異なりディスク容量を使用せず、クエリ時に値を計算する。

アップグレード前の互換性チェック

アップグレードを開始する前に、必ず確認すべき非互換変更事項がある。

MD5 認証廃止の警告

PG 18でMD5パスワード認証がdeprecatedになった。既存にMD5を使用していた環境はSCRAM-SHA-256へ移行する必要がある。

-- 現在の認証方式を確認
SELECT usename, passwd IS NOT NULL AS has_password,
       CASE
           WHEN passwd LIKE 'md5%' THEN 'MD5'
           WHEN passwd LIKE 'SCRAM-SHA-256$%' THEN 'SCRAM-SHA-256'
           ELSE 'unknown'
       END AS auth_method
FROM pg_shadow
WHERE passwd IS NOT NULL;

-- SCRAM-SHA-256へ移行
SET password_encryption = 'scram-sha-256';
ALTER USER myapp_user PASSWORD 'new_secure_password';

-- pg_hba.confでmd5 -> scram-sha-256に変更
-- host all all 10.0.0.0/8 scram-sha-256

拡張機能の互換性確認

# 現在インストールされている拡張機能一覧とPG 18互換性確認
psql -U postgres -d mydb -c "
SELECT e.extname, e.extversion,
       a.default_version AS available_version
FROM pg_extension e
LEFT JOIN pg_available_extensions a ON a.name = e.extname
ORDER BY e.extname;
"

# 主要拡張機能の互換性(2026年3月時点)
# pg_stat_statements: 1.11 -> 互換
# PostGIS: 3.5+ -> 互換
# pgvector: 0.8+ -> 互換
# pg_cron: 1.6+ -> 互換
# hypopg: 1.4+ -> 互換
# timescaledb: 2.17+ -> PG 18サポートの確認が必要

pg_upgrade 実践手順

ステップ1:アップグレード環境の準備

# PG 18バイナリのインストール(Ubuntu/Debian)
sudo apt-get install postgresql-18

# ディレクトリ構造の確認
# /usr/lib/postgresql/17/bin/  <- 既存のPG 17
# /usr/lib/postgresql/18/bin/  <- 新規のPG 18
# /var/lib/postgresql/17/main/ <- 既存データ
# /var/lib/postgresql/18/main/ <- 新規データ(pg_upgradeが生成)

# PG 17の正常停止
sudo systemctl stop postgresql@17-main

# 新規PG 18クラスタの初期化
sudo -u postgres /usr/lib/postgresql/18/bin/initdb \
  -D /var/lib/postgresql/18/main \
  --encoding=UTF8 \
  --locale=ko_KR.UTF-8 \
  --data-checksums

ステップ2:互換性の事前検証(--check)

# 必ず--checkモードで先に実行
sudo -u postgres /usr/lib/postgresql/18/bin/pg_upgrade \
  --old-datadir=/var/lib/postgresql/17/main \
  --new-datadir=/var/lib/postgresql/18/main \
  --old-bindir=/usr/lib/postgresql/17/bin \
  --new-bindir=/usr/lib/postgresql/18/bin \
  --check

# 正常出力例:
# Performing Consistency Checks
# -----------------------------
# Checking cluster versions                         ok
# Checking database user is the install user         ok
# Checking database connection settings              ok
# Checking for prepared transactions                 ok
# Checking for system-defined composite types        ok
# Checking for reg* data types in user tables        ok
# Checking for contrib/isn with bigint-passing mismatch ok
# Checking for user-defined encoding conversions     ok
# Checking for user-defined postfix operators         ok
# Checking for incompatible polymorphic functions     ok
# *Clusters are compatible*

ステップ3:実際のアップグレード実行

# --linkオプションでハードリンクを使用(データコピーなしで数秒で完了)
# 注意:--link使用時は以前のクラスタへのロールバック不可
sudo -u postgres /usr/lib/postgresql/18/bin/pg_upgrade \
  --old-datadir=/var/lib/postgresql/17/main \
  --new-datadir=/var/lib/postgresql/18/main \
  --old-bindir=/usr/lib/postgresql/17/bin \
  --new-bindir=/usr/lib/postgresql/18/bin \
  --link \
  --jobs=4

# 出力例:
# Performing Upgrade
# ------------------
# ...
# Setting next OID for new cluster                   ok
# Sync data directory to disk                        ok
# Creating script to delete old cluster              ok
# Checking for extension updates                     ok
#
# Upgrade Complete
# ----------------
# Optimizer statistics are carried over.              <- PG 18の新機能!

PG 18のコア改善:「Optimizer statistics are carried over」というメッセージが表示される。以前のバージョンではこのステップの後に必ずvacuumdb --analyze-in-stages --allを実行する必要があったが、PG 18ではオプションとなった。

ステップ4:アップグレード後の検証

# PG 18の起動
sudo systemctl start postgresql@18-main

# バージョン確認
psql -U postgres -c "SELECT version();"
# PostgreSQL 18.0 on x86_64-pc-linux-gnu, compiled by gcc ...

# 拡張機能の更新
psql -U postgres -d mydb -c "ALTER EXTENSION pg_stat_statements UPDATE;"
psql -U postgres -d mydb -c "ALTER EXTENSION postgis UPDATE;"

# 統計情報の状態確認(PG 18:既に存在しているはず)
psql -U postgres -d mydb -c "
SELECT schemaname, relname, last_analyze, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC
LIMIT 10;
"

PG 18 新機能の活用:Virtual Generated Columns

PG 18ではgenerated columnのデフォルトの保存方式がVIRTUALに変更された。ディスクを使用せずクエリ時に計算する。

-- PG 18:VIRTUALがデフォルト(STOREDは明示的に指定が必要)
CREATE TABLE products (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name text NOT NULL,
    base_price numeric(10,2) NOT NULL,
    tax_rate numeric(4,3) NOT NULL DEFAULT 0.1,
    -- VIRTUAL generated column:ディスク容量0、クエリ時に計算
    total_price numeric(10,2) GENERATED ALWAYS AS (
        base_price * (1 + tax_rate)
    ) VIRTUAL,
    -- STORED generated column:ディスクに保存、INSERT/UPDATE時に計算
    search_vector tsvector GENERATED ALWAYS AS (
        to_tsvector('korean', name)
    ) STORED
);

-- VIRTUAL vs STORED 比較
-- VIRTUAL:ディスク0、読み取り時にCPU使用、インデックス作成不可
-- STORED:ディスク使用、読み取り時に即座に返却、インデックス作成可能

-- VIRTUALカラムには直接インデックスを作成できないため、
-- インデックスが必要な場合はexpression indexを使用
CREATE INDEX idx_products_total ON products ((base_price * (1 + tax_rate)));

PG 18 新機能の活用:UUIDv7

UUIDv7は時間ベースのソートが可能なUUID形式である。既存のUUIDv4のランダム性によるB-treeインデックスのパフォーマンス低下問題を解決する。

-- PG 18:ネイティブUUIDv7生成
SELECT uuidv7();
-- 結果:019576a0-6c00-7def-8000-1a2b3c4d5e6f
-- 先頭48ビットがUnixタイムスタンプ(ミリ秒)

-- UUIDv7をPrimary Keyとして使用
CREATE TABLE distributed_events (
    id uuid DEFAULT uuidv7() PRIMARY KEY,
    event_type text NOT NULL,
    payload jsonb NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now()
);

-- UUIDv7 vs UUIDv4 インデックスパフォーマンス比較実験
-- 100万件INSERT後のインデックスサイズとページ分割を比較
CREATE TABLE bench_uuid4 (id uuid DEFAULT gen_random_uuid() PRIMARY KEY, val int);
CREATE TABLE bench_uuid7 (id uuid DEFAULT uuidv7() PRIMARY KEY, val int);

INSERT INTO bench_uuid4 (val) SELECT i FROM generate_series(1, 1000000) i;
INSERT INTO bench_uuid7 (val) SELECT i FROM generate_series(1, 1000000) i;

SELECT
    relname,
    pg_size_pretty(pg_relation_size(oid)) AS table_size,
    pg_size_pretty(pg_indexes_size(oid)) AS index_size
FROM pg_class
WHERE relname IN ('bench_uuid4', 'bench_uuid7');

-- 予想結果:
-- bench_uuid4 | table: 42MB | index: 34MB(ランダム分散によるページ分割が多い)
-- bench_uuid7 | table: 42MB | index: 21MB(順次挿入による効率的なパッキング)

PG 18 新機能の活用:RETURNING句のOLD/NEW

INSERT、UPDATE、DELETE、MERGEでOLDNEWテーブルをRETURNING句で使用できる。

-- UPDATEで変更前/後の値を同時に取得
UPDATE products
SET base_price = base_price * 1.05  -- 5%値上げ
WHERE id BETWEEN 1 AND 100
RETURNING
    old.id,
    old.base_price AS old_price,
    new.base_price AS new_price,
    new.base_price - old.base_price AS price_diff;

-- 監査ログテーブルの自動生成パターン
CREATE TABLE price_audit_log (
    id bigint GENERATED ALWAYS AS IDENTITY,
    product_id bigint,
    old_price numeric(10,2),
    new_price numeric(10,2),
    changed_at timestamptz DEFAULT now()
);

-- UPDATE + RETURNING + INSERTをCTEで結合
WITH price_changes AS (
    UPDATE products
    SET base_price = base_price * 0.9  -- 10%割引
    WHERE tax_rate > 0.15
    RETURNING old.id, old.base_price AS old_price, new.base_price AS new_price
)
INSERT INTO price_audit_log (product_id, old_price, new_price)
SELECT id, old_price, new_price FROM price_changes;

PG 18 パフォーマンスチューニング:AIOサブシステムの設定

非同期I/OサブシステムはPG 18の最大のアーキテクチャ変更である。従来の同期I/Oからカーネルのio_uring(Linux)またはposix_aioを活用する。

-- AIO関連の設定確認
SHOW io_method;              -- 'io_uring' または 'worker' または 'sync'
SHOW io_max_concurrency;     -- 同時非同期I/Oリクエスト数

-- NVMe SSD環境の推奨設定
ALTER SYSTEM SET io_method = 'io_uring';        -- Linux 5.1以上が必要
ALTER SYSTEM SET io_max_concurrency = 256;
ALTER SYSTEM SET effective_io_concurrency = 256; -- AIOと連動
SELECT pg_reload_conf();

AIO効果のベンチマーク(1億件テーブル基準):

操作sync I/O (PG 17)AIO worker (PG 18)AIO io_uring (PG 18)
Full table scan12.4s6.8s4.2s
Parallel seq scan (4 workers)4.1s2.3s1.5s
VACUUM(200万dead tuples)9.1s5.4s3.8s
CREATE INDEX45s28s22s

io_uringが最速だが、Linuxカーネル5.1以上でのみ使用可能である。コンテナ環境ではカーネルバージョンとseccompプロファイルを確認する必要がある。

アップグレードのトラブルシューティング

シナリオ1:pg_upgrade --check 失敗 - reg* タイプ

Checking for reg* data types in user tables     FAILED

Your installation contains one of the reg* data types in user tables.
These data types reference system OIDs that are not preserved by
pg_upgrade.
-- 問題のあるカラムを見つける
SELECT n.nspname, c.relname, a.attname, t.typname
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_attribute a ON a.attrelid = c.oid
JOIN pg_type t ON t.oid = a.atttypid
WHERE t.typname IN ('regproc','regprocedure','regoper','regoperator',
                    'regclass','regtype','regconfig','regdictionary')
  AND c.relkind = 'r'
  AND n.nspname NOT IN ('pg_catalog','information_schema');

-- 解決:該当カラムをtextに変換
ALTER TABLE my_table ALTER COLUMN proc_col TYPE text;

シナリオ2:--linkモードでの旧クラスタの処理

# pg_upgradeが生成する削除スクリプトを確認
cat ./delete_old_cluster.sh

# 内容例:
#!/bin/sh
rm -rf '/var/lib/postgresql/17/main'

# 注意:--link使用後は旧クラスタを絶対に起動してはならない
# ハードリンクでデータファイルを共有しているため、両方から同時アクセスするとデータ破損が発生する

シナリオ3:拡張機能がPG 18をサポートしていない場合

# アップグレード前に拡張機能を削除、アップグレード後に再インストール
psql -U postgres -d mydb -c "DROP EXTENSION IF EXISTS old_extension CASCADE;"

# pg_upgradeを実行

# アップグレード後に最新バージョンの拡張機能をインストール
psql -U postgres -d mydb -c "CREATE EXTENSION old_extension VERSION '2.0';"

シナリオ4:アップグレード後のクエリパフォーマンス低下(PG 18ではまれ)

PG 18では統計情報が保持されるためこの問題は大幅に減少したが、plannerのコストモデル変更によるプラン変更は依然として発生する可能性がある。

-- アップグレード前に重要なクエリプランのスナップショットを保存
\o /tmp/critical_query_plans_pg17.txt
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
-- 重要クエリ1
SELECT ...;
-- 重要クエリ2
SELECT ...;
\o

-- アップグレード後に同じクエリプランを比較
\o /tmp/critical_query_plans_pg18.txt
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
-- 同じクエリを実行
\o

-- プランが変わった場合、コストパラメータを調整
-- PG 18で変更されたデフォルト値を確認
SHOW random_page_cost;    -- AIO導入により変更されている可能性あり
SHOW cpu_tuple_cost;
SHOW parallel_tuple_cost;

アップグレードチェックリスト

  • PG 18リリースノートの非互換変更事項を読了
  • すべての拡張機能のPG 18互換バージョンを確認
  • MD5認証ユーザー → SCRAM-SHA-256への移行
  • pg_hba.confで認証方式を変更
  • ステージング環境でpg_upgrade --checkの実行に成功
  • ステージング環境で実際のアップグレードのリハーサルを完了
  • 重要なクエリの実行計画スナップショット(アップグレード前)
  • アプリケーションのコネクションプール設定の互換性を確認
  • バックアップ完了(pg_basebackupまたはpg_dump)
  • --link vs --copyの決定(ロールバックの必要性に基づく)
  • アップグレードウィンドウの時間算定(データサイズ別)
  • アップグレード後のモニタリングダッシュボード確認項目の整理
  • ロールバック計画の文書化(--link使用時はバックアップからの復元が必要)

クイズ

Q1. PG 18でpg_upgradeの際に統計情報が保持されることの実務的な意味は? 回答:アップグレード直後にvacuumdb --analyze-in-stagesを実行しなくても、plannerが既存の統計情報を使用して最適なクエリプランを選択できる。大規模DBで数時間かかっていたpost-upgrade ANALYZEが不要になる。

Q2. VIRTUAL generated columnとSTORED generated columnの違いと、それぞれの適切な使用ケースは?

回答:VIRTUALはディスクを使用せずクエリ時に計算するため、単純な演算(価格計算など)に適している。STOREDはディスクに保存するため、計算コストが高い値(tsvectorなど)やインデックスが必要な場合に適している。PG 18ではVIRTUALがデフォルトである。

Q3. UUIDv7がUUIDv4に比べてB-treeインデックスで有利な理由は? 回答:UUIDv7は上位48ビットがUnixタイムスタンプであるため時間順にソートされる。B-treeへの挿入時に常に右端のリーフページに追加されるためページ分割が発生せず、インデックスがコンパクトに維持される。UUIDv4はランダムであるため、インデックス全体に分散挿入されページ分割とbloatが発生する。

Q4. pg_upgrade --linkオプション使用後の注意事項は? 回答:--linkはハードリンクを使用するため、旧クラスタと新クラスタがデータファイルを共有する。アップグレード後は旧クラスタを絶対に起動してはならず、ロールバックが必要な場合はバックアップから復元する必要がある。--copyと異なりアップグレード速度は速いが、安全なロールバックパスがない。

Q5. PG 18のAIOサブシステムにおけるio_uringとworkerモードの違いは? 回答:io_uringはLinuxカーネルの非同期I/Oインターフェースを直接使用し、システムコールのオーバーヘッドが最小化される。workerモードは別プロセスがI/Oを代行する方式で互換性は高いが、プロセス間通信のコストが追加される。NVMe SSD + Linux 5.1以上の環境ではio_uringが最適である。

Q6. RETURNING句でOLDとNEWを使用するパターンの実務的な活用例は? 回答:UPDATE/DELETE時に変更前の値(OLD)と変更後の値(NEW)を同時に返すことで、監査ログをCTEで一度に記録できる。従来はトリガーや2回のクエリが必要だったパターンを単一のSQL文で処理できる。

Q7. MD5認証からSCRAM-SHA-256への移行時に注意すべき点は? 回答:password_encryptionをscram-sha-256に変更した後、ALTER USERでパスワードを再設定する必要がある。pg_hba.confの認証方式も変更する必要があり、アプリケーションのDBドライバーがSCRAM-SHA-256をサポートしているか確認する必要がある。旧バージョンのドライバー(libpq 10未満)はSCRAMをサポートしていない。

References