Day18 前半のゴール
「“なんとなく遅い”をやめて、“どこが遅いか”と言えるようになる」
パフォーマンスチューニング編の入口です。
Day18 のテーマは「ボトルネック特定」――つまり、「どこが遅いのかをちゃんと言語化すること」。
前半のゴールはこうです。
SQLが遅いときに、いきなり書き換えず「観察」から入る感覚を持つ。
ボトルネックを探すときの“見る順番”を、自分の中に1本決める。EXPLAIN ANALYZE を「読むため」ではなく「疑う場所を絞るため」に使えるようになる。
ここでは、まだ“直し方”には踏み込まず、「特定の仕方」に集中します。
まずは前提:チューニングは“感覚”ではなく“証拠”でやる
「“たぶんインデックスが足りない”は、もう卒業する」
よくあるパターンとして、「なんか遅いからインデックス張るか」という発想があります。
でも、それはほとんど「勘で殴っている」のに近いです。
本当にインデックスが足りないのか。
インデックスはあるけど使われていないのか。
そもそもSQLではなく、アプリ側の呼び出し回数が多すぎるのか。
こういうことを確認せずに手を動かすと、「直った気がするけど根本は変わっていない」状態になりがちです。
パフォーマンスチューニングの基本姿勢は、「証拠を見てから触る」です。
そのために使うのが、ログ・メトリクス・EXPLAIN ANALYZE です。
ボトルネック特定の“見る順番”を決める
「①どのクエリが遅いか → ②そのクエリのどこが遅いか」
いきなり1本のSQLにズームインする前に、まずは「どのSQLが問題児か」を決める必要があります。
現場でよくやる順番は、ざっくりこうです。
どのクエリが遅いのか(全体の中の“犯人探し”)。
そのクエリの中で、どの処理が遅いのか(実行計画での“部位特定”)。
Day18 前半では、特にこの2段階を意識してほしいです。
アプリ側のログやAPM(Application Performance Monitoring)があるなら、
「レスポンスの遅いAPI」「時間のかかっているSQL」を一覧で見るところから始めます。
学習環境なら、「自分でわざと重そうなクエリを書いて、それを題材にする」でもOKです。
大事なのは、「どのクエリを今から診るのか」をはっきりさせることです。
ステップ1:問題のクエリを1本決める
「“遅いと感じるクエリ”を、ちゃんと1つのSQLとして切り出す」
まずやることは、「問題のクエリを1本に絞る」ことです。
例えば、こんなAPIがあるとします。
「ユーザーの注文履歴を表示するAPIが遅い」
このとき、やるべきことはこうです。
そのAPIの中で実行されているSQLをログから拾う。
同じSQLが何度も呼ばれていないかを見る。
1回あたりの実行時間が長いのか、回数が多すぎるのかを分けて考える。
もし「同じSQLが1リクエストで100回呼ばれている」なら、
ボトルネックは「SQLの中身」ではなく「呼び出し方」にあるかもしれません。
逆に、「1回しか呼んでいないのに、その1回が500msかかっている」なら、
そのSQL自体を EXPLAIN ANALYZE で診る価値があります。
Day18 前半では、「問題のSQLを1本、テキストとしてコピペできる状態にする」ところまでを、
ボトルネック特定のスタートラインだと考えてください。
ステップ2:EXPLAIN ANALYZEで“どこが重いか”をざっくり見る
「実行計画は“読む”のではなく“赤いランプを探す”」
問題のSQLが決まったら、次は EXPLAIN ANALYZE です。
例として、少し重そうなクエリを考えます。
SELECT
o.id,
o.ordered_at,
u.name,
SUM(oi.quantity * oi.unit_price) AS order_amount
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN users u ON u.id = o.user_id
WHERE o.ordered_at >= CURRENT_DATE - INTERVAL '30 days'
AND u.status = 'active'
GROUP BY o.id, o.ordered_at, u.name
ORDER BY o.ordered_at DESC
LIMIT 50;
SQLこれに対して、こう打ちます。
EXPLAIN ANALYZE
SELECT ...
SQL出てきた実行計画を、最初から全部理解しようとしなくていいです。
まずは、次の3つだけを探します。
どのノードの actual time が一番大きいか。
どのノードの rows がやたら多いか。
Seq Scan になっている大きなテーブルはないか。
例えば、こんな感じの一部が見えたとします。
Hash Join (actual time=10.000..300.000 rows=500000 loops=1)
-> Seq Scan on orders o (actual time=0.050..250.000 rows=800000 loops=1)
Filter: (ordered_at >= ...)
-> Hash ...
ここから分かるのは、
orders の Seq Scan に 250ms かかっている。
その結果、50万行が JOIN に流れ込んでいる。
つまり、「orders をどう読むか」がボトルネック候補だ、ということです。
この時点ではまだ「どう直すか」は考えなくていいです。
「どのテーブルの、どのノードが重いか」を言語化できれば十分です。
ステップ3:“疑うポイント”を言葉にする
「“ordersのSeq Scanが重い”レベルまで具体化する」
ボトルネック特定の前半で一番大事なのは、「疑い方を雑にしない」ことです。
「なんかJOINが重そう」ではなく、
「ordersテーブルのSeq Scanが250msかかっていて、そこから50万行が流れている」
くらいまで具体的に言えると、次の一手が見えやすくなります。
疑い方の例をいくつか挙げます。
大きなテーブルに対する Seq Scan が重い
→ インデックスがない/効いていない可能性。
→ WHERE 条件の書き方や統計情報も疑う。
Sort ノードがやたら時間を食っている
→ ORDER BY のために全件ソートしている可能性。
→ インデックスで並び順を活かせないかを考える。
Nested Loop の内側で大きなテーブルに Index Scan が何度も走っている
→ N+1 的な構造になっている可能性。
→ JOIN の順番やクエリの書き方を見直す。
Day18 前半では、「実行計画を見て、疑うべき場所を1つの文で説明する」ことを目標にしてください。
例題:LIMITがあるのに遅いクエリのボトルネック特定
「“LIMITがあるから速いはず”は危険な思い込み」
もう1つ、よくあるパターンを例題にします。
SELECT
*
FROM order_items
WHERE user_id = 123
ORDER BY ordered_at DESC
LIMIT 20;
SQL「LIMIT 20 だから軽いはず」と思いがちですが、実際には遅いことがあります。EXPLAIN ANALYZE してみると、こんな一部が見えるかもしれません。
Limit (actual time=0.500..200.000 rows=20 loops=1)
-> Sort (actual time=0.500..200.000 rows=500000 loops=1)
Sort Key: ordered_at DESC
-> Seq Scan on order_items (actual time=0.050..100.000 rows=500000 loops=1)
Filter: (user_id = 123)
ここから読み取れるのは、
order_items を全件 Seq Scan している(50万行)。
その結果を全部 Sort してから、上位20件だけ返している。
つまり、「LIMIT 20 でも、全件読んで全件ソートしているから遅い」という構造です。
この場合の“疑いポイント”はこう言えます。
「order_items(user_id, ordered_at DESC) のインデックスがないので、Seq Scan+Sort になっている」
Day18 前半では、ここまで言えれば十分です。
実際にインデックスを張るのは、後半の「対処編」でやればいい。
ボトルネック特定の“やってはいけない”パターン
「いきなりクエリを書き換える」「ログを見ない」
最後に、よくある落とし穴もはっきりさせておきます。
実行計画を見ずに、クエリだけ書き換え続ける。
どのクエリが遅いかを特定せず、「なんとなく全部遅い気がする」と言う。
アプリ側の呼び出し回数を見ずに、「DBが遅い」と決めつける。
これらは全部、「証拠より感覚を優先している」状態です。
Day18 のテーマは、「感覚ではなく、観察と特定に時間を使う」こと。
直すのはそのあとでいい。
むしろ、特定が甘いまま直し始めると、遠回りになります。
Day18 前半のまとめ
パフォーマンスチューニングの出発点は、「なんとなく遅い」ではなく、「どのクエリが遅いか」「そのクエリのどの部分が遅いか」を証拠ベースで特定することであり、そのためにまず“問題のSQLを1本に絞り”、次に EXPLAIN ANALYZE で「どのノードの actual time が大きいか」「どのテーブルに対する Seq Scan が重いか」「どこで行数が膨らんでいるか」を見る。
ボトルネック特定の前半で目指すのは、「orders の Seq Scan が250msかかっていて50万行流れている」「order_items で Seq Scan+Sort のあとに LIMIT している」といった形で、“どのテーブルのどの処理が重いか”を1文で説明できる状態になることであり、クエリを書き換えるのはその後のフェーズだと割り切ることが、Day18 前半の着地点になる。
