「EXPLAIN」の先にある世界。PostgreSQLのクエリ実行エンジンを紐解く
やあ。今日も元気に`EXPLAIN`叩いてる?
現場でコードを書いていると、「なんでこのクエリ、こんなに遅いんだ?」って頭を抱える瞬間があるよね。そんな時、みんなは`EXPLAIN ANALYZE`の結果を眺めて、「あ、Seq Scanになってるな」とか「Nested Loopか…」なんて分析するはずだ。
でも、「PostgreSQLのエンジンが、その実行計画をどうやって現実に変えているのか」まで深掘りしたことはあるかな?今日は、PostgreSQLの心臓部、クエリ実行エンジン(Executor)の仕組みについて、少しだけ解像度を上げてみよう。ここを理解しておくと、パフォーマンスチューニングの視点がガラリと変わるはずだよ。
—
1. 実行エンジンは「演算子のリレー」だ
PostgreSQLの実行エンジンは、いわゆる「火山モデル(Volcano Model)」あるいは「イテレータモデル」と呼ばれる仕組みで動いている。
簡単に言うと、「上流のノードが下流のノードに『次の行をくれ!』と要求し、下流がそれに応える」というバケツリレー形式なんだ。
例えば、`SELECT FROM users WHERE age > 20` というクエリがあったとする。
1. 上流(Plan Node): 「結果を返せ!」と命令を出す。
2. 中間(Filter Node): 「テーブルスキャンして、条件に合うやつだけ渡すよ」と実行。
3. 下流(Scan Node): 「データページから1行フェッチした。条件チェックするぞ…OK、返すよ!」
この「GetNext」という関数を再帰的に呼び出すことで、クエリは実行される。シンプルだよね。でも、この単純な仕組みこそが、PostgreSQLの拡張性と安定性を支えているんだ。
—
2. 実践:実行計画を「動くもの」としてイメージする
エンジニアとして覚えておいてほしいのは、実行計画(Plan Tree)はあくまで「指示書」であり、実行エンジンは「役者」だということ。
例えば、JOINを実行するとき、エンジンは状況に応じて「役者(アルゴリズム)」を切り替える。
- Nested Loop: 小さなデータセットに対して。片方をループさせて逐次検索する。
- Hash Join: ハッシュテーブルをメモリに作って、一気に突合する。
- Merge Join: ソート済みのデータを順番に読み込んでマージする。
なぜこれが重要なのか?
現場でよく見る「メモリ不足(Work Memの枯渇)」は、この実行エンジンが「ハッシュテーブルをメモリに載せきれなくて、ディスク(temp file)に溢れさせた」ときに起こるんだ。
— チューニングのヒント:work_memを適切に設定する
SET work_mem = ’64MB’;
EXPLAIN ANALYZE SELECT FROM users u JOIN orders o ON u.id = o.user_id;
`EXPLAIN ANALYZE`の結果に `Batches: 2` とか `Disk: xxxkB` なんて表示が出ていたら、それは実行エンジンが苦しんでいる証拠。メモリ設定を見直すチャンスだね。
—
3. 私たちが意識すべき「データの流れ」
エンジンがどう動いているかを意識すると、クエリの書き方も変わってくる。
例えば、「不要なタプルを早めに捨てる」こと。実行エンジンは、下のノードから上のノードへデータを送るたびにコストを消費する。フィルタ条件(WHERE句)が実行計画のどの段階で適用されるか(Filter vs Index Cond)を意識するだけで、エンジンの負荷は劇的に軽くなるんだ。
先輩からのアドバイス
- インデックスを信じすぎない: インデックスがあっても、データ量や分布によってはSeq Scanの方が速いとエンジンが判断することがある。`pg_stats`を見て、統計情報が古いせいでエンジンが誤った判断をしていないか確認しよう。
- 関数をWHERE句に使うな: `WHERE YEAR(created_at) = 2023` と書くと、実行エンジンはテーブルの全行に対して関数を計算しなければならない。これはエンジンにとって重労働だ。`created_at BETWEEN ‘2023-01-01’ AND …` と書き換えるだけで、エンジンはインデックスを使って軽快にタプルを拾い上げてくれるようになる。
—
最後に:エンジンを理解する楽しさ
データベースという黒い箱の中で、何が起きているのか。それを想像できるようになると、チューニングは「お祈り」から「科学」に変わる。
PostgreSQLは非常に素直なデータベースだ。実行エンジンは、君が書いたSQLという「台本」を、可能な限り忠実に、かつ効率的に実行しようと努力している。たまには`EXPLAIN (VERBOSE, BUFFERS)`を付けて、エンジンが「どのバッファをどれくらい読み込んだか」まで覗いてみてほしい。
「なぜ遅いのか」がわかれば、修正案は自ずと見えてくるはずだ。
それじゃ、また現場で会おう。いいクエリライフを!
コメント