【テクニカル・上級編】 実行計画ツリー – PostgreSQL

PostgreSQLの「実行計画ツリー」を解剖する:なぜクエリはそこで迷子になるのか

PostgreSQLを長く触っていると、`EXPLAIN ANALYZE`の出力結果がただの文字列ではなく、一種の「楽譜」のように見えてくる瞬間があるはずです。プランナが生成する実行計画ツリー。それは単なる処理手順の羅列ではなく、PostgreSQLのオプティマイザが膨大な検索空間を彷徨った末に導き出した、「今、この瞬間の最適解」です。

今日は、この実行計画ツリーの内部構造と、パフォーマンスチューニングの現場で僕が何を考え、どこを注視しているのか、少し深掘りしてみましょう。

—

実行計画ツリーは「再帰的な実行体」である

多くのエンジニアは、実行計画を「上から下へ流れるフロー」と捉えがちです。しかし、PostgreSQLのアーキテクチャにおいて、実行計画ツリーは「Planノードの階層構造」そのものです。

各ノードはC言語の構造体(`Plan`)として定義されており、`lefttree`や`righttree`といったポインタを通じて、親子関係を形成しています。実行時には、Executorがルートノードの`ExecProcNode`を呼び出し、それが子ノードを再帰的に呼び出すことで、タプル(行)がパイプラインのように「プル」されていきます。

ここで重要なのは、「ノードは能動的にデータを押し出すのではなく、親からの要求に応じて必要分だけを返す」という点です。このプルモデルこそが、メモリを節約しつつ、巨大なデータセットを扱うためのPostgreSQLの知恵です。

トラブルシューティングの勘所:ノードの「誤解」を見抜く

パフォーマンスのボトルネックを特定する際、僕はまず「見積もりと実測の乖離」に目を向けます。

1. Cardinality(行数)のミスリード

プランナの計算が狂う最大の要因は、統計情報の陳腐化です。`EXPLAIN ANALYZE`で `rows=N` と `actual rows=M` が大きく異なる場合、そのノードから下の結合順序やスキャン手法が「最悪の選択」を強制されている可能性が高い。特に相関関係のあるカラム(例:市区町村と郵便番号)に対するフィルタリングでは、単一統計量では限界があります。

2. 結合戦略の「コスト」を疑う

`Hash Join` はメモリに乗り切れば最強ですが、`work_mem` を溢れてディスク(temp file)に書き出し始めた瞬間、そのノードはパフォーマンスの死神に変わります。

  • Hash Join: メモリ消費と引き換えに速度を得る。
  • Merge Join: ソート済みデータがあるなら最強だが、ソートコストが高い。
  • Nested Loop: 小規模結合なら速いが、外側の行数が増えると指数関数的に爆発する。

もし、プランナが大規模なテーブルに対して「Nested Loop」を選択しているなら、それは多くの場合「インデックスが使えない」か「結合条件が最適化されていない」というSOSサインです。

実践:実行計画を「読む」ための視点

僕が現場で実行計画を読むときは、以下の順序で意識を集中させます。

  • 「コスト」ではなく「実行時間」を見る: `EXPLAIN` のコスト値はあくまで相対的な指標です。実戦では、`actual time` と `loops` を掛け合わせた「真の消費時間」がどこに集中しているかを追うべきです。
  • 「どこで増幅しているか」を探す: 結合の途中で行数が急増しているノードはありませんか? `Join Filter` や `Hash Cond` の直前で期待以上にデータが膨らんでいる場所こそ、インデックスの追加や、クエリの再構築を検討すべき「現場」です。
  • 「共有バッファのヒット率」を想像する: 実行計画には出ませんが、`Shared Hit/Read` の比率は常に頭の中に置いておくべきです。インデックススキャンが多発していても、それがランダムアクセスを連発しているなら、シーケンシャルスキャンよりも遅いケースすらあるのです。

最後に:プランナと対話する

PostgreSQLのプランナは非常に優秀ですが、万能ではありません。彼らが「なぜその計画を選んだのか」を理解することは、データベースエンジニアとしてのOS(オペレーティング・システム)をアップデートすることに等しいと僕は思っています。

「なぜNested Loopを選んだのか?」「なぜHashのバケット数が足りなかったのか?」と自問自答を繰り返すこと。その積み重ねが、いざ本番環境で悲鳴を上げたクエリを救い出す唯一のスキルセットになります。

もし皆さんが今、クエリの実行計画と睨めっこしているなら、ぜひそのツリーの「深さ」と「幅」の中に、PostgreSQLが選んだ物語を読み解いてみてください。そこには、ただの数字以上の「論理」が詰まっているはずですから。

—

エンジニアの皆さんのクエリが、明日も軽やかに走ることを願っています。

コメント

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