「なぜPostgreSQLはそんなプランを選んだ?」クエリチューニングで悩んだら、まずはコストベース最適化(CBO)を理解しよう
現場でPostgreSQLを触っていると、たまに「なんでこのクエリ、こんなに遅い実行計画を立てるんだ?」と首を傾げたくなる瞬間があるよね。インデックスは貼ってあるし、データ量もそこまでじゃないはずなのに、なぜかフルスキャンを選んでしまう……。
そうしたとき、ただ闇雲にヒント句やプランナのパラメータをいじり回すのは、ちょっと待ってほしい。PostgreSQLの頭脳である「コストベース最適化(Cost-Based Optimization: CBO)」の仕組みを少し深掘りするだけで、チューニングの景色は一変するんだ。
今回は、PostgreSQLがどうやって「最適」な実行計画を決めているのか、その舞台裏と、現場での付き合い方を解説するよ。
—
CBOの基本:「コスト」とは何を指しているのか?
PostgreSQLのオプティマイザは、クエリが投げられると「考えられる実行計画」をいくつも生成し、それぞれに「コスト」という架空のスコアを割り振るんだ。そして、最も低いコストの計画を採用する。これがCBOの基本だ。
このコスト計算、実は非常にシンプルで、以下の要素の重み付け合計で成り立っている。
- ディスクI/Oコスト: ページをディスクから読み込むコスト。これが一番重い。
- CPUコスト: 行をフィルタリングしたり、ソートしたり、結合したりする演算コスト。
例えば、`seq_page_cost`(シーケンシャルアクセス時のコスト)と `random_page_cost`(ランダムアクセス時のコスト)という設定値を覚えているかな?デフォルトでは `seq_page_cost=1.0` に対して `random_page_cost=4.0` となっている。つまり、PostgreSQLは「ランダムアクセスはシーケンシャルの4倍コストがかかる」と見なしているわけだ。
SSDを使っている現代のインフラでは、この `random_page_cost` を下げてあげるだけで、オプティマイザの判断がインデックス寄りになることは珍しくない。これもCBOへの理解があってこそできるチューニングだよね。
—
統計情報:オプティマイザの「目」
コスト計算の精度を左右するのは、オプティマイザが持っている「統計情報」だ。`pg_statistic` というシステムカタログに格納されている情報のことだよ。
もし、統計情報が古かったらどうなるか?想像してみてほしい。オプティマイザは「このテーブルには数件しかない」と思い込んでNested Loopを選んだけれど、実際には数百万件のデータがあって、結果としてクエリが爆速で終わるはずが数分かかってしまう……。これが「プランナの誤解」の正体だ。
実践:統計情報の確認と更新
もし実行計画が怪しいと思ったら、まずはこのあたりをチェックしてみて。
— テーブルの統計情報がいつ更新されたか確認
SELECT relname, last_analyze, last_autoanalyze
FROM pg_stat_user_tables
WHERE relname = ‘your_table_name’;
— 統計情報が古いなら、手動でANALYZEを投げる
ANALYZE VERBOSE your_table_name;
`ANALYZE` を実行すると、各列の分布状況(ヒストグラムや頻出値)が更新される。これにより、CBOはより正確な「行数予測(Cardinality Estimation)」ができるようになり、適切な結合アルゴリズムやスキャン方式を選択できるようになるんだ。
—
「コスト」を味方にするための3つのステップ
現場でクエリが遅いと相談されたとき、僕はいつもこの順序で考えている。
1. 実行計画の可視化: `EXPLAIN (ANALYZE, BUFFERS)` を使おう。推定コストと実際のコストの乖離を見るのが一番の近道だ。
2. 統計情報の鮮度確認: `ANALYZE` で改善しないか試す。これだけで解決するケースは、実は体感で3割くらいある。
3. 相関(Correlation)の確認: インデックスの列と実際の物理的な行の並び順がバラバラだと、`random_page_cost` が跳ね上がる。この場合は、`CLUSTER` コマンドでデータを物理的に並び替えるか、インデックスの定義を見直す必要がある。
—
最後に:魔法の杖はない
ここまで書いておいてなんだけど、CBOはあくまで「統計に基づく推論」であって、神の視点を持っているわけじゃない。ときには統計情報がどうしても最適解を導けないケースもある。
そんなときは、無理にプランナを追い込むのではなく、クエリの書き方を工夫して「オプティマイザが迷わないような道筋」を作ってあげることが大切だ。例えば、サブクエリをCTEに切り出したり、WHERE句の条件を工夫してインデックスを効かせやすくしたりするようなことだね。
PostgreSQLと付き合うコツは、彼が何を考えているのかを想像すること。「なぜこのプランを選んだんだい?」とEXPLAINの出力と対話するようになれば、君ももう立派なPostgreSQL使いだよ。
何か具体的なクエリで困っていることがあれば、またいつでも相談してくれ。一緒にコードを読み解こう。
コメント