「おせっかいな最適化」と付き合う:PostgreSQLにおける定数畳み込みの深淵
データベースのクエリを眺めているとき、ふと「なぜPostgreSQLはこんな単純な計算すら、毎回実行時に行っているのだろう?」と疑問に思ったことはありませんか?
実は、PostgreSQLのクエリオプティマイザは、我々が想像する以上に「おせっかい」なほど先回りをして計算を済ませてくれます。それが今回取り上げる「定数畳み込み(Constant Folding)」です。
一見、地味な最適化に見えるかもしれません。しかし、大規模なデータセットを扱う現場では、この小さな最適化がクエリの実行計画(Execution Plan)を劇的に変える分岐点になることが多々あります。今日は、この隠れた立役者について、少し掘り下げてお話ししましょう。
—
定数畳み込みとは何か
定数畳み込みとは、端的に言えば「クエリのコンパイル段階(計画作成時)で、計算可能な式をあらかじめ計算し、その結果に置き換えてしまう処理」のことです。
例えば、`WHERE price > 100 24` という条件があったとしましょう。人間から見れば「`price > 2400` と書いてくれよ」と思うところですが、開発者は可読性のために計算式を残すことがよくあります。PostgreSQLのパーサとリライタは、この `100 24` を見つけ出し、最適化の過程で即座に `2400` という定数へ昇華させます。
これにより、実行エンジンは演算コストをゼロにできるだけでなく、インデックスの使用可否判定においても有利な状況を作れるようになります。
なぜ「熟練エンジニア」にとって重要なのか
「コンパイル時に計算してくれるなら、気にしなくていいのでは?」と思うかもしれません。しかし、ここには一つ大きな落とし穴があります。それは、「PostgreSQLがすべての式を定数畳み込みできるわけではない」という事実です。
1. 不変関数(IMMUTABLE)の壁
PostgreSQLは、関数の安定性(Volatility)を厳密に管理しています。
- IMMUTABLE: 入力が同じなら出力も常に同じ。定数畳み込みの対象。
- STABLE / VOLATILE: 現在の時刻やデータベースの状態に依存する。定数畳み込みは(原則)行われない。
例えば、`WHERE created_at > NOW() – INTERVAL ‘1 day’` というクエリ。`NOW()` は `VOLATILE` です。つまり、プランナは「実行の瞬間に値が変わる」ことを知っているため、あえて計算を先送りにします。もしこれを定数畳み込みしてしまうと、長時間走るトランザクションの中で、結果が「計画作成時の時刻」に固定されてしまうという致命的なバグを生むからです。
2. 型キャストの罠
意外と見落としがちなのが型キャストです。精度の異なる数値や、暗黙的な型変換が含まれる式では、畳み込みが抑制されるケースがあります。特にカスタムデータ型や複雑な演算子を定義している場合、オプティマイザが「これは本当に安全に畳み込めるのか?」と判断を迷い、畳み込みを諦めることがあります。
パフォーマンストラブルシューティングの勘所
現場で「なぜかインデックスが使われない」「実行計画が想定と違う」という壁にぶつかったとき、私はまず `EXPLAIN` の出力を眺めて、定数がどのように解釈されているかを確認します。
もし、最適化されるはずの式がそのままの形でプランに残っていたら、以下の点を疑ってください。
- 関数の安定性属性の確認: 自作関数を使っている場合、正しく `IMMUTABLE` とマークされていますか? これを間違えると、プランナは「予測不能な結果」を恐れて、インデックスの範囲スキャンを諦め、全件スキャンを選択します。
- プランキャッシュの汚染: `PREPARE` 文やアプリケーション側のプリペアドステートメントを使用している場合、パラメータ化されたクエリの一部が定数として認識されず、プランナが汎用的な計画(Generic Plan)を生成してしまうことがあります。これにより、特定の定数で劇的に速くなるはずのクエリが、不遇な計画を強いられることがあります。
最後に:データベースを信じすぎないこと
結局のところ、PostgreSQLの最適化エンジンは非常に優秀ですが、あくまで「ルールに基づいた推論」を行っているに過ぎません。
我々エンジニアがすべきなのは、オプティマイザを迷わせないクエリを書くことです。複雑な計算が必要なら、定数畳み込みを待つのではなく、アプリケーション側で計算して結果を渡す。あるいは、どうしてもDB側でやるなら、その値がどう処理されるのかを `EXPLAIN (VERBOSE, BUFFERS)` で徹底的に追跡する。
この「ブラックボックスをブラックボックスのままにしない姿勢」こそが、大規模システムを支えるデータベースエンジニアの矜持ではないでしょうか。
皆さんのクエリが、今日も効率的なプランで実行されることを祈っています。それでは、また。
コメント