PostgreSQLの「クエリ書き換えエンジン」という名の魔法:プランナ以前の静かな闘い
PostgreSQLのパフォーマンスに悩まされるとき、多くの人はまず `EXPLAIN ANALYZE` を叩き、プランナが生成した実行計画のコストやスキャン方式を眺めるでしょう。それは正しいアプローチです。しかし、プランナという「最適化の鬼」が頭を悩ませる前段階で、クエリは別の重要なプロセスを通過していることを忘れてはなりません。
それが「クエリ書き換えエンジン(Query Rewriter)」です。
ここは、パーサが生成した無骨なクエリツリー(Parse Tree)を、実行可能な形に磨き上げ、時には全く別の姿へと変貌させる、いわば「情報の精錬所」です。今日は、このあまり光の当たらない、しかし極めて重要なエンジン内部の世界を覗いてみましょう。
—
ルールシステムという名の「クエリの影武者」
PostgreSQLにおいて、書き換えエンジンを理解する上で避けて通れないのが「ルールシステム(Rule System)」です。
これはSQLレベルでのマクロ展開のようなものです。ビューや `ON … DO …` ルールが定義されている場合、書き換えエンジンはパーサが作ったツリーの枝を刈り取り、ルールに従って新しいノードを接ぎ木していきます。
ここで経験豊富なエンジニアなら一度は直面したはずです。「なぜか意図しないサブクエリが展開され、パフォーマンスが崩壊する」という事象。
例えば、複雑なビューを多段でネストさせた場合、書き換えエンジンは律儀に全てのルールを適用し、単一の巨大なクエリツリーを組み上げようとします。プランナに渡される前に、クエリが複雑怪奇なスパゲッティ状態になってしまうわけです。この段階でクエリの複雑性が限界を超えると、プランナは「妥当なコスト計算」を諦め、ヒューリスティックな選択に頼らざるを得なくなります。
書き換えエンジンの「隠れた仕事」:定数畳み込みと意味論的最適化
書き換えエンジンは単にルールを適用するだけではありません。もっと賢いこともしています。
例えば、`WHERE x = 1 + 2` という式があれば、書き換えエンジンはこれを `WHERE x = 3` に書き換えます。これは「定数畳み込み(Constant Folding)」と呼ばれるプロセスの一部です。プランナが実行計画を立てる前に、計算コストを削ぎ落とす重要なステップですね。
さらに興味深いのは「意味論的最適化」です。
制約(Constraint Exclusion)の活用がその代表例です。もしテーブル定義に `CHECK (id > 0)` があれば、`WHERE id < 0` というクエリに対して、書き換えエンジンは「この条件は絶対に偽だ」と判定し、空の実行計画(Dummy Path)を生成して処理を即座に終了させます。
この「動くまでもなく結果がわかるクエリ」をどれだけ効率的に弾けるか。これは大規模なデータセットを扱う際に、見えないところで我々のサーバーを救ってくれています。
---
パフォーマンストラブルシューティングの勘所
では、現場でこの書き換えプロセスが「重荷」になっているときはどう判断すべきか。
もし、特定のクエリが `EXPLAIN` の出力が出るまでに妙に時間がかかるなら、それはプランナが遅いのではなく、書き換えエンジンがクエリツリーの展開に苦しんでいる可能性が高いです。
- ビューの多段ネスト: ビューの中でさらにビューを呼び出していないか? それが書き換えエンジンの再帰処理を深めていないかを確認してください。
- ルールシステムの過剰な活用: 特定のテーブルに `ON INSERT` ルールが大量にぶら下がっている場合、書き換えエンジンは挿入のたびに巨大な書き換えツリーを構築します。これは `TRIGGER` に置き換えることで改善されるケースが多いです。
- 複雑なサブクエリの展開: 書き換えエンジンはサブクエリを平坦化(Flattening)しようとしますが、相関サブクエリが複雑すぎると、書き換えの段階で最適化の余地を自ら捨ててしまうことがあります。
—
最後に:エンジニアとしての嗅覚
PostgreSQLの書き換えエンジンは、非常に堅牢で洗練されています。しかし、それは「魔法」ではなく、アルゴリズムの帰結です。
我々エンジニアがすべきことは、このエンジンが「どのようなクエリを書き換えにくいと感じるか」という肌感覚を養うことです。クエリを書くとき、心の中で一度パーサを通し、書き換えエンジンがどうツリーを組み替えるか。その影の処理を想像できるようになれば、あなたはデータベースの設計者と同じ視座に立てていると言えるでしょう。
PostgreSQLは、今日も静かに、そして忠実にあなたのクエリを書き換えています。そのプロセスに意識を向けるだけで、これまで見えなかったボトルネックの正体が見えてくるはずです。
さて、次はどのクエリを最適化しますか?
コメント