「なぜか遅い」を卒業する。PostgreSQLのコストベース最適化と統計情報の付き合い方
現場でパフォーマンスチューニングをしていると、必ず一度は壁にぶつかるはずです。「インデックスは貼ったはずなのに、なぜかフルスキャン(Seq Scan)されるんだよな…」というやつ。
インデックスを貼るだけがチューニングじゃない。実は、PostgreSQLの「脳みそ」であるプランナに、データを正しく理解してもらうことが、最速への近道なんです。
今日は、PostgreSQLの統計情報とコスト計算の仕組みについて、少しだけ深く掘り下げてみましょう。
—
1. プランナは「占い師」ではなく「数学者」だ
PostgreSQLのプランナは、クエリが投げられた瞬間に「どの実行計画が一番速いか」を計算します。その時、実際に全データを舐めて比較するわけじゃありません。そんなことをしたらクエリより先に計算が終わってしまいますよね。
プランナが頼りにしているのは、`pg_stats` というビューに格納された統計情報です。
例えば、「このカラムには何種類の値があるか(n_distinct)」「nullはどれくらいあるか」「どんな値が分布しているか」といったサマリー情報です。プランナはこれをもとに、「この条件なら全件の1%くらいしかヒットしないだろうから、インデックスを使った方が速いな」とコストを算出します。
逆に言えば、この統計情報が古ければ、プランナは的外れな判断を下すということです。
2. なぜ `ANALYZE` が必要なのか?
PostgreSQLはバックグラウンドで `autovacuum` が統計情報を更新してくれますが、大量のデータ更新があった直後など、統計が実態と乖離することがあります。
「なんか最近、クエリが急に遅くなったな」と思ったら、まずはこれを疑ってください。
— 現在の統計情報を確認
SELECT relname, last_analyze, last_autoanalyze
FROM pg_stat_user_tables
WHERE relname = ‘your_table_name’;
もし `last_analyze` が随分と古いようなら、手動で叩いてみましょう。
ANALYZE your_table_name;
これだけで、プランナが「あ、データ分布が変わったんだな」と気づき、昨日までフルスキャンだったクエリが、急にインデックススキャンに切り替わることは現場では日常茶飯事です。
3. `pg_stats` を覗いてみる
統計情報がどう見えるか、一度確認したことはありますか?ただの数字の羅列に思えるかもしれませんが、ここには宝の山が眠っています。
SELECT attname, n_distinct, most_common_vals, most_common_freqs
FROM pg_stats
WHERE tablename = ‘orders’ AND attname = ‘status’;
- n_distinct: このカラムにユニークな値が何個あるか。これが極端に小さいと、プランナは「インデックスを使っても絞り込めないな」と判断してフルスキャンを選びます。
- most_common_vals (MCV): よく出現する値のリスト。
- most_common_freqs: その値がどれくらいの頻度で出現するか。
例えば、`status` カラムで「完了」が99%を占めているなら、`WHERE status = ‘完了’` という条件でインデックスを貼っても、プランナは「どうせほとんど読み込むんだから、インデックスを引くよりテーブルを全部読んだほうが速いよ」と判断します。これが、インデックスが無視される理由の正体です。
4. コスト計算の「裏側」を少しだけ
プランナは、コストを以下のような足し算で算出しています(ざっくりとした概念です)。
1. CPUコスト: タプルを1行読み込むコスト、演算コストなど。
2. I/Oコスト: ページ(ブロック)をディスクから読み込むコスト。
ここでポイントなのが、「ランダムアクセスの方がシーケンシャルアクセスよりもコストが高い」という前提です。インデックススキャンはランダムアクセスを多用するため、ヒットする行数が多すぎると、コスト計算の結果がフルスキャンを上回ってしまうんです。
もし、「インデックスを貼ったのに使われない!」と悩んだら、まずは `EXPLAIN` を叩いてみましょう。
EXPLAIN ANALYZE
SELECT FROM orders WHERE status = ‘キャンセル’;
出力結果の `cost=…` という数値に注目してください。ここが「プランナが予想したコスト」です。もし実際の実行時間(actual time)と大きな乖離があるなら、統計情報が古い可能性が濃厚です。
—
先輩からのアドバイス
「インデックスを闇雲に貼る」のは、もうやめましょう。
まずは `ANALYZE` で現状を正しく把握し、それでもダメなら `pg_stats` を眺めてみる。それでも制御できないなら、`SET enable_seqscan = off;` で無理やりインデックスを使わせてみて、本当に速くなるか比較検証する。
データベースエンジニアの仕事は、魔法をかけることではなく、「どうすればコンピュータが最も効率的に動けるか」を論理的に整理してあげることです。
ぜひ、皆さんのデータベースの統計情報、一度覗いてみてください。きっと、これまで見えなかったデータの特徴が見えてくるはずですよ。
それでは、また現場で!
コメント