ブログ

Postgres 18のポテンシャルを活かす設定 — 非同期I/Oによるリードレイテンシの改善事例

今回は、Postgres 18’s Async I/O Cut Our Read Latency 60%. We Changed One Config Line という記事を参考に、最新のPostgreSQLで導入された非同期I/O機能と、それを阻害していた意外な設定の盲点について整理してみます。

最適化の話題です。参考まで。


データベースをアップグレードしたものの、期待していたパフォーマンス向上が得られないという状況は、実務でもたまに遭遇するかと思います。今回紹介する事例は、設定ファイル1行の書き換えでリードレイテンシが60%も削減されたという、非常に示唆に富む内容です。


アップグレードしても変わらなかったパフォーマンス

PostgreSQL 18(現在は開発版・ベータのフェーズが含まれますが、ここでは最新の改善項目を指します)へアップグレードした際、多くのユーザーが期待するのは新しい I/O エンジンの恩恵です。しかし、あるチームではアップグレード後もリードレイテンシが全く改善されず、11日間も原因究明に追われることになりました。

結論から言うと、原因は postgresql.conf そのものではなく、インフラを管理していた Terraform のテンプレート内に隠れていました。

忘れ去られた1行の設定

過去にベータテストを行っていた際、特定のプランナーバグを回避するために、あるエンジニアが以下の設定を固定していました。

# Terraformのモジュール内に埋め込まれていた設定例
parameter {
  name  = "io_method"
  value = "sync"
}

この設定が Terraform のリファクタリング過程で継承され続け、Postgres 18 になっても「古い同期的な読み込み方式」を強制していたのが原因でした。設定がソースコードの奥深くに沈んでしまうと、データベース本体を新しくしても、その機能が眠ったままになってしまうという良い例かもしれません。


同期I/Oと非同期I/Oの決定的な違い

PostgreSQL 17 までの従来の方式(同期I/O)と、PostgreSQL 18 から本格的に導入される非同期I/Oの仕組みを比較してみます。

従来の同期I/O(sync)

これまでは、1つのバックエンドプロセスがデータを読み込む際、1ブロックごとに完了を待機していました。たとえば、16ブロックのデータが必要な場合、以下のような流れになります。

sequenceDiagram
    participant B as バックエンド
    participant OS as OS / カーネル
    participant D as ディスク (NVMe)

    Note over B, D: 同期I/O (io_method = sync)
    B->>OS: ブロック1を要求
    OS->>D: 読み込み
    D-->>OS: 完了
    OS-->>B: データ返却
    Note right of B: [待機時間]

    B->>OS: ブロック2を要求
    OS->>D: 読み込み
    D-->>OS: 完了
    OS-->>B: データ返却
    Note right of B: [待機時間]

    Note over B, D: これを16回繰り返す

この方式だと、最近の高速な NVMe ストレージを使用している場合、ディスク側にはまだまだ余裕(並列処理能力)があるのに、データベース側が「待機」している時間が長くなり、リソースを十分に活用できません。

Postgres 18 の非同期I/O

Postgres 18 では、I/Oワーカーを活用して複数のブロックをまとめて要求できるようになります。

flowchart TD
    subgraph PG ["PostgreSQL 18 (Backend)"]
        A["読み込み要求<br>(ブロック 1-16)"]
    end

    subgraph Workers ["I/O Workers / AIO Interface"]
        W1["ワーカー 1"]
        W2["ワーカー 2"]
        W3["ワーカー 3"]
    end

    subgraph Storage ["ストレージ層"]
        Q["ディスクキュー (深さ16)"]
        NVMe[("高速 NVMe")]
    end

    A --> W1 & W2 & W3
    W1 & W2 & W3 --> Q
    Q --> NVMe

このように、一度に「これだけのブロックが必要だ」とリクエストを投げ、バックエンドが個別の完了をいちいち待たずに処理を進められる(またはストレージの並列性を引き出せる)ようになるため、レイテンシが劇的に改善します。


パフォーマンスの比較

実際に io_method = sync からデフォルトの非同期設定へ戻した際の変化をまとめると、以下のようになります。

項目 同期I/O (旧方式) 非同期I/O (Postgres 18) 変化
リードレイテンシ 高い (100%とする) 約 40% 60% 削減
ストレージ利用効率 低い (逐次実行) 高い (並列実行) 大幅向上
NVMeのポテンシャル アイドル時間が長い キューを埋められる 最大限活用

現代のストレージ(特にクラウド環境の高速なネットワークストレージやローカルNVMe)は、単一の要求を高速に返すよりも、多くの要求を並列に処理する方が得意です。Postgres 18 のこの変更は、まさにハードウェアの進化にソフトウェア側が追いついた形と言えるでしょうか。


まとめ

今回の事例から学べるのは、新しいバージョンの恩恵を受けるためには「ただアップグレードするだけではなく、過去に施した古い最適化を疑う必要がある」という点です。

  • 設定の可視化: postgresql.conf だけでなく、Terraform や CloudFormation などの IaC テンプレートにハードコードされた古いパラメータがないか確認してみる。
  • デフォルト値の信頼: メジャーバージョンアップ時には、まずは「デフォルト設定」で計測を行い、自分たちが明示的に指定している設定が足枷になっていないかチェックする。

「遅い」という状態はエラーではないため、監視ツールでは正常に見えてしまいます。しかし、本来のポテンシャルを 60% も引き出せていないとしたら、それは隠れた大きなコストと言えるかもしれません。Postgres 18 への移行を検討されている方は、この io_method 辺りの設定を一度見直してみると良いかと思います。

参照記事