PostgreSQL | SQLite+MySQL経験者向け、30日で習得するPostgreSQL:設計とパフォーマンス - Day18 パフォーマンスチューニング

SQL PostgreSQL
スポンサーリンク

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 前半の着地点になる。

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