【実務・中級編】 式評価 – PostgreSQL

PostgreSQLの「式評価」をハックする:クエリの裏側で起きている魔法を紐解く

やあ。データベースのパフォーマンスチューニングに明け暮れていると、たまに「なぜこのクエリはこんなに遅いんだ?」という壁にぶつかるよね。

実行計画(`EXPLAIN ANALYZE`)を眺めていて、コストが妙に高い演算や関数に出会ったことはないかな?今日は、PostgreSQLがクエリの実行時にどうやってデータを加工しているのか、その心臓部である「式評価(Expression Evaluation)」という仕組みについて深掘りしてみよう。

ここを理解しておくと、「なぜこの書き方だとインデックスが効かないのか」「なぜこの関数は重いのか」という問いに対して、自分なりの答えが出せるようになるはずだ。

—

1. 式評価って、結局何をしてるの?

PostgreSQLにおいて、クエリは単なる「データの取り出し」じゃない。`SELECT price 0.9 AS discounted_price` と書けば、エンジンはテーブルから読み込んだ生データに対して、その場で計算を施す必要があるよね。

この「タプル(行)を受け取って、演算を適用し、結果を返す」という処理全体を、私たちは式評価と呼んでいる。

内部的には、PostgreSQLはクエリをパースした後、構文木(Parse Tree)を「実行可能な形」にコンパイルする。具体的には、`ExprState` という構造体のツリーを作り上げるんだ。これが実行時の司令塔になる。

2. 実行時の仕組み:ExprStateの正体

クエリが実行されるとき、PostgreSQLの実行エンジン(Executor)は、各ノード(SeqScanとかHashJoinとか)の中で、対象のタプルが来るたびにこの `ExprState` を呼び出す。

例えば、`WHERE (a + b) > 100` という条件があったら:
1. `a` と `b` の値を取り出す(Varノードの評価)
2. `+` 演算子を呼び出す(FuncExprの評価)
3. その結果と `100` を比較する(OpExprの評価)

これらが再帰的に実行される。シンプルに見えるけど、これが数百万行に対して行われるわけだから、ここのオーバーヘッドを甘く見てはいけない。

3. 実務で知っておくべき「落とし穴」

現場でよくある失敗談を2つ紹介しよう。

① 関数呼び出しのオーバーヘッド

PostgreSQLの関数(特にPL/pgSQL)は強力だけど、式評価のたびに「コンテキストスイッチ」が発生する。

— これだと、行数分だけ関数が呼び出される
SELECT FROM users WHERE calculate_score(id) > 80;

もし `calculate_score` が重い処理なら、評価のたびにエンジンが止まってしまう。可能であれば、インデックスを貼れる形(SARGable)に変えるか、あるいは計算済みの値をカラムとして持っておくのが鉄則だ。

② 不必要な型変換(キャスト)

式の中で型が一致していないと、エンジンは裏で「キャスト関数」を自動挿入する。

— 文字列型のカラムに数値で比較すると…
SELECT FROM logs WHERE status_code = 200;

`status_code` が `text` 型だと、比較のたびに `int4` へのキャスト関数が式評価の一部として呼ばれる。これ、データ量が数千万行を超えると結構な無視できないコストになるんだよね。

4. チューニングのヒント:どう戦うか

もし「特定の計算が遅い」と感じたら、まずは `EXPLAIN (ANALYZE, BUFFERS)` を叩いてみてほしい。

  • JITコンパイルが効いているか確認する: PostgreSQL 11以降なら、複雑な式はLLVMを使ってネイティブコード化される。大規模な計算が多いクエリなら、`SET jit = on;` で劇的に速くなることがある。
  • IMMUTABLE を活用する: 自作関数なら、必ず `IMMUTABLE`(入力が同じなら結果も必ず同じ)属性を付与しよう。PostgreSQLはこれを見て、式の評価を最適化(定数畳み込みなど)してくれる。

— 最適化のヒントを与える例
CREATE FUNCTION safe_calc(int) RETURNS int AS …
LANGUAGE sql IMMUTABLE;

最後に:データベースは「魔法の箱」じゃない

データベースのパフォーマンスを追求するのは、エンジニアにとって一番面白いパズルの一つだ。PostgreSQLの式評価エンジンは非常に洗練されているけれど、それでも「どう書けばエンジンが楽をできるか」を想像しながらSQLを書くだけで、システム全体のレスポンスは驚くほど変わる。

「動くクエリ」を書くのはスタート地点。その先の「速いクエリ」を目指すなら、ぜひ今日話したような内部の動きをイメージしながら、Explainの出力結果を深読みしてみてくれ。

また何か具体的なクエリで詰まったら、いつでも聞きに来てよ。現場からは以上だ!

コメント

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