PostgreSQL 18は速いのか、17から2000万件で実測【2026年版】
PostgreSQL 18は本当に速いのか。17.9と18に同じ2000万件を入れて実測したら、目玉の非同期I/Oが効くはずの大きな集計はむしろ遅くなりました。はっきり速くなったのは複合索引の絞り込みだけ。設定ひとつで結果がひっくり返る場所もあります。上げる前に知るべき実データを示します。
目次
PostgreSQL 18は本当に速いのか。17.9と18に同じ2000万件を入れて実測したら、目玉の非同期I/Oが効くはずの大きな集計はむしろ遅くなりました。はっきり速くなったのは複合索引の絞り込みだけ。設定ひとつで結果がひっくり返る場所もあります。上げる前に知るべき実データを示します。
「18にすれば全部速くなる」は、実測すると違った
このブログ自身を PostgreSQL 17.9 から 18 に上げる前に、「実際どれくらい変わるのか」を手元で測りました。17.9 と 18 のコンテナを2つ立て、まったく同じ2000万件のデータを入れて、業務でよくある5種類のテーブルとクエリで比べています。
結果は予想と違いました。PostgreSQL 18の目玉とされる「非同期I/O」が効くはずの大きな集計クエリは、むしろ17より遅くなりました。はっきり速くなったのは1か所(スキップスキャン)だけ。残りは「変わらない」か「設定次第」です。誇張なしの実測値を、まず一覧で出します。
| あなたのテーブル | PostgreSQL 17.9 | PostgreSQL 18 | どうなったか |
|---|---|---|---|
| 業務テーブルの絞り込み (スキップスキャン) | 847ms | 585ms | 速くなった |
| 売上・ログの集計レポート (非同期I/O) | 4.3秒 | 7.1秒 | 逆に遅くなった |
| 多テーブルをたどるJOIN集計 | 1.7秒 | 2.3秒 → 1.1秒 | 設定次第 |
| ログイン(メールで1人検索) | 1ms未満 | 1ms未満 | 変わらない |
| 記事のあいまい検索(部分一致) | 8.2ms | 8.1ms | 変わらない |
「JOINの 2.3秒 → 1.1秒」は、18の非同期I/Oの設定(io_method)を既定値から変えたときの差です。ここがこの記事のいちばん大事なポイントなので、後で詳しく書きます。
使った検証コードは全部 GitHub(postgres-17-18-benchmark) に置いてあります。docker compose とコマンド数本で、誰でも同じ比較を再現・追試できます。数値はあくまで「ある1つの環境での実測」です。鵜呑みにせず、自分の環境で確かめてほしいので公開しています。
なぜ測ったのか
PostgreSQL 18は2025年9月にリリースされた、データベース管理システムの最新メジャーバージョンです。目玉として「非同期I/O」「スキップスキャン」「アップグレード時の統計情報引き継ぎ」などが入り、リリースノートには「速くなる」と書いてあります。
ただ、リリースノートの「速くなる」は条件付きです。どんなテーブルで、どんなクエリで、どれくらい変わるのか。ここがわからないと、本番を止めてまでアップグレードする判断ができません。ちょうどこのブログのデータベースを 17.9 から上げる計画があったので、上げる前に自分のユースケースに近い形で測ることにしました。
以前、主キーをUUIDにすべきかbigintにすべきかを1000万件で検証した記事でも PostgreSQL 18 を使いました。今回はその続きで、「バージョンを上げること自体」で何がどう変わるのかを正面から見ます。
どう測ったか

業務SaaS(プロジェクト管理+行動ログ分析+社内の記事管理)を題材に、実務でありがちな7つのテーブルを1つのデータベースにまとめました。組織・ユーザー・プロジェクト・行動ログ・記事などです。読者が「自分のところもこんなテーブルだ」と当てはめられることを重視しています。
データは「種プール増幅」でリアルさと量を両立
中身が空っぽのダミーデータでは、現実の検索の重さは測れません。かといって、Faker(実在感のある氏名・会社名・文章を生成するライブラリ)で2000万件を1件ずつ作ると日が暮れます。
そこで、Faker でリアルな「種プール」(氏名・会社名・文章を数万件)を一度だけ作り、それを組み合わせて大量行へ COPY で増幅しました。実在感を保ったまま量を稼ぐやり方です。乱数の種を固定しているので、17.9 と 18 にはまったく同じデータが入ります。これでバージョンの差だけを取り出せます。
# Fakerで一度だけ種プールを生成(数万件)→ あとは組み合わせてCOPYで増幅 names = [fake.name() for _ in range(50000)] # 実在感のある氏名 sentences = [fake.text() for _ in range(5000)] # 記事本文の素材 # 行動ログ2000万件は、この種プールを samples して COPY で流し込む
shared_buffers(PostgreSQLが自分用に確保するメモリ)は256MBに絞り、ディスクからの読み込みが起きる状態にしました。行動ログテーブルは2000万件で約2.9GB。各クエリを EXPLAIN (ANALYZE, BUFFERS) で5回流して中央値を取り、環境は Docker 上の PostgreSQL 17.9 と 18、8コア・メモリ7.6GBのマシンです。「40%速い」のような割合ではなく、人間が画面の前で実際に待つ秒数で見ていきます。
いちばん意外だった:大きな集計が18で「遅く」なった

売上やアクセスログを月別・地域別にまとめる「大きな集計」は、PostgreSQL 18の既定で約2倍も遅くなりました。ですが、犯人だと思っていた目玉機能「非同期I/O」は無実でした。本当の原因は、データベースの司令塔であるプランナ(実行計画を立てる部分)が、18では「並列処理(手分け)」をやめて1人で抱え込んでしまったことです。そしてこれは、ちょっとした設定や統計情報の追加で元どおり——いや、それ以上に速くできます。ここでは、その犯人探しの過程を、実際にこの端末で測った数字つきでそのまま追っていきます。
(環境メモ:今回の追試は 16コア・メモリ37GB・ローカルNVMe SSD の端末で、2000万件・同一データを 17.9 と 18.4 の両方に入れて測っています。記事冒頭の数値とは機械が違うため絶対値は前後しますが、「何が起きているか」は同じです。)
期待:「非同期I/Oが効くから速くなるはず」
この集計は、インデックスを使わずテーブル全体を読んで合計するタイプです。ディスクからたくさん読むので、PostgreSQL 18の目玉「非同期I/O」——ざっくり言うと「ディスクに何件もまとめて読み取りを頼み、待っている間に次を進める」仕組み——がいちばん効くはずの場所でした。どれくらい速くなるか、わくわくしながら測りました。
実測:むしろ約2倍に遅くなった
結果は真逆でした。同じSQL・同じデータなのに、17.9で約2.9秒、18(既定)で約5.7秒。5回測っても安定して遅く、誤差ではありません。「速くなるはず」がきれいに裏切られました。

なぜか。実行計画(EXPLAIN)を並べてすぐ目に飛び込んできたのは、「並列か、直列か」の違いでした。
-- PostgreSQL 17.9(既定)→ 2人がかりの「並列」
Gather Merge (Workers Launched: 2) ← 並列の入口
-> Partial GroupAggregate ← 各ワーカーが手分けして集計
-> Parallel Seq Scan on events
実行 2.9秒 / I/O待ち 295ms
-- PostgreSQL 18.4(既定)→ 1人だけの「直列」に転落
GroupAggregate
-> Sort (external merge 298MB) ← 全1000万行を1回でソート
-> Seq Scan on events (Workers Launched: 0) ← 並列なし
実行 6.1秒 / I/O待ち わずか8ms17は2人のワーカーで手分け(並列)、18は1人で全部抱える(直列)。この集計は「2000万行の合計を計算するCPU仕事」が中心なので、手分けをやめれば素直に約2倍かかります。これが遅さの正体でした。
容疑者・非同期I/Oには「アリバイ」があった
では非同期I/Oは何をしていたのか。上の計画の最後の行に注目してください。I/O待ち時間は、17が295ミリ秒、18はわずか8ミリ秒。つまり非同期I/Oは仕事をしていて、むしろディスクの読み取り待ちをほぼ消していました。遅さの原因がI/Oなら、ここが大きくなるはず。なっていない。非同期I/Oは無実です。容疑者にはアリバイがありました。
真犯人:「グループ数の見積りミス」で並列をやめていた
では、なぜ18は並列をやめたのか。プランナの見積りを覗くと、決定的な証拠が出ました。
-- 18 が見積った「集計後のグループ数」 GroupAggregate (rows=8,815,869) ← 約880万グループになると予想していた -- 実際のグループ数(12か月 × 6地域 など) SELECT count(*) FROM (… GROUP BY 月, region) → 144
本当は144グループしかないのに、18は約880万グループと見積もっていました。原因は date_trunc('month', occurred_at) という「日時を月に丸める式」です。これはタイムゾーン次第で答えが変わるSTABLE(不変ではない)な式なので、プランナは「この式が何種類の値を返すか」を事前に推定できず、安全側に倒して「ものすごくたくさん」と見積もります。
グループが880万もあると思い込むと、並列の出口(Gather Merge)を「880万行が通る関所」とみなし、その通行コストを高く見積もります。結果、「並列にすると関所が重い → 直列のほうが安い」と誤判断してしまうのです。
疑いを確かめる:本当に「見積り」が引き金か
仮説が正しければ、見積りまわりを動かすだけで並列に戻るはずです。やってみました。
- ✓関所の通行コスト(
parallel_tuple_cost)を下げる → 18も即座に並列(Gather Merge)に戻った - ✓見積りが当たる
GROUP BY region(6グループ)に変える → 18も普通に並列で実行した
疑いは確信に変わりました。真犯人はグループ数の見積りミスです。ちなみに17も同じ見積りミスをしていますが、コスト計算がギリギリ「並列側」に倒れて助かっていただけ。18はそのギリギリのラインで「直列側」に落ちた、という違いでした。非同期I/Oのための新しいコスト計算が、たまたまこの一線を越えさせた、とも言えます。
じゃあ、どうすれば速くなるのか
原因がわかれば対策は素直です。CPU仕事なので「手分け(並列)」を取り戻せばいい。さらに、そもそもの見積りミスを直せば、もっと速くなります。
-- 対策A:並列度を上げる(18の「直列に倒れる」を力技で押し戻す)
SET max_parallel_workers_per_gather = 8;
-- 対策B:式に対する「拡張統計」でグループ数を正しく教える(根本対策)
CREATE STATISTICS s_events_mr
ON (date_trunc('month', occurred_at)), region
FROM events;
ANALYZE events;
-- → 見積りが 880万 → 144 に直り、巨大ソートが消えて部分集計+並列の最速プランに| 大きな集計(2000万件・この端末での中央値) | 実行時間 | 中身 |
|---|---|---|
| PostgreSQL 17.9(既定) | 2.9秒 | 並列(2ワーカー) |
| PostgreSQL 18.4(既定) | 5.7秒 | 直列に転落(約2倍) |
| 18 + 並列度↑(mpw=8) | 2.9秒 | 並列に復帰=17と同等 |
| 17.9 + 拡張統計 | 1.5秒 | 見積りが直り最速級 |
| 18.4 + 拡張統計 | 1.4秒 | 両版とも最速 |

大事な注意を1つ。拡張統計は18の新機能ではありません(PostgreSQL 10からあり、式に使えるのは14から)。実際17でも18でも同じように 1.4〜1.5秒まで速くなりました。つまりこれは「18だから速い」話ではなく、どのバージョンでも効く一般的な対策です。フェアに見れば、18が既定で遅いのは事実だが、その遅さは設定で消せるし、根本対策をすれば17も18もまとめて速くなる、というのが正確なところです。なお work_mem を増やす・io_method を変える、はこの集計には効きませんでした(CPU仕事なので。work_mem増はむしろ直列を後押しして悪化)。
では非同期I/Oは「どこで」効くのか──ここが今回いちばん大事
無実とわかった非同期I/Oですが、まったく無意味なわけではありません。非同期I/Oは「ディスクの読み取り待ちを隠す機構」です。キャッシュを空にして(cold)ディスクから本当に読ませると、多テーブルを行き来する深いJOINで、I/O待ち時間そのものは 1.65秒 → 0.05秒へ桁違いに減りました。機構としては本物です。

ところが——実時間(実際に待つ秒数)はほとんど縮みません(sync 1.79秒 → worker 1.56秒)。理由は単純で、今回の高速NVMeはそもそも1回の読み取りが速いので、その待ちをいくら上手に隠しても、全体の時間はたいして変わらないからです。「I/O待ちが激減」という派手な数字は、実時間の得とは別物だと知っておくべきです。
同時アクセスが多いと、むしろ逆転する
ここまでは「1回ずつ」の話でした。でも現実のサービスは、たくさんの人が同時にアクセスします。そこでpgbenchで同時接続数を増やしながら、ランダムな読み取りスループット(1秒あたり何件さばけるか)を測りました。結果は直感の逆でした。

同時接続が増えると、非同期I/Oの優位は消え、AIOを切った sync がいちばん速くなりました。そして既定の worker が最も遅いのです(同時16接続で sync 約3000件/秒 に対し、既定worker 約1450件/秒)。からくりはこうです。多数の接続そのものが、すでにディスクへ大量の読み取りを同時に投げているので、非同期I/Oの「先読みでまとめて投げる」働きは不要になります。むしろ worker 方式は、専用プロセス(既定でたった3つ)へ仕事を渡すぶんのIPCコストと、その3プロセスの奪い合いが純粋な足かせになります。実際、io_workers を3→16に増やすと、中程度の同時接続で 1450 → 2265件/秒 に改善しました。既定の3が詰まっていた証拠です。
さらに、テーブルがメモリに載らない「遅いディスク」状況(読み取りIOPSを絞って再現)でも測りましたが、こちらはworker・sync・io_uring すべて横並びでした。非同期I/Oはディスクの“天井”そのものを上げるわけではないからです。
まとめると、非同期I/Oが理屈どおり効くのは「高レイテンシ(1回の読み取りが遅い)ストレージで、1つの大きなスキャンを流すとき」——つまり遅いネットワークディスクでの分析クエリのような場面に絞られます。今回の環境(速いNVMe・多くは小さなクエリ)では、その本領は出ませんでした。「18にすればI/Oが速くなる」は、ワークロードとストレージ次第でまったく当てはまらない、というのが正直な実測結果です。
アップグレード時に見直すべき設定(まとめ)
この一件から、18へ上げるときにチェックすべき点が見えてきました。
- ①大きな集計・分析クエリがあるなら
max_parallel_workers_per_gatherを見直す。18は控えめに直列へ倒れることがあります。CPUに余裕があるなら少し上げると安全です。 - ②式で
GROUP BYしているクエリには拡張統計を。月丸めや関数での集計は見積りを外しやすい。CREATE STATISTICSで一発で直り、17でも効きます。 - ③
io_methodは「既定のworkerが最速」と思い込まない。とくに同時接続が多いワークロードでは、workerがいちばん遅いことすらあります。io_uring(新しめのLinux)やsyncと必ず実測で比べ、workerのままならio_workersを絞りすぎない(増やす方向で見直す)。
そして何より、数字は環境で大きく変わります。CPUの数、メモリ、ディスクの速さ(クラウドの遅いディスクか、ローカルの高速NVMeか)、そして同時接続の多さで、結論はここまで見てきたとおり真逆にひっくり返り得ます。「18にすれば速くなる/遅くなる」を鵜呑みにせず、自分のデータと自分のクエリで、上げる前に必ず測ってください。検証コードは末尾に置いてあり、誰でも同じ追試ができます。
はっきり速くなったのはここ:業務テーブルの絞り込み

5つのうち、18でちゃんと速くなったのがこれです。マルチテナント(複数の会社が1つのシステムを共有する)SaaSでよくある、行動ログテーブルを期間で絞り込むクエリです。
このテーブルには「種類(閲覧・クリックなど8種類)と日時」をセットにした複合インデックス(索引)を張っています。実務でありがちな構成です。ところが、クエリが「日時だけ」で絞ると、PostgreSQL 17 はこの索引をうまく使えません。先頭の「種類」が指定されていないからです。結果、テーブル全体を頭から舐める シーケンシャルスキャン(全表走査) に倒れます。
PostgreSQL 18のスキップスキャンは、ここで「種類」の8種類を内部で読み飛ばしながら、日時の索引だけを使えるようになりました。同じSQL・同じデータ・同じインデックスなのに、実行計画が別物に切り替わります。実測では 847ms → 585ms。読み取ったディスクブロックも、17の約37万から18は約15万へ大きく減っていました。
-- PostgreSQL 17.9(複合索引の先頭列が無いので全表走査に倒れる) Parallel Seq Scan on events -- 約37万ブロック読む -- PostgreSQL 18(スキップスキャンで索引を使う) Bitmap Index Scan on idx_events_type_time -- 約15万ブロックで済む Index Searches: 17 -- 索引を何回かに分けて探索(これがスキップスキャン)
ここで大事なのは、これが「インデックスを張り直さなくても効く」点です。アプリのコードも、テーブル定義も、インデックスもそのまま。バージョンを上げるだけで、これまで全表走査だったクエリが索引を使うようになります。多くの会社が持っている「とりあえず複合インデックスを張った業務テーブル」が、いちばん恩恵を受けます。
速くなる幅は、絞り込んだ結果が少ないほど大きくなります。同じクエリを1000万件・もっと狭い期間で試したときは 34ms → 5.6ms(約6倍) と劇的でした。今回の2000万件では結果が約19万件と多く、その分だけ差は縮みます(それでも索引が効くぶん速い)。「自分の検索が何件返すか」「複合索引の先頭列が何種類あるか」で効き目が変わります。先頭列が何百万種類もあると、読み飛ばす回数が増えて旨味は減ります。
深いJOINは「既定だと遅い、設定を変えると速い」

行動ログ→プロジェクト→組織→ユーザーと4つのテーブルをたどる、深いJOIN集計も測りました。ここがいちばん「設定次第」がはっきり出た場所です。
| 深いJOIN集計(2000万件) | 実行時間 |
|---|---|
| PostgreSQL 17.9 | 1.7秒 |
| PostgreSQL 18(worker・既定) | 2.3秒(17より遅い) |
| PostgreSQL 18(io_uring) | 1.1秒(17より速い) |
既定の worker では17より遅い(1.7秒→2.3秒)のに、io_method=io_uring に変えると17より速くなりました(1.1秒)。同じ18でも、I/Oの方式ひとつで「遅い」と「速い」がひっくり返ります。
ここから言えるのは1つ。PostgreSQL 18に上げたら、まず io_method を自分のワークロードで比べるべきだということです。既定の worker が常に最適とは限りません。Linuxで新しめのカーネルなら io_uring を試す価値があります。
認証とあいまい検索は、まったく変わらなかった

ログイン時の「メールアドレスで1人を引く」点ルックアップは、17も18も1ミリ秒未満で一瞬。元々インデックスで一瞬なので、ここはアップグレードしても体感は変わりません。記事のあいまい検索(部分一致)も 8.2ms → 8.1ms と、ほぼ同じでした。
これは悪い知らせではありません。「認証やマスタ参照のような軽いクエリ中心のシステムは、18に上げても速度面の体感は変わらない」とわかること自体が、アップグレード判断の材料になります。速くなる/遅くなるの変化が出るのは、大きなテーブルをまとめて読むような重いクエリだけです。
アップグレード当日の落とし穴:統計情報の引き継ぎ

速度の良し悪しとは別に、運用で地味に効くのが「統計情報の引き継ぎ」です。これは知らないとアップグレード当日にハマります。
PostgreSQLは「どのデータがどれくらいの量・分布で入っているか」という統計情報をもとに、最適な実行計画を立てます。ところが PostgreSQL 17 までは、pg_upgrade(バージョン間移行ツール)でアップグレードするとこの統計情報が引き継がれませんでした。移行直後はデータベースが「中身を何も知らない」状態になり、ANALYZE という統計の取り直しを手動で走らせるまで、誤った実行計画で遅くなります。
この ANALYZE はテーブルが大きいほど時間がかかります。今回の2000万件では約1秒で済みましたが、数千万〜億行規模になると数分かかることもあります。その間、本番が本調子に戻りません。PostgreSQL 18は pg_upgrade が統計情報を引き継ぐようになり、移行が終わった瞬間から最初のクエリが正しい計画で動きます。アップグレード直後の「なぜか全部遅い」時間がゼロになります。地味ですが、ダウンタイムを短くしたい本番運用では大きい改善です(拡張統計だけは引き継がれない、--no-statistics で無効化できる、といった細かい注意はあります)。
セキュリティと、廃止・非推奨になったもの

速度以外で、上げる前に知っておくべき変更もあります。実際に17.9と18のコンテナで挙動を確認しました。
データの破損検知が既定でオンに
PostgreSQL 18では、新しくデータベースを初期化するとデータチェックサム(保存データの破損を検知する仕組み)が既定で有効になりました。実際に確認すると、17.9は data_checksums = off、18は on です。安全側に倒れた良い変更ですが、注意点があります。pg_upgrade は移行元と移行先でチェックサム設定が一致している必要があるため、チェックサム無しの古いクラスタから上げるときは、移行先を --no-data-checksums で初期化するなどの調整が要ります。
古いパスワード方式(MD5)が非推奨に
古い認証方式である MD5 パスワードが正式に非推奨になりました。18でMD5パスワードのユーザーを作ろうとすると、はっきり警告が出ます(17では何も出ません)。
-- PostgreSQL 18 で MD5 パスワードを設定すると…
WARNING: setting an MD5-encrypted password
DETAIL: MD5 password support is deprecated and will be removed
in a future release of PostgreSQL.将来のバージョンで完全に削除される予定です。MD5を使っているなら、より安全な scram-sha-256 方式への移行を今のうちに進めておくべきです。
削除されたもの(上げる前に要確認)
PostgreSQL 18 で完全に削除された主なものは次の通りです。古い環境から上げる場合は、これらに依存していないか先に確認してください。
- •
pg_stat_walの一部の列(wal_write/wal_syncなど)。監視ツールが参照していると壊れる可能性があります - •OpenSSL 1.1.1 より古いバージョンのサポート
- •ビルド時オプション
--disable-spinlocks/--disable-atomics、および古いCPUアーキテクチャ(HP-PA)
なお、前回の主キー検証でも触れた uuidv7()(時系列順に並ぶID生成関数)も18の正式機能です。あわせて、生成列(計算で値が決まる列)の既定が「保存せず読むたび計算する」方式(VIRTUAL)に変わった点も、テーブル定義を移すときの確認ポイントです。
この検証の限界
フェアに書いておきます。以下は今回測れていません。
- ?ストレージごとの差。非同期I/Oの効果はディスクの速さ・種類に強く依存します。今回の「18で集計が遅い」も、別のストレージでは結果が変わる可能性があります
- ?もっと大きな規模。今回は検証マシンの都合で2000万件を上限にしました(リポジトリのコードは
SCALEを変えれば5000万・1億件も流せます) - ?多数の接続が同時に書き込み続ける高並行スループット。単発の INSERT / UPDATE / DELETE は別途測りました(本文参照)が、大量同時書き込みの負荷は測れていません
数値そのものより、「どの種類のテーブル・クエリで変わり、どこは変わらないか」「設定でひっくり返る場所がある」という地図として読んでもらえればと思います。条件を変えた追試は、後述のリポジトリでそのままできます。
書き込み(INSERT / UPDATE / DELETE)は遅くなる?──測ってみた
ここまでは読み取りの話でした。では書き込みはどうか。とくに気になるのは、18が新しいデータベースでデータチェックサム(保存データの破損を検知する仕組み)を既定オンにした点です。書き込みのたびにチェックサムを計算するぶん、INSERTやUPDATEが重くなるのでは——という疑いです。実際に300万件のINSERT・UPDATE・DELETE・VACUUMを、17.9と18.4の両方で測りました。
| 書き込み操作(300万件) | 17.9(チェックサムoff) | 18.4(チェックサムon) | 差 |
|---|---|---|---|
| INSERT(一括投入) | 3.8秒 | 3.9秒 | ほぼ同じ(数%) |
| UPDATE(約60万行) | 2.27秒 | 2.30秒 | 誤差 |
| DELETE(約43万行) | 0.62秒 | 0.61秒 | 誤差 |
| VACUUM | 0.72秒 | 0.73秒 | 誤差 |
結論は「ほぼ互角」です。INSERTで18がほんの数%遅いかな、という程度で、UPDATE・DELETE・VACUUMは誤差の範囲でした。念のため、チェックサムだけを切り分けるために同じ18でチェックサムON/OFFを比べる追加実験もしましたが、差は測定のばらつきに埋もれて見えませんでした。
つまり、18でチェックサムが既定オンになっても、ふつうの書き込み性能は実質的に変わりません。「破損をいち早く検知できる」という安全のメリットだけを、ほぼコストなしで受け取れる、という結果です。書き込みを理由に18を避ける必要はなさそうです(※今回測ったのは単発のCRUDです。多数の接続が同時に書き込み続ける高並行スループットは別テーマで、ここには含みません)。
まとめ:上げる価値はあるか
「全部が速くなる」わけではありませんでした。むしろ、目玉の非同期I/Oが効くと思っていた大きな集計は、今回の環境では逆に遅くなりました。それでも、結論は「上げる価値はある」です。理由は3つです。
① 複合インデックスを張った業務テーブルがあるなら、スキップスキャンだけで価値がある。コードもインデックスもそのままで、全表走査だったクエリが索引を使うようになります。② 統計情報の引き継ぎ・チェックサム既定オン・MD5非推奨など、運用とセキュリティの改善がまとまって入っている。これは計画的に上げる十分な理由になります。
③ ただし、上げたら必ず自分の環境で測ること。とくに大きな集計やJOINを多用しているなら、io_method を worker / sync / io_uring で比べてください。既定値が最適とは限らず、ここを変えるだけで「遅い」が「速い」に変わることがあります。
このブログのデータベースも、この結果を踏まえて 18 へ上げることにしました(io_method は実環境で比較してから決めます)。次は書き込み負荷でも測ってみたいと思っています。
参照元・検証コード
- •検証コード一式(GitHub) — docker-compose・データ生成・ベンチ・グラフ。
make数本で再現できます - •PostgreSQL 18 リリースノート(公式)
- •UUIDv4 vs v7 vs bigint、PostgreSQLで1000万件検証(関連記事)
- •計測手法:
EXPLAIN (ANALYZE, BUFFERS)5回の中央値、PostgreSQL 17.9 / 18、同一データ(乱数固定)、shared_buffers=256MB、メモリ7.6GB・8コア

堀川 慎
Backend Engineer / AWS / Django / Go