Skip to content

필사 모드: Postgres 19の pg_plan_advice — ヒントを拒み続けてきたプロジェクトが出した妥協案

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

はじめに — "私たちはヒントを入れない"

PostgreSQLコミュニティに長年掲げられてきた一文があります。プロジェクトWikiのOptimizer hints discussionページはこう始まります — 私たちは、他のデータベースでよく実装されているようなやり方でヒントを実装するつもりはない。

その下に、理由が6つ並んでいます。要約するとこうです。

  1. アプリケーションコードの保守性悪化 — クエリにヒントを埋め込むと、後で大規模なリファクタリングが必要になる。
  2. アップグレードの妨げ — 今日役立つヒントが、アップグレード後は性能を落とす。
  3. 悪いDBAの習慣を助長 — 本当の原因を探す代わりにヒントを貼って終わらせる。
  4. データサイズによって合わなくなる — テーブルが小さいときに正しかったヒントは、大きくなると誤りになる。
  5. 実際には性能が悪化する — たいていはオプティマイザの方が正しい。
  6. プランナーの改善を妨げる — ヒントを使う人は、問題をプロジェクトに報告しない。

このリストは単なる意固地さではありません。オラクルやMySQLでヒントが実際に引き起こした問題をまとめたものに近いです。ところが同じページはこう付け加えます — 他のヒントシステムで観察された問題を避ける提案であれば関心がある。

2026年6月4日に出たPostgreSQL 19 Beta 1に、その提案が入りました。Robert Haasが作ったpg_plan_advicepg_stash_advice、2つのcontribモジュールです。リリースノートの表現は「ユーザーがプランナーの決定を安定させ、制御できるようにする」です。

本稿は、それが正確には何であり、上の6項目のうち何を消し、何を消せなかったかを見ていきます。ちなみに19の最終リリースは2026年9〜10月ごろと予告されており、現時点ではベータです。

ヒントではなく「アドバイス」と呼ぶ理由

公式ドキュメントF.30. pg_plan_adviceは、このモジュールをこう説明します — プランナーの主要な決定を「plan advice」という専用のミニ言語で記述し、再現し、変更できるようにする。狙いは二つです。ユーザーが良いと判断したプランを固定すること、そしてプランナーが最適でないと考えているプランを実験してみることです。

名前がhintではなくadviceであるのには設計上の理由があります。既存のヒントと異なる点が3つあります。

第一に、アドバイスはSQLの外に生きています。 オラクルのヒントやpg_hint_planは、クエリテキストの中(コメント)にヒントを入れます。pg_plan_advicepg_plan_advice.adviceというGUCで与えるか、pg_stash_adviceを通じてクエリIDをキーに別のストアへ入れます。アプリケーションのSQL文字列には手を触れません。Wiki反対理由の1番を正面から狙った設計です。

第二に、プランナーを置き換えるのではなく、探索空間を狭めます。 これが最も重要な違いです。ドキュメントの文をそのまま移せば — どんなアドバイスを与えても、そのクエリについてコアプランナーが検討したはずのプランしか出てこない。つまりアドバイスは「このプランを使え」という命令ではなく、「この候補たちから選べ(それ以外は除く)」に近いものです。プランナーが意味的に誤った結果を出すと考えているプランは、アドバイスが何と言おうと出てきません。

第三に、部分だけを固定することもできます。 アドバイス文字列をまるごと詰め込んでプラン全体を固定することもできますが、必要な一部分だけを指定して残りはプランナーに任せることもできます。Christophe PettusはHints, Part 3: Advice, Not Orders(2026-04-21)で、この抑制的な使い方がWiki反対理由の2番(アップグレードの妨げ)と4番(データサイズ)を和らげる余地だと指摘します。ただし彼はすぐに但し書きを付けます — このツールは抑制を許容するだけで、強制はしない。

アドバイスは実際にどんな見た目か

抽象的なままだとピンとこないので、実物を見てみましょう。モジュールをロードすると、EXPLAINPLAN_ADVICEオプションが追加されます。今プランナーが選んだプランを、アドバイス文字列として書き戻してくれる機能です。

EXPLAIN (COSTS OFF, PLAN_ADVICE)
        SELECT * FROM join_fact f JOIN join_dim d ON f.dim_id = d.id;

ドキュメントに載っている出力はこうです。

Generated Plan Advice:
   JOIN_ORDER(f d)
   HASH_JOIN(d)
   SEQ_SCAN(f d)
   NO_GATHER(f d)

読み方は直感的です — fとdをこの順序でジョインし、dを内側に置いてハッシュジョインし、両方とも順次スキャンし、並列Gatherは使わなかった。この文字列をそのまま与え直せば、同じプランが再現されます。

SET pg_plan_advice.advice = 'JOIN_ORDER(f d)';

アドバイス言語の語彙は意図的に狭く作られています。ドキュメントが定義するタグはおおむね4つのカテゴリです。

  • スキャン方式: SEQ_SCAN(target), TID_SCAN(target), INDEX_SCAN(target index_name), INDEX_ONLY_SCAN(target index_name), BITMAP_HEAP_SCAN(target), DO_NOT_SCAN(target)
  • ジョイン順序: JOIN_ORDER(...) — 括弧でくくって結合順序を強制したり、緩めたりできます
  • ジョイン方式: HASH_JOIN(...), MERGE_JOIN_PLAIN(...), MERGE_JOIN_MATERIALIZE(...), NESTED_LOOP_PLAIN(...), NESTED_LOOP_MEMOIZE(...), NESTED_LOOP_MATERIALIZE(...)
  • 並列化: GATHER(...), GATHER_MERGE(...), NO_GATHER(target)

ここにパーティションワイズジョイン(PARTITIONWISE)とセミジョインの一意性(SEMIJOIN_UNIQUE / SEMIJOIN_NON_UNIQUE)の制御が加わります。対象の指定はエイリアス基準で、同じエイリアスが複数回出てくる場合はf#2のように出現順番が付き、パーティションやサブプランまで指し示す拡張構文もあります。

Daniel Westermannの実践記録(2026-03-13)がこの流れをよく示しています。100万行のt1と200万行のt2をジョインすると、プランナーはこんなアドバイスを吐き出します。

JOIN_ORDER(t2 t1) NESTED_LOOP_MEMOIZE(t1) SEQ_SCAN(t2) INDEX_SCAN(t1 public.t1_pkey) NO_GATHER(t1 t2)

そのまま与え直すと、すべてmatchedと表示され、プランはそのままです。逆にジョイン順序だけを逆にしてJOIN_ORDER(t1 t2)を与えると、プランはMerge Join + Sort + Materializeに変わります。アドバイスが実際に効いていることを確かめる、最短の実験です。

アドバイスが効いたかどうかを教えてくれるフィードバック

この部分が個人的にはこの設計で一番気に入っています。ヒントシステムの持病は「ヒントを使ったのに無視されたようだが、確かめる方法がない」です。pg_plan_adviceはアドバイスの一行ごとに、コメントで判定を付けてくれます。

INDEX_SCAN(f no_such_index) /* matched, inapplicable, failed */
HASH_JOIN(f) /* matched */
HASH_JOIN(g) /* not matched */
JOIN_ORDER(f g) /* partially matched */

判定の語彙はドキュメント基準で6種類です — matched(対象をプランニング中に見て、適用可能だった)、partially matched(一部だけ見た、あるいは一緒には見なかった)、not matched(対象をまったく見なかった)、inapplicable(存在しないインデックスのようにタグを適用できなかった)、conflicting(2つのアドバイスが互いに矛盾する)、failed(matchedになったアドバイスに最終プランが従わなかった)。

pg_plan_advice.feedback_warningsをオンにすると、アドバイスが強制されなかったときに警告を出します。デフォルトはoffです。これがなぜ重要かというと — アドバイスは時間が経つと腐っていく(スキーマが変わり、インデックスが消え、データ分布が変わるので)ため、腐ったアドバイスが静かに無視されるのと、うるさく警告を出すのとでは、運用上まったく違う話だからです。

pg_stash_advice — アプリケーションを直さずにプランを固定する

pg_plan_adviceはセッションGUCとしてアドバイスを与えます。実験には向いていますが、本番で問題のクエリを一つ固定するためだけに、セッションごとにSETを打つわけにはいきません。そこでF.33. pg_stash_adviceがあります。

動作は単純です。クエリIDからアドバイス文字列へのマッピング(ドキュメントの用語でadvice stash)を動的共有メモリに置き、プランニング中のクエリのIDがスタッシュにあれば、そのアドバイスを自動的に適用します。Shaun ThomasがpgEdgeの記事(2026-06-05)で示す使い方はこうです。

SELECT pg_create_advice_stash('production_tuning');
SELECT pg_set_stashed_advice('production_tuning', 5424487836266966148,
    'INDEX_SCAN(f idx_fact_dim_id) NESTED_LOOP_PLAIN(f)');
SET pg_stash_advice.stash_name = 'production_tuning';

pg_stash_advice.stash_nameALTER DATABASE ... SETALTER ROLE ... SETでも設定できるので、実質的にアプリケーションのコードを一行も変えずに、特定のクエリのプランを固定できます。SQL Serverのplan guideやDB2のプラン固定と同じ系統の運用パターンです。

前提条件がいくつかあります。shared_preload_librariesに入れて再起動する必要があり、compute_query_idがオンになっている必要があります(autoならモジュールのロード時に自動でオンになります)。デフォルトでは、スタッシュはデータディレクトリのpg_stash_advice.tsvに30秒間隔で永続化され、この間隔はpg_stash_advice.persist_intervalで調整します。

Wikiの反対6項目、採点表

ここからが面白いところです。上の設計が、プロジェクト自身が書き残した反対理由をどれだけ消したか、一つずつ見ていきましょう。

1. アプリケーションの保守性 — 消えました。 アドバイスはSQLの外にあるので、クエリテキストは汚染されません。これは明確な改善です。

5. たいていはオプティマイザが正しい — 構造的に消えました。 アドバイスは、コアプランナーが検討するプラン集合の中からしか選べません。プランナーが「これは誤った結果を出す」と判断したプランは、アドバイスをもってしても引き出せません。つまり最悪のケースは「遅いプラン」であって「誤ったプラン」ではありません。これがオラクル式ヒントとの根本的な違いです。

6. プランナーの改善を妨げる — 部分的に消えました。 フィードバックのコメントとEXPLAIN (PLAN_ADVICE)のおかげで、「プランナーが何を選び、自分が何を強制したか」が機械可読な形で残ります。バグレポートに添付するのに良い形式です。ただし、アドバイスを貼って黙って済ませる人を止めることはできません。

2. アップグレードの妨げ / 4. データサイズ — 一部だけ消えました。 アドバイスを最小限にとどめれば、残りはプランナーが引き続き適応します。しかし、プラン全体をまるごと固定すると、この2つの問題はそのまま戻ってきます。Pettusの指摘はまさにこの点です — プランを完全に固定すると、アップグレードの妨げとスケーリングの問題が再び呼び戻される。ツールが抑制を許容するだけで、強制はしないからです。ドキュメントも同じことを言っています — データ分布が変わったときにプランナーがプランを変えられることは、一般的にはバグではなく機能である。

3. 悪いDBAの習慣 — 消えませんでした。 Pettusの一文が最も正直です — 統計の問題を直す代わりにpg_plan_adviceを手に取るDBAは、同じように悪い結果を得る。そして彼はこれを「技術の問題ではなく文化の問題」に分類します。ツールでは解決できない種類の反対理由です。

まとめると、6項目のうち2つは確実に、1つはかなりの部分、2つはユーザーが抑制的に使ったときだけ、1つはまったく消えませんでした。悪くない成績ですが、完勝ではありません。

正直な限界

ドキュメント自身が認めていることが、かなりあります。ここは読み飛ばしてはいけません。

制御できない領域が広いです。 集計をソートでやるかハッシュでやるかは決められません。eager aggregationやpartitionwise aggregationといった戦略にも手を出せません。UNIONINTERSECTのような集合演算の動作も制御対象ではありません。depeszがまとめたコミットメッセージのとおり、この機能はジョイン方式・スキャン種類のような結果を狙うものであり、コスト推定や選択度(selectivity)そのものには手を触れません。

プランを変えなくてもコストが付きます。 pg_stash_adviceのドキュメントの表現は明確です — プランアドバイスを適用することは、プランを変えなくても、目に見える(noticeable)性能コストを伴う。必要なときだけ使えと釘を刺しています。pgEdgeの記事も、アドバイスがプランナーのループを途中で断ち切るため、小さなペナルティが避けられないと説明しています。具体的な数値はどちらも示していないので、ここで数字を作り出すことはしません。

セキュリティモデルはまだ未成熟です。 ドキュメントは"unsophisticated"という単語を使っています。スタッシュの作成・変更はスーパーユーザーや権限を持つロールにしかできませんが、どのユーザーでも自分のセッションにpg_stash_advice.stash_nameを設定できるため、スタッシュの内容が非権限ユーザーに露出する可能性があります。アドバイス文字列にはインデックス名やテーブルのエイリアスが入るので、スキーマ情報の漏洩経路になります。

メモリを消費します。 スタッシュは動的共有メモリに置かれます。どれだけ多くのアドバイスを入れるかを決める際は、この点を考慮せよというのがドキュメントの助言です。

そしてこれは1.0です。 Haas本人が自身のブログで(まだコミット前だった時点で)こう書いています — これは非常に明らかにバージョン1.0の技術だ。限界が多く、まだかなり粗いところがいくつもある。コミットは2026年3月12日に行われベータに載りましたが、その自己評価が誤っていたと考える理由はありません。

最後にdepeszの一言が、このツールの性格を最もよく要約しています — 強力であり、同時にフットガンである。要求が変わっても本番に残り続けるアドバイスが害をなしうるという意味です。

設計の裏話 — mechanism, not policy

もう一つ知っておく価値があることがあります。Haasはこの作業を意図的に最小限にとどめました。彼の言葉を借りれば — 私は意図的にメカニズムとポリシーを切り離そうとした。pg_plan_adviceは、アドバイスを生成する最も単純な方法(つまりEXPLAIN)と、アドバイスを与える最も単純な方法(つまりpg_plan_advice.advice設定)だけを提供するコア技術だが、プラグ可能である。

この切り離しがなぜ重要かというと、その下に敷かれたインフラがpg_plan_advice自体より長生きしうるからです。pganalyzeがまとめたところによれば、HaasはRelOptInfopgs_maskというビットマスクを入れ、拡張が「このテーブルには順次スキャンを使うな」といった制約をプランナーの最も早い段階で課せるようにしました。既存のpg_hint_planにはこれがなかったため、コアソース2,500行以上をコピーしてグローバル設定を操作する必要がありましたが、このインフラの上でならそのコピーの大半を消せるというのがpganalyzeの観察です。つまり、pg_plan_advice自体が気に入らなくても、サードパーティのヒント拡張がずっとリスクの低いやり方で再実装される道が開かれたことになります。

Haasが最初に提案した3つのモジュールのうち、pg_collect_adviceは19のドキュメントのcontrib一覧にありません。実際に入ったのはpg_plan_advice(F.30)とpg_stash_advice(F.33)の2つです。

では、いつ使い、いつ使うべきでないか

ドキュメントの警告から移します — プランナーはたいてい良い決定を下すので、その判断を覆すことは簡単に逆効果になりうる。プランアドバイスは、プランナーの選択を制約するリスクが利益より小さいときにだけ使うことが重要である。

手を出す前に、まずやるべきことがあります。

  • ANALYZEがきちんと回っているか、統計目標が十分かを確認する
  • 相関のあるカラムなら、拡張統計(CREATE STATISTICS)をまず試す
  • random_page_costeffective_cache_sizeのようなコストパラメータが実際のハードウェアと合っているか点検する
  • インデックスがないせいで起きた問題を、ジョイン方式の強制でごまかそうとしていないか自問する

この4つを全部やってみてもなお、プランナーが繰り返し間違え、その間違いがビジネスに痛手を与える特定のクエリ — そこからがpg_plan_adviceの出番です。Pettusの言葉どおり、統計チューニングの代替ではなく、避けられないプランナーの誤判断に対する最後の手段です。

使うべきでないとき

  • まだ19はGAではありません。今はベータであり、最終リリースは2026年9〜10月ごろの予定です。今、本番導入を計画する段階ではありません。
  • プランナーの問題ではなく統計の問題であるとき。ほとんどの場合がここに当てはまります。
  • クエリ数個ではなく数十個を固定したくなったとき。その時点では、ツールではなくスキーマや統計、ハードウェアに本当の問題があります。
  • データ分布が変わり続けるテーブル。今日正しいアドバイスは、来月には誤りになります — Wiki反対理由の4番は、この設計でも消えていません。
  • 誰がいつなぜアドバイスを設定したかを記録する文化がないチーム。持ち主のいないアドバイスは、数か月後に誰も触れない地雷になります。

使うならこう使う

アドバイスは最小限にとどめてください。ドキュメント自身が直接勧めるやり方がこれです — アドバイス文字列を絞り込んで必要な部分だけを制御し、プランナーの誤判断リスクが大きい場所にだけ適用せよ。プラン全体をまるごとコピー&ペーストした瞬間に、Wiki反対理由の2番と4番がそのまま戻ってきます。そしてfeedback_warningsをオンにして、腐ったアドバイスが静かに死なないようにしてください。

おわりに

pg_plan_adviceを「Postgresがついにヒントを受け入れた」とまとめると、2か所で間違えます。第一に、これは命令ではなく制約です — プランナーがそもそも検討したはずのプランの中からしか選ばないので、使い方を誤っても最悪は遅いプランであって、誤ったプランではありません。第二に、これはポリシーではなくメカニズムです — Haasは最小限の土台だけを置き、残りは拡張に委ねました。

プロジェクトが長年守ってきた反対リストに、この設計が答えたのはおよそ半分強です。SQLの汚染と「オプティマイザはたいてい正しい」は構造的に解決され、アップグレード・スケーリングの問題はユーザーが抑制的であるときだけ和らぎ、悪いDBAの習慣はツールが解決できる問題ではありません。その半分が本物の改善であることと、残りの半分が依然としてあなたの責任であることは、どちらも事実です。

19がGAになったら、統計とコストパラメータを全部整えてもなお動かないその一つのクエリに、必要な分だけ使ってください。それが、このツール自身が求める使い方です。

参考資料

현재 단락 (1/96)

PostgreSQLコミュニティに長年掲げられてきた一文があります。プロジェクトWikiの[Optimizer hints discussion](https://wiki.postgresql.or...

작성 글자: 0원문 글자: 11,216작성 단락: 0/96