「なぜPostgreSQLはたまに迷子になるのか?」― 結合順序の最適化とGEQOとの付き合い方
現場でバリバリコードを書いていると、たまに遭遇するじゃないですか。「さっきまで爆速だったクエリが、テーブルを一つJOINした瞬間に化石みたいな遅さになった」という現象。
あれ、実はPostgreSQLの「クエリプランナ」が、複雑なパズルに頭を抱えて迷子になっている状態なんです。今日は、PostgreSQLがどうやって「どのテーブルから先に結合するか」を決めているのか、そしてプランナが限界を迎えた時にどう助けてあげればいいのか、現場の視点でガッツリ解説します。
—
プランナの脳内:結合順序を決めるアルゴリズム
PostgreSQLのプランナは、基本的には「コストベース」で動いています。統計情報を参照して、一番計算コストが低そうな実行計画を選ぶわけですね。
テーブルが2つや3つなら、プランナは全ての順序(A→B→C, C→B→Aなど)を網羅的に計算して、最短ルートを見つけ出します。これを動的計画法(Dynamic Programming)と呼びます。
でも、これがテーブル数が増えてくると話が変わります。例えば10個のテーブルをJOINしようとすると、組み合わせの数は爆発的に増え、プランナが最適解を探すだけでクエリの実行時間以上の時間がかかってしまう。「最適解を探すためのコスト」が「最適化による節約分」を上回ったら本末転倒ですよね。
—
GEQO(遺伝的クエリ最適化)という「妥協案」
そこで登場するのが GEQO (Genetic Query Optimization) です。
テーブル数がある閾値(`geqo_threshold`、デフォルトは12)を超えると、PostgreSQLは網羅的な探索を諦めて、遺伝的アルゴリズムを使って「そこそこ良い順序」を短時間で見つけようとします。
「そこそこ良い」というのがポイントです。GEQOはあくまでヒューリスティックな手法なので、必ずしもベストな実行計画を出すわけではありません。
GEQOが暴走している時のサイン
もし、大規模なJOINを含むクエリが、統計情報を更新してもやたらと遅い場合、プランナが「悪い順序」をガチャで引き続けている可能性があります。
そんな時は、試しにGEQOをオフにして様子を見るのも手です。
— 特定のセッションだけでGEQOを無効化して実行計画を確認
SET geqo = off;
EXPLAIN ANALYZE SELECT … ;
もしこれで劇的に速くなるなら、プランナが探索の迷路で道に迷っていた証拠。でも、安易に全体でGEQOをオフにするのは禁物です。他のクエリに悪影響が出る可能性があるからね。
—
現場で使える「プランナを導く」テクニック
プランナが迷子になったとき、僕たちができるのは「ヒント」を与えることではなく、「構造を整理すること」です。
1. 統計情報の鮮度を疑う
基本中の基本ですが、`ANALYZE`はかけてますか? プランナは統計情報という「地図」を頼りに歩いています。地図が古ければ、そりゃあ崖から落ちる(フルスキャンする)ような計画を立ててしまいます。
2. サブクエリやCTEの境界線を意識する
PostgreSQLのプランナは、基本的にCTE(WITH句)の境界を越えて最適化するのが苦手な場合があります(バージョン12以降は改善されていますが、それでも過信は禁物)。
もし複雑な結合が遅いなら、一度中間結果を絞り込んでから結合するように書くと、プランナが「どこを先にやるべきか」を理解しやすくなります。
— 悪い例:全部いっぺんにJOIN
SELECT FROM orders o
JOIN users u ON o.user_id = u.id
JOIN … (あと5個続く)
— 良い例:先に絞り込む
WITH filtered_orders AS (
SELECT FROM orders WHERE created_at > ‘2023-01-01’
)
SELECT FROM filtered_orders fo
JOIN users u ON fo.user_id = u.id …
3. 結合順序を強制する(最後の手段)
`join_collapse_limit` や `from_collapse_limit` を調整することで、プランナの挙動を制限することもできますが、これはかなりトリッキーです。基本的には、SQLの書き方を工夫して、プランナが正しい判断をしやすい土俵を作ってあげるのが一番の近道です。
—
最後に:エンジニアとしての心構え
「データベースが遅い」と言われたとき、多くのエンジニアはインデックスを追加して解決しようとします。でも、今回のように「結合順序」が原因の場合、インデックスをいくら増やしても状況は変わりません。
大切なのは、「プランナが今、どのような順序でデータを拾おうとしているのか」を `EXPLAIN ANALYZE` で覗き込むこと。
PostgreSQLは非常に優秀な相棒ですが、時には迷子にもなります。そんな時にそっと正しい道を指し示してあげる。それこそが、データベースエンジニアとしての腕の見せ所じゃないかな、と僕は思います。
何か困ったクエリがあれば、いつでも相談してくださいね。一緒に実行計画を紐解いていきましょう!
コメント