PostgreSQLのプランナ制御、「禁断の果実」とどう付き合うか
「このクエリ、なんでこんなに遅いんだ……? なんでPostgreSQLはわざわざ全表スキャン(Seq Scan)を選んでるんだよ!」
そんなふうに、EXPLAIN ANALYZEを眺めて頭を抱えた経験、誰しもありますよね。今日はそんな時に誰もが一度は誘惑に駆られる、PostgreSQLの「プランナ制御パラメータ」の話をしようと思う。
`enable_seqscan`や`enable_indexscan`といったパラメータのことだ。これらを使えば、PostgreSQLのオプティマイザに「強制的にこのルートを通れ」と指示を出せる。いわば、エンジニアにとっての「禁断の果実」だ。
でも、結論から先に言っておく。これらを安易に本番環境でいじってはいけない。 今日はその理由と、どうしても使わなきゃいけない時の「正しい付き合い方」を伝授するよ。
—
なぜプランナは「間違った選択」をするのか?
そもそも、PostgreSQLのプランナは非常に優秀だ。統計情報を元に、最もコストが低いと思われる実行計画を算出してくれる。
それでも「あえて遅い計画」を選ぶのは、統計情報が古かったり、データ分布が極端に偏っていたりして、プランナが「こっちの方が速いだろう」という見当違いな推測をしてしまうからだ。
ここで皆がやりがちなのが、これだ。
— 禁断の操作その1:Seq Scanの禁止
SET enable_seqscan = off;
これをやると、確かにそのクエリはIndex Scanを選ぶようになる。目の前のクエリは解決するかもしれない。でも、その副作用は想像以上に根深いんだ。
—
禁断の果実をかじると何が起きるのか
プランナ制御をオフにするということは、「このサーバー上で、この実行戦略を永久に禁止する」という宣言に等しい。
1. 「将来の自分」を殺す
今はデータが100万件だからインデックスが速いかもしれない。でも、データが1億件になったとき、あるいはインデックスの断片化が進んだとき、全表スキャンの方が速いケースだって出てくる。その時、この設定が足かせになってシステム全体が悲鳴を上げることになる。
2. グローバル設定の危険性
`postgresql.conf`でこれらをいじれば、そのサーバー上のすべてのクエリに影響が出る。あるクエリを救うために、別のクエリを殺す。そんな最悪のトレードオフが発生するんだ。
—
それでも「制御」が必要な時の正しいアプローチ
じゃあ、どうすればいいのか。実務の現場では、以下のステップで対応するのが「プロの流儀」だ。
1. パラメータ変更の前に「統計情報」を疑う
ほとんどの場合、プランナのミスは統計情報の鮮度不足だ。まずはこれを試せ。
ANALYZE テーブル名;
これだけで解決することも多い。統計情報が最新なのにダメなら、`default_statistics_target`を上げて、プランナにより詳細なヒントを与えるのが正攻法だ。
2. 「特定のクエリだけ」を制御する(セッション単位)
どうしても実行計画を変えたいなら、影響範囲をそのクエリだけに限定しよう。
— トランザクションの中で一時的に制御する
BEGIN;
SET LOCAL enable_seqscan = off;
SELECT FROM users WHERE status = ‘active’; — 狙った計画で実行させる
COMMIT; — 設定は自動的に戻る
3. pg_hint_plan を検討する
PostgreSQLには、特定のクエリに対してヒント句(`/+ … /`)を埋め込める拡張機能`pg_hint_plan`がある。これを使えば、パラメータをいじるよりもずっと明示的に、「このクエリはNested Loopを使え」といった指示が出せる。実務でどうしようもない時の最終兵器だ。
—
先輩からのアドバイス
いいかい、データベースは「生き物」だ。今の最適解が、明日の最適解であるとは限らない。
プランナ制御パラメータをいじるのは、「自分の推測が、何万行ものコードで書かれたオプティマイザのロジックよりも優れている」と断言できる時だけだ。それ以外のときは、インデックスを貼り直したり、クエリの書き方を工夫したり、統計情報を調整したりする方が、よっぽど未来に優しいコードになる。
「`enable_seqscan = off`にして解決!」と喜ぶのは、まだ駆け出しのエンジニアだよ。その先にある「なぜプランナはそう判断したのか?」という深い部分まで潜っていくのが、僕たちエンジニアの醍醐味なんだから。
また何か壁にぶつかったら相談してくれ。一緒にEXPLAINの深淵を覗こうじゃないか。
コメント