【実務・中級編】 EXPLAIN ANALYZEの読み解き – PostgreSQL

「とりあえず `EXPLAIN ANALYZE` を叩いて、なんとなく数字を見て終わり」……なんてこと、やってないよね?

現場でコードレビューをしていると、実行計画の「コスト」の数値だけ見て「大きいからダメだ」と判断しているケースによく遭遇するんだ。でも、PostgreSQLの実行計画は、もっと雄弁に、君のクエリの「どこが痛いのか」を語ってくれている。

今日は、ただ実行計画を見るだけじゃなく、そこから「ボトルネックの正体」を暴き出すための、僕なりの読み解き方をシェアしようと思う。

—

1. まずは「コスト」という幻影から離れよう

`EXPLAIN ANALYZE` を実行すると、まず目に入るのが `cost=0.00..450.20` みたいな数字だよね。これ、実は「相対的な見積もり」に過ぎないって知ってた?

  • コストはあくまで「目安」: システムの負荷やディスクの速度を反映した「推測値」だから、絶対的な指標じゃない。
  • 見るべきは「Actual Time」: `cost` よりも、実際に何ミリ秒かかったか(`actual time=0.045..0.120`)を重視しよう。

特に注意してほしいのは、「見積もりと実際の行数の乖離」だ。`rows=100` と書いてあるのに、実際は `actual rows=100000` だったりすると、プランナは自信満々に「全件スキャン(Seq Scan)」を選んでしまう。これが、遅延の最大の原因の一つなんだ。

2. 注目すべき「4つの武器」

実行計画を読むとき、僕がまずチェックするのは以下の4つだ。

① Loops:君は「何度も」同じ苦労をしていないか?

`Loops` は、そのステップが何回繰り返されたかを示している。
例えば、Nested Loopの中で `Index Scan` が発生しているとき、`Loops` が1,000回とかになっていると要注意だ。外側のテーブルから1,000回も内側へ問い合わせていることになる。「インデックスは効いているのに遅い」という場合、大抵はこの `Loops` の回数が多すぎることが原因だよ。

② Shared Hit / Read:ディスクとの戦い

ここが一番の肝だ。

  • Shared Hit: メモリ(バッファ)に乗っているデータ数。
  • Shared Read: ディスクから読み込んだデータ数。

パフォーマンスを出すための鉄則は「Readを減らすこと」。`Shared Read` が大きいなら、インデックスが足りていないか、あるいはキャッシュ効率が悪化している証拠だね。

③ Filter / Rows Removed:無駄なフィルタリング

`Filter: (status = ‘active’)` の横に `Rows Removed by Filter: 500000` とか出ていたら、それは「50万行のデータを読み込んでから捨てている」ということ。インデックスで最初から絞り込めていない証拠だ。ここをインデックスで削れば、クエリは劇的に速くなる。

④ Memory Usage:仕事のしすぎ

`Sort` や `Hash` 操作があるとき、`Memory: 32kB` 程度ならいいけど、数メガバイトを超えて `Disk: 1024kB` なんて表示されたら要注意だ。これは「メモリに乗り切らなくて一時ファイル(temp file)を作っている」サイン。`work_mem` の調整を考えるタイミングだね。

—

3. 実践:ボトルネックを見つける手順

例えば、こんなクエリが遅いとしよう。

EXPLAIN (ANALYZE, BUFFERS)
SELECT FROM orders WHERE user_id = 12345 ORDER BY created_at DESC;

ここで `BUFFERS` オプションを忘れちゃいけない。これをつけると、メモリやディスクへのアクセス状況が詳細に出るからね。

分析のステップ:
1. 「どこで時間がかかっているか」を特定する: 一番右側の `actual time` の累積値が大きいノードを探す。
2. 「なぜそこで止まっているか」を推測する:

  • `Seq Scan` になっているなら、`user_id` にインデックスがない?
  • `Index Scan` なのに `Shared Read` が多いなら、インデックスが断片化しているか、メモリが足りない?
  • `Sort` が発生しているなら、`user_id` と `created_at` を組み合わせた複合インデックスで回避できないか?

—

最後に:先輩からのアドバイス

実行計画は、データベースが「どうやって料理を作ったか」のレシピだよ。
もし料理が出てくるのが遅いなら、レシピのどこかで「材料の買い出し(Read)」が多すぎるか、「包丁さばき(SortやFilter)」が悪すぎるか、そのどちらかだ。

最初から完璧に読める必要はない。まずは `EXPLAIN (ANALYZE, BUFFERS)` を実行して、`Shared Read` が多い箇所を一つずつ「減らす」努力をしてみてほしい。

「なぜ遅いのか」を勘で当てるんじゃなくて、実行計画という「答え」を読み解く。これができると、君のエンジニアとしての価値はグッと上がるはずだよ。

何か具体的なクエリで困ったら、またいつでも聞いてくれ。一緒に最適化しよう!

コメント

タイトルとURLをコピーしました