🚀

PostgreSQL 18の非同期I/Oで最大3倍高速化!仕組みと実装のまとめ

に公開

はじめに

2025年9月25日にリリースされたPostgreSQL 18では、非同期I/O(AIO)サブシステムの導入により、特定のワークロードで最大3倍のパフォーマンス向上が実現されました。

本記事では、PostgreSQL 18の主要な新機能を概観した上で、特に注目度の高い非同期I/Oの仕組みについて深掘りします。「なぜ3倍なのか?」「どのようなクエリで効果があるのか?」といった疑問に、具体的な実装例とともに答えていきます。

PostgreSQL 18の主要な新機能(概要)

まず、PostgreSQL 18で追加された主要な機能を簡単に紹介します:

  1. 非同期I/O (AIO) サブシステム - 最大3倍の高速化(本記事で詳しく解説)
  2. メジャーバージョンアップグレードの高速化 - 統計情報の引き継ぎ
  3. Skip Scan - 複合インデックスの柔軟な利用
  4. UUIDv7のサポート - タイムスタンプ順にソート可能なUUID
  5. OAuth 2.0認証 - モダンな認証基盤の組み込み
  6. EXPLAIN ANALYZEの機能強化 - より詳細な統計情報
  7. VACUUMの改善 - aggressive vacuumの必要性が減少
  8. GINインデックスの並列ビルド - 全文検索インデックスの高速化

本記事では、これらの中でも特に影響が大きい非同期I/Oに焦点を当てて解説します。


非同期I/O (AIO) サブシステムの導入

PostgreSQL 18の最も重要な変更の一つが、新しい非同期I/Oサブシステムの導入です。この機能により、特定のワークロードで最大3倍のパフォーマンス向上が報告されています。

変更点の概要

Before (PostgreSQL 17以前):

  • OSのreadahead機構に依存したデータ取得
  • I/Oリクエストを順次実行(一つずつ完了を待つ)
  • OSがデータベース固有のアクセスパターンを把握できず、最適化が不十分
  • Sequential Scanでは同期I/Oのみ

After (PostgreSQL 18):

  • 新しい非同期I/Oサブシステムの導入
  • 複数のI/Oリクエストを同時発行可能
  • 特定のワークロードで最大3倍のパフォーマンス向上
  • Sequential Scans、Bitmap Heap Scans、VACUUMでAIO対応
  • 3つのI/O実行モードを選択可能(workerio_uringsync

非同期I/Oとは何か?

PostgreSQLのデータ格納の基本単位:8KBブロック

PostgreSQLは、データを永続化ストレージ上に 8KB(8,192バイト)の固定サイズのブロック(ページ) として格納します[1]。これはPostgreSQLのストレージ管理の基本単位であり、以下のような特徴があります:

  • 固定サイズ: すべてのテーブルデータ、インデックスデータは8KBブロック単位で管理される
  • I/Oの最小単位: データベースがストレージからデータを読み込む際、必ず8KBブロック単位で読み込む(1バイトだけ必要でも8KB全体を読む)
  • 共有バッファとの対応: PostgreSQLのメモリ領域である共有バッファ(shared_buffers)も8KBブロック単位で管理される
PostgreSQLのデータアクセスの流れ:

クライアント → サーバプロセス → 共有バッファ(8KBブロック単位)
                                    ↓
                              テーブルファイル(8KBブロック単位)

この8KBブロック単位のI/O処理を効率化するのが、PostgreSQL 18の非同期I/Oサブシステムです。

非同期I/Oは、テーブルの物理ファイルから8KBブロック単位でデータを読み込む際の処理方式です。

テーブルの物理構造

large_table (物理ファイル: 1GB = 131,072ブロック)
┌──────────┬──────────┬──────────┬─────┬────────────┐
│ Block 0  │ Block 1  │ Block 2  │ ... │ Block N    │
│  (8KB)   │  (8KB)   │  (8KB)   │     │   (8KB)    │
├──────────┼──────────┼──────────┼─────┼────────────┤
│ 50行     │ 50行     │ 50行     │     │ 50行       │
└──────────┴──────────┴──────────┴─────┴────────────┘
     ↑          ↑          ↑              ↑
     これらのブロックを読み込むI/O処理が対象

シンプルなクエリを例にします:

SELECT * FROM large_table WHERE status = 'active';

このクエリでは、テーブル全体をスキャンして条件に合う行を探します。その際、ストレージから8KBブロック単位でデータを読み込みます。このブロック読み込み処理が非同期I/Oの対象です。

同期I/Oと非同期I/Oの違い

PostgreSQL 17以前(同期I/O)

クエリ実行の流れ(131,072ブロックを読み込む場合):

時刻 0ms:
  PostgreSQL → 「Block 0を読んで」
               ↓ I/O要求
  [ストレージ] Block 0を読み込み中... 
  PostgreSQL   [待機中...] ← 何もできない
  
時刻 0.3ms:
  [ストレージ] → Block 0のデータ
  PostgreSQL   Block 0を処理
  
時刻 0.5ms:
  PostgreSQL → 「Block 1を読んで」← Block 0完了後
               ↓ I/O要求
  [ストレージ] Block 1を読み込み中...
  PostgreSQL   [待機中...] ← 何もできない
  
時刻 0.8ms:
  [ストレージ] → Block 1のデータ
  PostgreSQL   Block 1を処理

... 131,072ブロック分繰り返し

合計時間: 0.3ms × 131,072 ≈ 39秒

問題点:

  • 各ブロックの読み込みが完了するまで次のリクエストを発行できない
  • I/O待機中、CPUが遊んでいる
  • ストレージの並行処理能力を活用できない

PostgreSQL 18(非同期I/O)

クエリ実行の流れ:

時刻 0ms:
  PostgreSQL → 「Block 0, 1, 2, ..., 15を読んで」← 一度に16個要求
               ↓ 複数I/O要求(非同期)
  
  [SSD内部]
    NAND Chip 0: Block 0 読み込み中...
    NAND Chip 1: Block 1 読み込み中... ┐
    NAND Chip 2: Block 2 読み込み中... ├─ 並行実行
    ...                                 │
    NAND Chip 15: Block 15 読み込み中...┘
  
  PostgreSQL   次の処理準備(待機ではない)
  
時刻 0.3ms:
  [ストレージ] → Block 0が到着
  PostgreSQL   Block 0を処理開始
  PostgreSQL → 「Block 16も読んでおいて」← すぐ次の要求
  
時刻 0.31ms:
  [ストレージ] → Block 1が到着(既に読み込み済み)
  PostgreSQL   Block 1をすぐ処理
  PostgreSQL → 「Block 17も読んでおいて」

... パイプライン処理で継続

合計時間: 約13秒(約3倍高速)

改善点:

  • 複数ブロックのI/O要求を同時発行
  • I/O待機時間を重複させることで実質的な待機時間を削減
  • ストレージの並行処理能力を最大限活用
  • 先読み(prefetch)により、データが必要になる前に読み込み完了
    • 例: Block 0を処理している間に、Block 1-15の読み込みを先に開始
    • Block 1が必要になった時には既に読み込み完了しているため、待機時間がゼロになる

なぜ高速化するのか?

理由1: ストレージの並行処理能力の活用

現代のSSDは、複数のI/O要求を同時に処理できます:

SSD内部構造(簡略図):

[PostgreSQLからの複数リクエスト]
        ↓  ↓  ↓  ↓
    ┌────────────────┐
    │  SSDコントローラ  │
    └────────────────┘
         ↓  ↓  ↓  ↓
    ┌────┬────┬────┬────┐
    │NAND│NAND│NAND│NAND│← 物理的に独立した
    │ 0  │ 1  │ 2  │ 3  │   チップで並行処理
    └────┴────┴────┴────┘
  • 同期I/O: SSDの1つのチップしか使わない → 性能の20-25%しか引き出せない
  • 非同期I/O: 複数チップを同時利用 → SSDの性能を最大限引き出せる

理由2: I/O待機時間の重複

同期I/Oのタイムライン:
[Block 0 I/O][待機][Block 1 I/O][待機][Block 2 I/O][待機]...
───────────────────────────────────────────────────→ 時間
待機時間が累積: 0.3ms × 131,072 = 39秒

非同期I/Oのタイムライン:
[Block 0-15 I/O要求]
  [Block 0 I/O]
  [Block 1 I/O]  ← これらが重複
  [Block 2 I/O]
  ...
  [Block 15 I/O]
─────────────────→ 時間
待機時間が重複: 実質0.3ms(16倍効率的)

理由3: パイプライン処理

PostgreSQL 18では、読み込みと処理がパイプライン化:

┌─ Block 0 読み込み ─┐
└────────────────────┘
         ┌─ Block 1 読み込み ─┐
         └────────────────────┘
                  ┌─ Block 2 読み込み ─┐
                  └────────────────────┘
┌─ Block 0 処理 ─┐
└────────────────┘
         ┌─ Block 1 処理 ─┐
         └────────────────┘
                  ┌─ Block 2 処理 ─┐
                  └────────────────┘

読み込みと処理が並行して進むため、全体の処理時間が短縮されます。

理由4: なぜ「最大3倍」なのか?

PostgreSQL公式プレスキットでは「特定のシナリオで最大3倍の性能向上」と報告されています[2]。なぜ4倍や5倍ではなく「3倍」が上限なのでしょうか?

ボトルネックの内訳(推定):

PostgreSQL 17(同期I/O): 39秒
├─ I/O待機時間: 30秒(77%)← 非同期I/Oで削減可能
└─ CPU処理時間: 9秒(23%) ← 削減不可能

PostgreSQL 18(非同期I/O): 13秒
├─ I/O待機時間: 4秒(31%) ← 完全にはゼロにできない
│  ├─ 実際のストレージI/O: 2秒
│  └─ 非同期I/Oのオーバーヘッド: 2秒
└─ CPU処理時間: 9秒(69%) ← 変わらず

改善率: 39秒 → 13秒 = 約3倍

3倍が上限となる主な理由:

  1. CPU処理時間は削減できない(約23%)

    • データの解析、フィルタリング、集計などのCPU処理は非同期I/Oでは高速化できない
    • I/Oをゼロにしても、CPU処理時間(9秒)は残る
    • 理論上の最大値: 39秒 ÷ 9秒 ≈ 4.3倍
  2. 非同期I/Oにもオーバーヘッドがある

    • I/O要求の管理コスト
    • コンテキストスイッチのコスト
    • 並行制御のロックコスト
    • これらが5-10%程度のオーバーヘッドとなる
  3. effective_io_concurrency(デフォルト16)の制約

    • デフォルト設定では16個の並行I/Oまで
    • 理論上は16倍の高速化が可能だが、他のボトルネックにより実際は約3倍
  4. ストレージの物理的限界

    • 同期I/O: SSDの性能の20-25%しか活用できない
    • 非同期I/O: SSDの性能を最大限活用
    • 改善幅: 最大4-5倍が理論値だが、実際は他のボトルネックにより約3倍

より高速化するには:

  • effective_io_concurrencyを64や128に増やす
  • より高速なCPU(CPU処理時間を削減)
  • クエリの最適化(不要なCPU処理を削減)
  • より高速なストレージ(高性能SSDなど)

ただし、Amdahlの法則により、どこかで必ず頭打ちになります。「最大3倍」は、デフォルト設定での現実的な改善値と言えます。

実装例と設定方法

基本設定

重要: PostgreSQL 18にアップグレードして再起動するだけで、自動的に非同期I/Oが有効になります!

PostgreSQL 18ではio_methodパラメータで非同期I/Oの実行方法を選択できます。公式ドキュメントによると:

Selects the method for executing asynchronous I/O. Possible values are:

  • worker (execute asynchronous I/O using worker processes)
  • io_uring (execute asynchronous I/O using io_uring, requires a build with --with-liburing / -Dliburing)
  • sync (execute asynchronous-eligible I/O synchronously)

The default is worker.

This parameter can only be set at server start.

PostgreSQL 18 Documentation - Resource Consumption

デフォルトがworker(非同期I/O)なので、特別な設定なしで恩恵を受けられます。

-- 現在のI/Oメソッドを確認
SHOW io_method;
-- デフォルト: worker(非同期I/O有効)

-- より高性能なio_uringを使いたい場合のみ設定変更
-- (postgresql.confで設定、サーバー再起動が必要)
-- io_method = 'io_uring'    # Linux専用、最高性能
-- io_method = 'worker'      # クロスプラットフォーム対応(デフォルト)
-- io_method = 'sync'        # 従来の同期モード(非推奨)

I/Oワーカー数の調整

-- postgresql.confで設定
-- デフォルト値の詳細は公式ドキュメントを参照してください

-- ワーカー数を増やす(高IOPSストレージの場合)
-- io_workers = 8

I/O並行度の設定

effective_io_concurrencyは、PostgreSQLが同時に実行できる並行I/O操作の数を制御します:

Sets the number of concurrent storage I/O operations that PostgreSQL expects can be executed simultaneously. Raising this value will increase the number of I/O operations that any individual PostgreSQL session attempts to initiate in parallel. The allowed range is 1 to 1000, or 0 to disable issuance of asynchronous I/O requests. The default is 16.

PostgreSQL 18 Documentation - Resource Consumption

-- セッションごとに設定可能
SET effective_io_concurrency = 32;  -- デフォルト: 16

-- テーブルスペース単位で設定
ALTER TABLESPACE fast_ssd SET (effective_io_concurrency = 64);

-- 最大I/O並行度の確認(サーバー起動時のみ設定可能)
SHOW io_max_concurrency;
-- デフォルト値は環境により異なる場合があります

どのようなクエリで効果があるのか

ケース1: 大規模テーブルのフルスキャン(効果: 大)

-- 大量レコードのテーブルからフィルタリング
SELECT * FROM large_table WHERE status = 'active';

-- 実行計画で確認
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM large_table WHERE status = 'active';

-- 出力例:
-- Seq Scan on large_table
--   Buffers: shared read=442478  ← ストレージから大量読み込み
--   Execution Time: 13000 ms     ← PostgreSQL 17では39000 ms

なぜ速い?

  • Sequential Scanで全ブロックを読み込む
  • 大量のI/O要求を非同期で並行処理
  • I/O待機時間が重複して削減

ケース2: JSONB解析を伴う大規模スキャン(効果: 大)

-- JSONBカラムの複雑な解析
SELECT 
    id,
    jsonb_column->>'name' as name,
    jsonb_column->'attributes'->>'category' as category
FROM large_table
WHERE jsonb_column @> '{"status": "active"}';

-- I/Oがボトルネックの場合、大幅な高速化が期待できる

条件: I/Oボトルネックであること

-- ボトルネック判定
EXPLAIN (ANALYZE, BUFFERS)
SELECT ...;

-- Buffers: shared hit=5000 read=400000  ← read >> hit
-- → I/Oボトルネック、AIO効果大!

-- Buffers: shared hit=400000 read=5000  ← hit >> read  
-- → CPUボトルネック、AIO効果小

ケース3: Bitmap Heap Scan(効果: 中)

-- 複数条件のOR検索
SELECT * FROM users 
WHERE id IN (1, 5, 10, 100, 500, ...1000);

-- 実行計画:
-- Bitmap Heap Scan
--   Buffers: shared read=15000
--   Execution Time: 800 ms  ← PostgreSQL 17では1200 ms

なぜ速い?

  • PostgreSQL 17でも限定的な並行I/Oがあったが、PostgreSQL 18では完全な非同期I/O
  • 不連続なブロック読み込みでも効率的

ケース4: VACUUM処理(効果: 大)

-- 大規模テーブルのVACUUM
VACUUM (VERBOSE, ANALYZE) large_table;

-- PostgreSQL 17: 10分
-- PostgreSQL 18: 4分(2.5倍高速化)

なぜ速い?

  • VACUUMも大量のブロックI/Oを実行
  • 非同期I/Oで読み込みと処理を並行化

効果が「ない」ケース

以下のケースではAIOの効果は限定的です:

インデックススキャン(効果: 小)

-- 主キー検索
SELECT * FROM users WHERE id = 12345;

-- 実行計画:
-- Index Scan using users_pkey
--   Buffers: shared hit=4 read=1  ← 少量のI/O
--   Execution Time: 0.123 ms

理由: 読み込むブロック数が少ないため、並行化の恩恵が小さい

キャッシュヒット率が高い場合(効果: なし)

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM hot_table;

-- Seq Scan on hot_table
--   Buffers: shared hit=100000 read=0  ← すべてキャッシュ
--   Execution Time: 500 ms

理由: ストレージI/Oが発生しないため、非同期I/Oの出番がない

CPUボトルネックのクエリ(効果: なし)

-- 複雑な集計処理
SELECT 
    category,
    AVG(price),
    PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY price)
FROM products
GROUP BY category;

-- Buffers: shared hit=50000 read=100  ← I/Oは少ない
-- Execution Time: 8000 ms  ← CPU計算時間が支配的

理由: I/Oではなく、CPU処理が時間のほとんどを占めている

クエリごとの一時設定

-- 重いクエリ実行前に一時的に調整
BEGIN;
SET LOCAL effective_io_concurrency = 128;

-- 大量データのスキャン
SELECT ... FROM very_large_table WHERE ...;

COMMIT;
-- トランザクション終了後は元の設定に戻る

効果測定の方法

-- Before測定(同期モード)
ALTER SYSTEM SET io_method = 'sync';
SELECT pg_reload_conf();  -- または再起動

\timing on
SELECT COUNT(*) FROM large_table WHERE status = 'active';
-- Time: 39243.567 ms (00:39.244)

-- After測定(非同期モード)
ALTER SYSTEM SET io_method = 'worker';
-- 再起動

\timing on
SELECT COUNT(*) FROM large_table WHERE status = 'active';
-- Time: 13081.234 ms (00:13.081)

-- 改善率: 39.2秒 → 13.1秒 (約3倍高速化)

ユースケース

非同期I/Oが特に効果を発揮する環境:

データウェアハウス

  • 大規模なファクトテーブルのスキャン
  • ETLバッチ処理
  • 集計クエリ(WHERE句でフィルタリングが多い)
-- 数億レコードのファクトテーブル
SELECT 
    date_trunc('day', created_at) as day,
    COUNT(*),
    SUM(amount)
FROM fact_sales
WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31'
GROUP BY day;

-- PostgreSQL 18 + AIOで大幅高速化

ログ分析システム

  • 大量のログレコードから特定パターンを検索
  • JSONBを使った柔軟なログ構造
-- 数千万件のログから検索
SELECT * FROM application_logs
WHERE log_data @> '{"level": "ERROR"}'
  AND created_at > NOW() - INTERVAL '7 days';

バッチ処理

  • 夜間バッチでの大量データ処理
  • VACUUMやANALYZEの高速化
-- 定期メンテナンス
VACUUM (ANALYZE) large_table_1;
VACUUM (ANALYZE) large_table_2;
-- 従来4時間 → PostgreSQL 18で1.5時間に短縮

タイムアウト対策

  • クエリタイムアウトギリギリの処理
  • 30秒タイムアウトで35秒かかっていたクエリ → 12秒に短縮
-- アプリケーション側のタイムアウト設定: 30秒
SET statement_timeout = '30s';

-- PostgreSQL 17: 35秒 → タイムアウト
-- PostgreSQL 18: 12秒 → 成功
SELECT ... FROM large_table WHERE ...;

注意点と制約

サーバー再起動が必要

io_methodパラメータはサーバー起動時のみ設定可能です(公式ドキュメント: "This parameter can only be set at server start")。

-- io_methodの変更は再起動が必要
ALTER SYSTEM SET io_method = 'io_uring';
-- 設定は反映されるが、有効化には再起動必須
SELECT pg_reload_conf();  -- これだけでは不十分

メモリ使用量の増加

-- 並行I/O数を増やすとメモリ消費も増加
-- effective_io_concurrency = 128の場合
-- 約128 × 8KB = 1MB per プロセス
-- 100接続なら約100MBの追加メモリ

ワークロードによる効果の違い

ワークロード I/O特性 AIO効果
フルテーブルスキャン read多数 🔥🔥🔥 非常に高い
JSONB解析(大量レコード) read多数 🔥🔥🔥 非常に高い
Bitmap Heap Scan read中程度 🔥🔥 高い
VACUUM read多数 🔥🔥🔥 非常に高い
インデックススキャン read少数 🔥 低い
キャッシュヒット read=0 ❌ なし
CPU集約的クエリ hit多数 ❌ なし

実装のロードマップ

  1. 評価フェーズ(ステージング環境)

    -- 現在のクエリパフォーマンスを測定
    EXPLAIN (ANALYZE, BUFFERS) SELECT ...;
    
    -- I/Oボトルネックを特定
    -- read >> hit なら効果大
    
  2. テストフェーズ

    # PostgreSQL 18にアップグレード
    # postgresql.confで設定
    io_method = 'worker'
    effective_io_concurrency = 32
    io_workers = 4
    
    # 再起動
    sudo systemctl restart postgresql
    
  3. ベンチマーク

    -- 同じクエリで測定
    \timing on
    SELECT ...;
    
    -- 改善率を確認
    
  4. 本番適用

    • メンテナンスウィンドウで設定変更
    • 段階的にrollout
    • モニタリング強化

2. メジャーバージョンアップグレードの高速化

変更点

Before (PostgreSQL 17以前):

  • メジャーバージョンアップグレード時に統計情報が引き継がれない
  • アップグレード後にANALYZEが完了するまでクエリパフォーマンスが低下
  • テーブルやシーケンスが多い場合、アップグレードに時間がかかる

After (PostgreSQL 18):

  • プランナー統計情報がメジャーバージョンアップグレード時に引き継がれる
  • アップグレード直後から期待されるパフォーマンスを発揮
  • pg_upgradeの高速化(多数のオブジェクトがある場合)
  • 並列処理の改善

実装例

# 基本的なアップグレード
pg_upgrade \
  -b /usr/lib/postgresql/17/bin \
  -B /usr/lib/postgresql/18/bin \
  -d /var/lib/postgresql/17/data \
  -D /var/lib/postgresql/18/data

# 並列処理を使用したアップグレード(高速化)
pg_upgrade \
  -b /usr/lib/postgresql/17/bin \
  -B /usr/lib/postgresql/18/bin \
  -d /var/lib/postgresql/17/data \
  -D /var/lib/postgresql/18/data \
  --jobs 4

ユースケース

  • ダウンタイムを最小限に抑えたいプロダクション環境のアップグレード
  • 多数のテーブルやシーケンスを持つ大規模データベース
  • アップグレード後すぐに本番トラフィックを流す必要がある環境

3. Skip Scanによるインデックス最適化

変更点

Before (PostgreSQL 17以前):

  • 複合B-treeインデックスで先頭カラムに=条件がない場合、インデックスを効率的に使用できない
  • フルスキャンが発生するケースが多い

After (PostgreSQL 18):

  • Skip Scan機能の導入
  • 複合インデックスの先頭カラムに条件がない場合でも、後続カラムでインデックスを活用可能
  • より多くのケースで複合B-treeインデックスを効率的に使用できる

実装例

-- 複合インデックスの作成
CREATE INDEX idx_users_dept_created 
ON users(department_id, created_at);

-- Before (PostgreSQL 17以前): 
-- 先頭カラム(department_id)に条件がないため、
-- このクエリはインデックスを効率的に使えなかった
-- After (PostgreSQL 18): 
-- Skip Scanにより、先頭カラムをスキップして
-- 後続カラム(created_at)でインデックスを活用可能
SELECT * FROM users 
WHERE created_at > '2025-01-01';

-- EXPLAINで確認
EXPLAIN (ANALYZE, BUFFERS) 
SELECT * FROM users 
WHERE created_at > '2025-01-01';

-- 出力例(PostgreSQL 18):
-- Index Skip Scan using idx_users_dept_created on users
--   Filter: (created_at > '2025-01-01'::date)

ユースケース

  • 複合インデックスを持つが、検索条件が動的に変わるアプリケーション
  • 多様な検索パターンがあり、すべてのパターンに個別インデックスを作成できない場合
  • 先頭カラムの選択性が低く、後続カラムで絞り込みたい場合

4. UUIDv7のサポート

変更点

Before (PostgreSQL 17以前):

  • gen_random_uuid()でUUIDv4を生成
  • ランダムなUUIDのため、インデックスの断片化が発生しやすい
  • 時系列での並び替えができない

After (PostgreSQL 18):

  • uuidv7()関数の追加
  • タイムスタンプ順にソート可能なUUID
  • インデックスとキャッシュの効率向上
  • uuidv4()gen_random_uuid()のエイリアスとして追加

実装例

-- UUIDv7の生成(タイムスタンプ順)
CREATE TABLE events (
    id UUID PRIMARY KEY DEFAULT uuidv7(),
    event_type VARCHAR(50),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Before (UUIDv4)
CREATE TABLE events_old (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    event_type VARCHAR(50),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- UUIDv7の利点:時系列順にソート可能
INSERT INTO events (event_type) VALUES ('login');
INSERT INTO events (event_type) VALUES ('logout');

-- idの順序が時系列と一致
SELECT id, event_type, created_at 
FROM events 
ORDER BY id;

-- パフォーマンス比較
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM events WHERE id > '0190...'::uuid;

ユースケース

  • イベントログやタイムシリーズデータの主キー
  • 分散システムでの一意性とソート性能の両立
  • インデックスの断片化を抑えたい大規模テーブル

5. OAuth 2.0認証のサポート

変更点

Before (PostgreSQL 17以前):

  • パスワード認証(md5、SCRAM)が主流
  • シングルサインオン(SSO)との統合が困難
  • 外部認証は拡張機能に依存

After (PostgreSQL 18):

  • OAuth 2.0認証の組み込みサポート
  • SSOシステムとの統合が容易に
  • md5パスワード認証は非推奨(将来削除予定)

ユースケース

  • 企業のSSOシステム(Azure AD、Okta等)との統合
  • マイクロサービスアーキテクチャでの認証統合
  • セキュリティコンプライアンスが厳しい環境

6. EXPLAIN ANALYZEの機能強化

変更点

Before (PostgreSQL 17以前):

  • 基本的な実行計画とタイミング情報
  • 限定的な統計情報

After (PostgreSQL 18):

  • インデックススキャン時のルックアップ回数を表示
  • VERBOSEオプションでCPU、WAL、平均読み取り統計を追加表示
  • より詳細なバッファ統計情報

実装例

-- PostgreSQL 18での詳細な実行計画
EXPLAIN (ANALYZE, BUFFERS) 
SELECT u.name, o.total 
FROM users u 
JOIN orders o ON u.id = o.user_id 
WHERE u.created_at > '2025-01-01';

-- 出力例(PostgreSQL 18)
-- Buffers: shared hit=XXX read=XXX
-- Index Lookups: XXX  <- PostgreSQL 18で追加

-- より詳細な統計情報
EXPLAIN (ANALYZE, VERBOSE, BUFFERS) 
SELECT * FROM large_table WHERE status = 'active';

-- 出力例(PostgreSQL 18で拡張された情報)
-- より詳細なバッファ統計
-- I/O統計の詳細

ユースケース

  • クエリパフォーマンスのチューニング
  • インデックス戦略の評価
  • I/Oボトルネックの特定

7. VACUUMの改善

変更点

Before (PostgreSQL 17以前):

  • 通常のVACUUMでは積極的なページフリーズを実行しない
  • aggressive vacuumが必要になるケースが多い
  • オーバーヘッドが大きい

After (PostgreSQL 18):

  • 通常のVACUUM時により多くのページを事前にフリーズ
  • aggressive vacuumの必要性が減少
  • オーバーヘッドの削減

実装例

-- 通常のVACUUM(PostgreSQL 18では自動的に最適化)
VACUUM users;

-- VERBOSEで動作確認
VACUUM (VERBOSE, ANALYZE) users;

-- pg_stat_all_tablesでVACUUM統計を確認
SELECT 
    schemaname,
    relname,
    last_vacuum,
    last_autovacuum,
    vacuum_count,
    autovacuum_count,
    n_tup_ins,
    n_tup_upd,
    n_tup_del
FROM pg_stat_all_tables
WHERE schemaname = 'public';

ユースケース

  • 高トランザクション環境でのメンテナンス負荷軽減
  • トランザクションIDの枯渇対策
  • 定期メンテナンスウィンドウの短縮

8. GINインデックスの並列ビルド

変更点

Before (PostgreSQL 17以前):

  • GINインデックスは単一プロセスでビルド
  • 大規模テーブルでのインデックス作成に時間がかかる
  • B-tree、BRINのみ並列ビルド対応

After (PostgreSQL 18):

  • GINインデックスの並列ビルドをサポート
  • 全文検索インデックスの作成が高速化
  • B-tree、BRIN、GINすべてで並列ビルド可能

実装例

-- 全文検索用GINインデックスの並列ビルド
CREATE INDEX CONCURRENTLY idx_documents_content_gin 
ON documents USING GIN (to_tsvector('english', content));

-- max_parallel_maintenance_workersで並列度を制御
SET max_parallel_maintenance_workers = 4;

-- JSONB用GINインデックス
CREATE INDEX idx_data_jsonb_gin 
ON events USING GIN (data jsonb_path_ops);

-- インデックス作成の進捗確認
SELECT 
    pid,
    datname,
    query,
    state,
    wait_event_type,
    wait_event
FROM pg_stat_activity
WHERE query LIKE '%CREATE INDEX%';

ユースケース

  • 全文検索機能を持つアプリケーション
  • JSONB型を多用するドキュメント指向の設計
  • 大規模テーブルへのGINインデックス追加

まとめ

PostgreSQL 18では、非同期I/O(AIO)サブシステムの導入により、I/Oボトルネックのあるワークロードで最大3倍のパフォーマンス向上が実現されました。

非同期I/Oの重要ポイント

  • 8KBブロック単位のI/O処理を並行化することで、ストレージの性能を最大限活用
  • デフォルト設定で約3倍の高速化(effective_io_concurrency=16)
  • Sequential Scan、Bitmap Heap Scan、VACUUMで特に効果大
  • CPU処理時間の制約により、理論上の最大値は約4.3倍

その他の主要な新機能

PostgreSQL 18では、非同期I/O以外にも以下の重要な機能が追加されています:

  1. メジャーバージョンアップグレードの高速化 - 統計情報の引き継ぎ
  2. Skip Scan - 複合インデックスの柔軟な利用
  3. UUIDv7のサポート - タイムスタンプ順にソート可能なUUID
  4. OAuth 2.0認証 - モダンな認証基盤の組み込み
  5. EXPLAIN ANALYZEの機能強化 - より詳細な統計情報
  6. VACUUMの改善 - aggressive vacuumの必要性が減少
  7. GINインデックスの並列ビルド - 全文検索インデックスの高速化

特に大規模データを扱うシステムI/Oがボトルネックになっているシステムでは、PostgreSQL 18へのアップグレードによる大幅なパフォーマンス改善が期待できます。

参考資料

脚注
  1. PostgreSQL Internals - アーキテクチャ概要 ↩︎

  2. PostgreSQL 18 公式プレスキット ↩︎

Discussion