「遅いクエリ」との戦い方:EXPLAIN ANALYZE を武器にする
現場で働いていると、必ず一度は遭遇するよね。「昨日までサクサク動いていたはずの画面が、急に真っ白になった」「ログを見たらクエリがタイムアウトしている」なんていう悪夢のような瞬間。
そんな時、焦って適当にインデックスを貼ったり、クエリを書き直したりするのはNGだ。まずは「何が起きているのか」を正しく観測すること。そのための最強の武器が、PostgreSQLの `EXPLAIN ANALYZE` だ。
今日は、教科書的な説明はさらっと流して、僕が現場でどうやってこれを使ってボトルネックを炙り出しているか、その「勘所」を話そうと思う。
—
EXPLAIN ANALYZE は「診断書」だ
ただの `EXPLAIN` は、あくまでプランナが「こう動く予定です」という計画書を見せてくれるもの。対して `EXPLAIN ANALYZE` は、実際にクエリを実行し、その結果を突き合わせる「診断書」だ。
EXPLAIN ANALYZE
SELECT FROM orders
WHERE user_id = 12345 AND created_at > ‘2023-01-01’;
これを叩くと、こんな感じの情報が返ってくるはずだ。
- cost: プランナが予測したコスト(あくまで相対的な数値)
- actual time: 実際に要した時間(ミリ秒)
- rows: 処理した行数
- loops: 何回その操作が繰り返されたか
「予測」と「現実」のギャップに潜む罠
ここからが腕の見せ所だ。まず最初にチェックすべきは、「cost」や「rows」の予測と、「actual」の数値が大きく乖離していないかを確認すること。
もし、プランナが「1行しか返ってこないはずだ」と予測しているのに、実際には「100万行」読み込んでいたとしたら、それは統計情報が古くなっている証拠だ。`ANALYZE` コマンドで統計情報を更新するだけで、プランが劇的に改善されることはよくある。
現場で見るべき「3つのポイント」
僕がパフォーマンスチューニングをするとき、真っ先に目を光らせるのがこの3つだ。
1. Seq Scan(シーケンシャルスキャン)の有無
インデックスが効いていない証拠だ。小さいテーブルなら問題ないけれど、数百万行あるテーブルでこれが出ているなら、インデックスを貼るか、`WHERE` 句の条件を見直す必要がある。
2. Actual Time の「累積」
実行計画の出力にある `actual time=0.050..100.500` のような数値。ここで注目すべきは、どのステップで一番時間が溶けているかだ。特に、Nested Loop の中で `loops` 数が多い箇所は要注意。外側のループが1回動くたびに、内側の処理が何度も走っている可能性があるからね。
3. Memory Usage と Temporary File
並べ替え(Sort)やグループ化(HashAggregate)がメモリ(`work_mem`)に収まりきらず、ディスクに書き出されていると、途端にレスポンスが悪化する。`Disk: …` と表示されていたら、メモリ設定を調整するか、インデックスで並び順を解決できないか検討しよう。
—
注意点:実務での「お作法」
これだけは覚えておいてほしい。`EXPLAIN ANALYZE` は実際にクエリを実行するんだ。つまり、`UPDATE` や `DELETE` 文でやると、データが書き換わってしまう。
安全に確認したいときは、必ずこうやってトランザクションで囲むのが鉄則だ。
BEGIN;
EXPLAIN ANALYZE UPDATE orders SET status = ‘shipped’ WHERE id = 999;
ROLLBACK; — ここで取り消す!
—
最後に:エンジニアとしての嗅覚
結局のところ、ツールが吐き出す結果をどう解釈するかは、僕たちの「経験」にかかっている。
「なぜインデックスが効かないのか?」「なぜこの結合方法が選ばれたのか?」と問い続けること。そうやって実行計画と向き合っていると、だんだんクエリを書く段階で「あ、これはインデックスが効きにくいな」と直感できるようになるはずだ。
データベースを味方につければ、アプリケーションはもっと自由になれる。まずは今日のクエリ、`EXPLAIN ANALYZE` で覗いてみるところから始めてみよう。
何かハマったことがあれば、またいつでも聞いてくれよ。エンジニア同士、一緒に腕を上げていこうぜ。
コメント