なぜ君のクエリは遅いのか?PostgreSQLの「頭脳」、クエリプランナと仲良くなる話
現場でコードを書いていると、たまに「なんでこんな単純なクエリがこんなに遅いんだ?」と頭を抱える夜があるよね。
インデックスは貼った、テーブル定義も悪くない。それなのに、PostgreSQLが選んだ実行計画(Explain Plan)を見ると、なぜかフルスキャン(Seq Scan)を連発している……。
実は、PostgreSQLのパフォーマンスチューニングにおいて最も重要なのは、「クエリプランナ」という名の優秀だけど時々頑固な執事と対話することなんだ。今日は、このプランナが裏側で何を考えているのか、どうすれば彼らの機嫌を損ねずにパフォーマンスを引き出せるのか、少しだけ深掘りしてみよう。
—
1. クエリプランナは「確率論のギャンブラー」だ
まず誤解を解いておこう。クエリプランナは、常に「最短ルート」を見つけているわけじゃない。彼がやっているのは、「統計情報を元にした、コスト計算に基づく最適化」だ。
SQLが投げられると、プランナは以下のステップを踏む。
1. パーサ・アナライザ: SQLの構文が合っているかチェックし、オブジェクト(テーブルやカラム)が実在するか確認する。
2. リライタ: ビューの展開や、ルールベースの書き換えを行う。
3. プランナ(ここが心臓部!):
- 統計情報(`pg_statistic`)を参照。
- 複数の実行戦略(Nested Loopなのか、Hash Joinなのか等)を検討。
- それぞれに「コスト」を割り振る。
- 最もコストが低いルートを採用する。
ここで肝心なのは、プランナが「統計情報」を信じ切っているということ。もし、君が`ANALYZE`をサボって統計情報が古くなっていたら? プランナは「このテーブル、件数少ないから全部メモリに載せちゃえ」という致命的な判断ミスを犯す。これが「遅いクエリ」の定石だね。
—
2. 実行計画を「読む」という技術
現場でトラブルが起きたとき、迷わず `EXPLAIN (ANALYZE, BUFFERS)` を叩く癖はついているかな?
EXPLAIN (ANALYZE, BUFFERS)
SELECT FROM orders WHERE user_id = 12345;
出力結果で特に注目してほしいのは、以下の3点だ。
- Actual Cost: プランナが見積もったコストと、実際の実行時間の乖離。ここが大きくズレていたら、統計情報が死んでいる証拠だ。
- Buffers: `shared hit`(メモリヒット)と `read`(ディスク読み込み)の数。ここが `read` だらけなら、メモリ不足かインデックスが効いていない。
- Rows Removed by Filter: WHERE句の条件で、せっかく読み込んだのに行を捨てていないか? それはインデックスの貼り方が甘いサインだ。
—
3. プランナを「誘導」する技術的アプローチ
たまに、どれだけ統計情報を更新しても、プランナが「あえて遅い道」を選ぶことがある。そんな時は、僕たちエンジニアが少しだけ誘導してやる必要がある。
① 統計情報の鮮度を保つ(基本中の基本)
`VACUUM ANALYZE` を定期的に実行するのは当然として、特定のカラムの分布が偏っている場合は `ALTER TABLE … ALTER COLUMN … SET STATISTICS` で統計情報の精度を上げることもできる。プランナの解像度を上げるイメージだね。
② ヒント句を使わず「SQLの書き方」で制御する
PostgreSQLにはMySQLのような「インデックスヒント」がない(それがPostgreSQLの流儀だ)。だからこそ、クエリの書き方を工夫する。
例えば、複雑なJOINでプランナが迷走している場合、一時テーブルに切り出すか、あるいは `CTE (Common Table Expressions)` を使って「まずはここで結果を絞り込む」という意図を明確にしてやるんだ。
— プランナが混乱しそうな複雑なJOIN
WITH filtered_orders AS (
SELECT FROM orders WHERE created_at > ‘2023-01-01’
)
SELECT FROM filtered_orders f
JOIN users u ON f.user_id = u.id;
※ PostgreSQL 12以降はCTEが最適化されやすくなったけれど、それでも「処理の順序」を論理的に明示することで、プランナの選択肢を絞り込み、迷子を防ぐことができる。
—
最後に:プランナは「敵」じゃない
たまに「プランナが馬鹿な選択をした」と怒るエンジニアがいるけれど、プランナは常にその瞬間の「手持ちの情報」だけでベストを尽くしているんだ。
もしプランナが期待通りに動かないなら、それは「渡している情報(統計情報やインデックスの設計)」が足りていないか、あるいは「クエリが複雑すぎて、プランナが探索できる範囲を超えている」かのどちらか。
まずは彼(プランナ)の視点に立って、 `EXPLAIN` の行間を読んでみてほしい。そうすれば、今までただの「実行結果」だったものが、PostgreSQLからの「ここを最適化してくれ!」というメッセージに見えてくるはずだよ。
さあ、今日はどのクエリを最適化しに行こうか? また現場で会おう。
コメント