0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

1. はじめに

PostgreSQL18(2025年9月25日リリース)の目玉機能の一つが、非同期I/Oの導入です。

これまでのPostgreSQLは、ディスクI/Oを同期的に処理していました。1回のI/Oリクエストを発行したら、その完了を待ってから次の処理に進む、という流れです。非同期I/Oはこの制約を取り払い、複数のI/Oリクエストを並行して発行できるようにします。

「並行してI/Oが走る」と聞くと難しそうに聞こえますが、本記事では実際にt3.micro(Amazon Linux 2023)とRHEL 10の2環境でpgbenchを動かしながら、17と18の数字の違いを観察していきます。

なお、結論を先に述べると、今回の検証環境(t3.micro + EBS gp3)ではPostgreSQL18の非同期I/Oの効果を再現することはできませんでした。原因の特定には至っていませんが、EBSのレイテンシ、CPU不足、workerプロセスの管理コストなど複数の要因が絡み合っている可能性があります。非同期I/Oの効果を実感するにはNVMe SSD直結環境や高スペックなインスタンスが必要と考えられます。それでも pg_stat_io を通じてI/Oの挙動の違いを数字で観察することはできますので、その点に着目しながら読み進めてください。

本記事は「pgbenchで見るPostgreSQL18の変化」シリーズの第1弾です。
PostgreSQL17との比較を中心に、非同期I/Oが実際の読み取り処理にどう影響するかを手を動かして確認していきます。

1.1 この記事で確認すること

  • PostgreSQL18の非同期I/Oの仕組みを理解する
  • io_method パラメータの設定と動作の違いを確認する
  • pgbench標準テーブルを使ったSeq Scanワークロードで17と18の性能差を実測する
  • pg_stat_io ビューでI/O状況を観察する

1.2 検証環境

環境A(Amazon Linux 2023)

項目 内容
OS Amazon Linux 2023
PostgreSQL 17.8 / 18.3
インスタンスタイプ t3.micro(2 vCPU、1GiB メモリ) EBS 30GB gp3
io_method sync(PG17) / worker(PG18デフォルト)
ベンチマークツール pgbench

環境B(RHEL 10)

項目 内容
OS Red Hat Enterprise Linux 10.1
PostgreSQL 18.4(PGDG RPM)
インスタンスタイプ t3.micro(2 vCPU、1GiB メモリ) EBS 30GB gp3
io_method io_uring
ベンチマークツール pgbench

2. 非同期I/Oとは

2.1 これまでの同期I/Oの動き

PostgreSQL17までのI/Oは、基本的に**同期(Synchronous)**でした。バッファキャッシュにないブロックを読み込む際、以下の流れで処理が進みます。

[プロセス] → read()システムコール発行 → [OSカーネル] → ディスクからブロックを読み込む
              ↑ここで待機                                            ↓
[プロセス] ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← 完了通知を受け取る

Seq Scanで大量のブロックを読み込む場合、「読んで待つ・読んで待つ」を繰り返すことになり、CPUが手待ち状態になる時間が発生します。

2.2 PostgreSQL18の非同期I/Oの動き

PostgreSQL18では、複数のブロックをまとめてI/Oリクエストとして発行できるようになりました。

[プロセス] → 複数ブロックのI/Oリクエストをまとめて発行
              ↓
           I/O完了を待つ間、別の処理(次のリクエスト準備など)を実行
              ↓
           完了したブロックから順次処理

効果が出やすいのは以下のケースです。

  • Seq Scan:大きなテーブルを先頭から末尾まで読むワークロード
  • Bitmap Heap Scan:インデックスで絞り込んだ後、ヒープを読みに行く処理
  • VACUUM:dead tupleを探しながらテーブル全体を走査する処理

2.3 io_method パラメータ

PostgreSQL18では io_method パラメータで動作モードを切り替えられます。

説明
worker(デフォルト) バックグラウンドワーカーを使った非同期I/O。io_workers で並行数を設定
sync 従来の同期I/O。PG17以前と同じ動作
io_uring Linux の io_uring API を使った非同期I/O。カーネルのビルドオプション依存のため、環境によっては選択不可

今回の検証環境(Amazon Linux 2023)では io_uring は使用できませんでした。io_methodworker を使います。

io_uring を設定しようとしたところ、以下のエラーが発生しました。

LOG:  invalid value for parameter "io_method": "io_uring"
HINT:  Available values: sync, worker.

RHEL 10.1(カーネル 6.12)にPGDG RPMのPostgreSQL18をインストールしたところ、インストール後にio_uring を設定する事で利用可能でした。Rocky Linux 10 / AlmaLinux 10でも同様に使用できると考えられますが、本記事ではRHEL 10のみで確認しています。

3. postgresql.confの設定

3.1 検証用の設定項目

今回の検証に合わせて、以下のパラメータを設定します。

パラメータ PG17デフォルト PG18デフォルト 検証値 説明
io_method (なし) worker worker 非同期I/OのI/Oバックエンド。PG18で新設
io_workers (なし) 3 3 worker モード時のバックグラウンドワーカー数
effective_io_concurrency 1 16 16 通常クエリ(Seq Scanなど)の並行I/O数。PG18でデフォルトが引き上げられた
maintenance_io_concurrency 10 16 16 VACUUM等のメンテナンス処理の並行I/O数。同じくPG18でデフォルトが引き上げられた
shared_buffers 128MB 128MB 128MB t3.microに合わせてデフォルトのまま。バッファキャッシュに収まらないデータを読み出すことで非同期I/Oの効果が測りやすくなる
autovacuum on on off 計測中に自動VACUUMが走って結果がぶれないよう無効化する
logging_collector off off on ログをファイルに蓄積する(PGDGのRPMパッケージではデフォルトで on の場合あり)
track_io_timing off off on pg_stat_ioread_time / write_time を有効化する

autovacuum = off はあくまで検証目的の設定です。本番環境では必ず有効にしてください。

postgresql.conf を変更したら、PostgreSQLを再起動してください。

$ sudo systemctl restart postgresql

3.2 設定確認

$ psql -U postgres -d pgbench_test -c "SELECT name, setting FROM pg_settings WHERE name IN ('io_method', 'io_workers', 'effective_io_concurrency', 'maintenance_io_concurrency', 'track_io_timing') ORDER BY name;"
            name             | setting
-----------------------------+---------
 effective_io_concurrency    | 16
 io_method                   | worker
 io_workers                  | 3
 maintenance_io_concurrency  | 16
 track_io_timing             | on
(5 rows)

4. pgbenchで性能を比較する

4.1 pgbench標準テーブルの確認

pgbenchの標準テーブル(pgbench_accounts など)を使います。shared_buffers に収まらないデータ量であることが重要です。

スケールファクタ100で初期化すると pgbench_accounts が1,000万行になります。

$ pgbench -i -s 100 -U postgres pgbench_test

pgbench_accounts が1,281MB(1,000万行)あり、t3.micro(shared_buffers 128MB)に対して十分な大きさです。Seq Scanでバッファキャッシュに収まらないブロックを読み出すことができます。

テーブルサイズを確認します。

$ psql -U postgres -d pgbench_test -c "SELECT relname, pg_size_pretty(pg_relation_size(oid)) AS table_size FROM pg_class WHERE relname LIKE 'pgbench_%' ORDER BY pg_relation_size(oid) DESC;"
       relname        | table_size
----------------------+------------
 pgbench_accounts     | 1281 MB
 pgbench_accounts_pkey| 214 MB
 pgbench_tellers      | 48 kB
 pgbench_tellers_pkey | 40 kB
 pgbench_branches_pkey| 16 kB
 pgbench_branches     | 8192 bytes
 pgbench_history      | 0 bytes
(7 rows)

4.2 カスタムスクリプトの作成

pgbench_accounts に対してSeq Scanを繰り返すスクリプトを用意します。

$ mkdir -p ./pgsql
$ cat > ./pgsql/test18_seq_scan.sql << 'EOF'
SELECT count(*) FROM pgbench_accounts WHERE filler IS NOT NULL;
EOF

filler 列はインデックスを持たないため、必ずSeq Scanになります。

4.3 pgbenchの実行

バッファキャッシュをクリアしてから実行することで、ディスクからの読み取りが発生する条件をそろえます。

# バッファキャッシュをクリア(検証環境のみ)
$ sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'

# PostgreSQL17での実行
$ pgbench -c 2 -j 2 -T 60 \
  -f ./pgsql/test18_seq_scan.sql \
  --no-vacuum \
  -U postgres pgbench_test
pgbench結果 PostgreSQL17 ``` pgbench (17.8) transaction type: ./pgsql/test18_seq_scan.sql scaling factor: 1 query mode: simple number of clients: 2 number of threads: 2 maximum number of tries: 1 duration: 60 s number of transactions actually processed: 14 number of failed transactions: 0 (0.000%) latency average = 8870.114 ms initial connection time = 16.238 ms tps = 0.225476 (without initial connection time) ```
$ sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'

# PostgreSQL18での実行(worker)
$ pgbench -c 2 -j 2 -T 60 \
  -f ./pgsql/test18_seq_scan.sql \
  --no-vacuum \
  -U postgres pgbench_test
pgbench結果 PostgreSQL18 ``` pgbench (18.3) transaction type: ./pgsql/test18_seq_scan.sql scaling factor: 1 query mode: simple number of clients: 2 number of threads: 2 maximum number of tries: 1 duration: 60 s number of transactions actually processed: 12 number of failed transactions: 0 (0.000%) latency average = 10227.448 ms initial connection time = 15.959 ms tps = 0.195552 (without initial connection time) ```

4.4 実測結果

TPS

バージョン io_method io_workers effective_io_concurrency 1回目 2回目 3回目
PostgreSQL17 sync - 1 0.225 0.212 0.225
PostgreSQL18 worker 3 16 0.196 0.201 0.196
PostgreSQL18 worker 1 16 0.198 0.202 0.200
PostgreSQL18 worker 1 1 0.214 0.230 0.215

平均レイテンシ (ms)

バージョン io_method io_workers effective_io_concurrency 1回目 2回目 3回目
PostgreSQL17 sync - 1 8870.114 9451.279 8881.796
PostgreSQL18 worker 3 16 10227.448 9933.554 10208.560
PostgreSQL18 worker 1 16 10105.138 9922.754 10019.021
PostgreSQL18 worker 1 1 9354.123 8689.363 9285.401

io_workers = 3 / effective_io_concurrency = 16(PostgreSQL18デフォルト)ではPostgreSQL17より性能が低下したが、io_workers = 1 / effective_io_concurrency = 1 に絞り込むとPostgreSQL17と同等かわずかに上回る結果となりました。t3.micro + EBS環境では並行I/O数を増やすことでオーバーヘッドが生じており、PostgreSQL18の非同期I/Oの恩恵を受けるにはより高スペックなインスタンスが適していると思われます。

5. pg_stat_io でI/Oの変化を観察する

PostgreSQL16から導入された pg_stat_io ビューでは、I/Oの詳細をバックエンドの種別・コンテキスト単位で確認できます。track_io_timing = on を設定していると read_time が計測できます。

5.1 Seq Scan実行の確認

# 統計情報リセット
$ psql -U postgres -d pgbench_test -c "SELECT pg_stat_reset_shared('io');"

# SeqScan実行
$ psql -U postgres -d pgbench_test -c "SELECT count(*) FROM pgbench_accounts WHERE filler IS NOT NULL;"

# 統計情報の確認
$ psql -U postgres -d pgbench_test -c "SELECT backend_type, object, context, reads, round(read_time::numeric, 2) AS read_time_ms, evictions FROM pg_stat_io WHERE reads > 0 ORDER BY reads DESC;"
統計情報 pg_stat_io PostgreSQL17 ```shell backend_type | object | context | reads | read_time_ms | evictions ------------------------------------------------------------------------ background worker | relation | bulkread | 108895 | 14460.39 | 0 client backend | relation | bulkread | 55040 | 7233.36 | 0 client backend | relation | normal | 42 | 13.86 | 0 background worker | relation | normal | 7 | 2.19 | 0 (4 rows) ```
統計情報 pg_stat_io PostgreSQL18 ```shell backend_type | object | context | reads | read_time_ms | evictions ----------------------------------------------------------------------- background worker | relation | bulkread | 8565 | 16176.25 | 96 client backend | relation | bulkread | 4284 | 8071.74 | 48 client backend | relation | normal | 25 | 17.40 | 25 (3 rows) ```

pg_stat_io の結果(context = 'bulkread' がSeq Scanに対応する読み取り):

background worker

context 項目 PostgreSQL17 PostgreSQL18
bulkread reads 108,895 8,565
bulkread read_time_ms 14,460.39 16,176.25
bulkread evictions 0 96
normal reads 7 -
normal read_time_ms 2.19 -
normal evictions 0 -

client backend

context 項目 PostgreSQL17 PostgreSQL18
bulkread reads 55,040 4,284
bulkread read_time_ms 7,233.36 8,071.74
bulkread evictions 0 48
normal reads 42 25
normal read_time_ms 13.86 17.40
normal evictions 0 25

PostgreSQL18のread回数はPostgreSQL17の約1/13に減っています。一方でread_time_msはPostgreSQL18の方がやや高く、1回のI/Oリクエストあたりのレイテンシが大きいことが分かります。

6. VACUUMへの影響を確認する

非同期I/OはVACUUMにも効果があります。maintenance_io_concurrency パラメータがVACUUMの並行I/O数を制御します。

dead tupleを意図的に作ってからVACUUMを計測します。毎回同じ条件になるよう、VACUUM後に再度UPDATEしてdead tupleを作ってから次の計測を行います。

# dead tupleを作る
psql -U postgres -d pgbench_test -c "UPDATE pgbench_accounts SET filler = repeat('x', 84) WHERE aid <= 500000;"
 
# VACUUM前の状態確認
psql -U postgres -d pgbench_test -c "SELECT n_dead_tup, n_live_tup FROM pg_stat_user_tables WHERE relname = 'pgbench_accounts';"
 
# VACUUM時間計測
time psql -U postgres -d pgbench_test -c "VACUUM ANALYZE pgbench_accounts;"

VACUUM所要時間 (real)

バージョン io_method io_workers maintenance_io_concurrency 1回目 (秒) 2回目 (秒) 3回目 (秒)
PG17 sync - 10 7.846 7.739 7.782
PG18 worker 3 16 15.924 16.100 15.451
PG18 worker 1 1 16.031 16.265 17.338

VACUUMもSeq Scanと同様に、io_workersmaintenance_io_concurrency を下げてもPostgreSQL18の遅さは改善しませんでした。特にPostgreSQL18はPostgreSQL17の約2倍の時間を要しており、Seq Scan時と同様にI/O並列化のメリットよりもオーバーヘッドの影響が大きかった可能性があります。

7. RHEL 10で io_uring を試す

Amazon Linux 2023ではPGDGパッケージが io_uring 未対応だったが、RHEL 10.1(カーネル6.12)にPGDG RPMのPostgreSQL18.4をインストールしたところ、postgresql.confのio_methodio_uring を設定する事で有効化できました。

postgres=# SHOW io_method;
 io_method
-----------
 io_uring
(1 row)

7.1 pgbenchの実行

同じカスタムスクリプトと条件で3回計測しました。

sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'
pgbench -c 2 -j 2 -T 60   -f ./pgsql/test18_seq_scan.sql   --no-vacuum   -U postgres pgbench_test
pgbench結果 RHEL10 / PostgreSQL18 ``` pgbench (18.4) transaction type: ./pgsql/test18_seq_scan.sql scaling factor: 1 query mode: simple number of clients: 2 number of threads: 2 maximum number of tries: 1 duration: 60 s number of transactions actually processed: 14 number of failed transactions: 0 (0.000%) latency average = 9487.901 ms initial connection time = 17.325 ms tps = 0.210795 (without initial connection time) ```
pgbench (18.4)
transaction type: ./pgsql/test18_seq_scan.sql
scaling factor: 1
query mode: simple
number of clients: 2
number of threads: 2
maximum number of tries: 1
duration: 60 s
number of transactions actually processed: 14
number of failed transactions: 0 (0.000%)
latency average = 9590.597 ms
initial connection time = 17.188 ms
tps = 0.208538 (without initial connection time)
pgbench (18.4)
transaction type: ./pgsql/test18_seq_scan.sql
scaling factor: 1
query mode: simple
number of clients: 2
number of threads: 2
maximum number of tries: 1
duration: 60 s
number of transactions actually processed: 14
number of failed transactions: 0 (0.000%)
latency average = 9166.191 ms
initial connection time = 17.308 ms
tps = 0.218193 (without initial connection time)

7.2 環境の比較(PostgreSQL17・18(AL2023)・18(RHEL10))

TPS

バージョン / 環境 OS io_method io_workers effective_io_concurrency 1回目 2回目 3回目
PostgreSQL17 Amazon Linux 2023 sync - 1 0.225 0.212 0.225
PostgreSQL18 Amazon Linux 2023 worker 3 16 0.196 0.201 0.196
PostgreSQL18 Amazon Linux 2023 worker 1 16 0.198 0.202 0.200
PostgreSQL18 Amazon Linux 2023 worker 1 1 0.214 0.230 0.215
PostgreSQL18 RHEL 10 io_uring - 16 0.211 0.209 0.218

平均レイテンシ (ms)

バージョン / 環境 OS io_method io_workers effective_io_concurrency 1回目 2回目 3回目
PostgreSQL17 Amazon Linux 2023 sync - 1 8870.114 9451.279 8881.796
PostgreSQL18 Amazon Linux 2023 worker 3 16 10227.448 9933.554 10208.560
PostgreSQL18 Amazon Linux 2023 worker 1 16 10105.138 9922.754 10019.021
PostgreSQL18 Amazon Linux 2023 worker 1 1 9354.123 8689.363 9285.401
PostgreSQL18 RHEL 10 io_uring 3 16 9487.901 9590.597 9166.191

io_uring(RHEL 10)はPostgreSQL18デフォルト(worker / effective_io_concurrency = 16)と比べてTPSが改善し、PostgreSQL17に近い水準となりました。ただしt3.micro + EBSという条件は同じであり、EBSレイテンシのボトルネックは変わらないため、劇的な差にはなりませんでした。io_uring の検証に関しては、それなりに高スペックな環境が必要と思われます。

8. もっと深く学びたい方へ

公式ドキュメント

ページ URL
pg_stat_io https://www.postgresql.jp/document/18/html/monitoring-stats.html#MONITORING-PG-STAT-IO-VIEW
io_method パラメータ https://www.postgresql.jp/document/18/html/runtime-config-resource.html#GUC-IO-METHOD
effective_io_concurrency https://www.postgresql.jp/document/18/html/runtime-config-resource.html#GUC-EFFECTIVE-IO-CONCURRENCY
PostgreSQL18 リリースノート https://www.postgresql.jp/document/18/html/release-18.html

書籍

書籍名称 内容
[改訂3版]内部構造から学ぶPostgreSQL―設計・運用計画の鉄則 I/O周りのアーキテクチャを理解するのに役立ちます
PostgreSQL実践入門 ─⁠─アーキテクチャ、運用監視、性能改善 最新版の実践知識として参照しています

9. まとめ

本記事では、PostgreSQL18の非同期I/Oについて以下を確認しました。

  • PostgreSQL18のデフォルト io_methodworker。バックグラウンドワーカーが複数のI/Oリクエストを並行処理する
  • io_uring はカーネルのビルドオプション依存のため、利用前に SHOW io_method で選択肢を確認する
  • effective_io_concurrencymaintenance_io_concurrency のデフォルトがPostgreSQL17の1/10からPostgreSQL18で両方16に引き上げられた
  • track_io_timing = on にしておくと pg_stat_ioread_time でI/O待ち時間の変化を数字で追える

t3.micro + EBS(gp3)環境での検証では、PostgreSQL18(io_method = worker)はPostgreSQL17と比べてTPSがやや低下する結果となりました。pg_stat_io のPostgreSQL18のread回数に関しては、非同期I/Oにより1回のI/O要求でより多くのブロックを扱い、PostgreSQL18では非同期I/OによりI/O要求の集約方法が変化するため、readsの単純比較だけで実際のディスクアクセス量を評価することはできません。

pgbenchで大容量のデータでの試験は可能ですが、優位な差が発生する状況に持っていくのは、今回は出来なかったのが非常に残念です。ですが、pg_stat_ioを確認する事で、差が確認できることはこの記事から学べるポイントとなっています。情報を集めて、優位な差が現れる条件を探していきたいと思います(t3.microのCPU不足、EBS gp3のレイテンシ etc...)。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?