「とりあえず `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` が多い箇所を一つずつ「減らす」努力をしてみてほしい。
「なぜ遅いのか」を勘で当てるんじゃなくて、実行計画という「答え」を読み解く。これができると、君のエンジニアとしての価値はグッと上がるはずだよ。
何か具体的なクエリで困ったら、またいつでも聞いてくれ。一緒に最適化しよう!
コメント