PostgreSQLの心臓部を覗く:式評価(Expression Evaluation)の深淵と最適化の勘所
PostgreSQLを長年触っていると、ふと立ち止まって考えることがあります。「なぜ、この単純なクエリが期待したほど速くないのか?」「あの巨大なテーブルをスキャンする際、CPUは何に忙殺されているのか?」と。
実行計画(EXPLAIN)を見れば `Seq Scan` や `Hash Join` が並んでいるのは分かります。しかし、そのさらに奥深く、タプルがメモリにロードされた瞬間に何が起きているのか。今回は、PostgreSQLの「式評価(Expression Evaluation)」という、地味ながらも極めて重要なコアメカニズムに焦点を当ててみましょう。
式評価の「裏側」:ExecExprStateの正体
クエリ内の演算子や関数が、実際にタプルに対してどう適用されるか。PostgreSQLは、クエリ実行時にSQLの抽象構文木(AST)を直接解釈するような野暮なことはしません。それではあまりに遅すぎるからです。
PostgreSQLは、実行計画の初期化段階で、それらの式を「評価ステップのリスト」にコンパイルします。これが `ExprState` 構造体であり、その内部には `ExprEvalStep` の配列が整然と並んでいます。
- OpExpr: 演算子の実行
- FuncExpr: 関数の呼び出し
- Var: タプルからの属性値抽出
これらがスタックベースの命令セットのように順次実行されることで、私たちの書いた複雑な `WHERE` 句や `SELECT` リストが、CPUにとって理解可能な命令へと変換されます。最近のバージョン(特にPostgreSQL 10以降)では、この仕組みがより洗練され、JIT(Just-In-Time)コンパイルの恩恵も受けられるようになりました。
なぜ「式評価」がボトルネックになるのか
パフォーマンスチューニングの現場で、「CPU使用率が100%に近いのに、ディスクI/Oはそこまで高くない」という状況に遭遇したことはありませんか?
多くの場合、犯人は「コストの高い式評価」です。特に以下のケースでは注意が必要です。
- 過剰な関数呼び出し:
SQLの関数呼び出しは、たとえ `STRICT` であっても、評価のたびにコンテキストの切り替えが発生します。数百万行のタプルに対して、行ごとに複雑な正規表現関数を呼び出せば、CPUは悲鳴を上げます。
- 型変換のオーバーヘッド:
暗黙の型変換(Implicit Cast)は便利ですが、式評価のステップ数に余計な「変換命令」を紛れ込ませます。特に、インデックスの定義とクエリの型が微妙にずれている場合、インデックスが効かないだけでなく、評価コストまで跳ね上がるという二重苦に陥ります。
- JITの不発:
JITは万能ではありません。複雑すぎる式はLLVMによるコンパイルコストの方が高くつくこともあります。`jit_above_cost` のチューニングは、単なる設定値以上の「アート」です。
現場で役立つトラブルシューティングの視点
もし、ある特定のクエリが重いと感じたら、私はまず `perf` や `vtune` を持ち出す前に、`EXPLAIN (ANALYZE, BUFFERS)` をじっくりと眺めます。
注目すべきは、実行計画のノードが報告する「実行時間」です。もし、`Filter` や `Rows Removed by Filter` が異常に大きい場合、それはディスクI/Oの問題ではなく、「式評価のコストがタプル数に対して大きすぎる」ことを示唆しています。
対処のヒント:
1. 関数のインライン化: 可能な限り SQL 関数を減らし、純粋な演算に書き換える。
2. 型を一致させる: `EXPLAIN` の `Filter` 句を注意深く見てください。`::text` や `::int` といったキャストが自動挿入されていませんか? それが評価コストを押し上げています。
3. 計算の先出し: `WHERE` 句で計算を行うのではなく、生成列(Generated Columns)や、あらかじめ計算済みのテーブルを作成しておく。これは「計算を評価のタイミングでやらせない」という究極の最適化です。
最後に:データベースは「命令の集合」である
式評価というメカニズムを理解すると、PostgreSQLが単なる「データを保存する箱」ではなく、「動的にマシンコードを組み立てる実行エンジン」であることが分かってきます。
複雑なクエリを書く際、単に「結果が出るか」だけでなく、「PostgreSQLがこの式をどんなステップで評価するのか」を想像してみてください。その想像力が、あなたのクエリを、数秒かかっていた処理を数ミリ秒で終わらせる「洗練されたアルゴリズム」へと変貌させるはずです。
データベースエンジニアとしての腕の見せ所は、こうした「計算の無駄」をいかに排除するかにかかっている。私はそう信じています。
皆さんのクエリが、今日も軽快に走りますように。それでは、また。
コメント