なぜPostgreSQLのプランナは、たまに「とんでもない嘘」をつくのか? ― 関数従属性統計による見積もり精度の突破口
PostgreSQLのクエリチューニングにおいて、最も頭を悩ませるのは「実行計画の乖離」ですよね。「数万件はヒットするはずのクエリが、プランナの甘い見積もりによってNested Loopを選択し、本番環境で悲劇を生む」――。そんな光景を、皆さんも一度は目にしたことがあるはずです。
その原因の多くは、プランナが「列同士の相関関係」を無視し、独立事象として確率を乗算してしまうことにあります。今回は、この見積もり精度を劇的に改善する強力な武器、「関数従属性統計(Functional Dependency Statistics)」について掘り下げていきましょう。
—
1. 悲劇の始まり:独立性の仮定という「幻想」
PostgreSQLの標準的な統計情報(`pg_statistic`)は、基本的には列ごとの分布しか保持していません。例えば、ECサイトのデータベースで「郵便番号(zip_code)」と「市区町村(city)」という列があったとします。
SELECT FROM addresses
WHERE zip_code = ‘100-0001’ AND city = ‘千代田区’;
プランナは、`zip_code`の選択率と`city`の選択率をそれぞれ計算し、単にそれらを掛け合わせます。しかし、現実には`zip_code`が決まれば`city`は自ずと一意に決まります。プランナにとってこの2つは「独立した条件」に見えますが、実際には「完全な従属関係」にあるわけです。
結果として、プランナは「絞り込みが非常に強力である」と過小評価してしまい、不適切なプランを選んでしまう。これが、複雑なWHERE句でクエリが遅くなる典型的なパターンです。
2. 関数従属性統計がもたらす「真実」
PostgreSQL 10で導入された拡張統計(Extended Statistics)は、こうした列間の相関をプランナに教え込むための仕組みです。特に「関数従属性」は、まさに今回のようなケースのためにあります。
実装の勘所
まずは、対象の列に対して統計オブジェクトを作成しましょう。
CREATE STATISTICS stx_zip_city (dependencies)
ON zip_code, city FROM addresses;
これを作成した後に `ANALYZE` を実行すると、PostgreSQLは内部的に「`zip_code`の値が決定すれば、`city`の値も決まる」という関係性を検出し、メタデータとして保持します。
内部アーキテクチャの視点で見ると、これは`pg_statistic_ext_data`カタログに格納されます。プランナはこの統計を読み込むことで、AND条件で結合された複数の列を「独立した確率の積」としてではなく、「従属関係にある制約」として再計算できるようになります。
3. なぜ「手作業」のインデックスだけではダメなのか
多くのエンジニアが「複合インデックスを貼ればいいのでは?」と考えがちですが、それは少し違います。
複合インデックスはあくまで「検索効率」を高めるための構造であり、プランナに「列間の相関」というメタ情報を教えるものではありません。複合インデックスがあっても、プランナが内部的に行う「行数の見積もり(Cardinality Estimation)」は、統計情報が正しくなければ的外れな数値のままです。
関数従属性統計は、インデックスの有無に関わらず、「プランナの見積もり能力そのものを補正する」という点で、極めて純粋で強力な最適化手法なのです。
4. トラブルシューティングの現場から
私が現場でよく行う、この機能の有効性を確認する手順を共有します。
1. 見積もり乖離の特定: `EXPLAIN ANALYZE` を実行し、`rows`(見積もり)と `actual rows`(実測)を比較します。桁違いに乖離している箇所があれば、そこがボトルネックです。
2. 相関の疑い: 複数列のWHERE条件に、論理的な相関がないか確認します(地域と郵便番号、カテゴリとサブカテゴリなど)。
3. 統計の適用: `CREATE STATISTICS` を適用し、再度 `ANALYZE` します。
4. 検証: 再び `EXPLAIN ANALYZE` を実行し、見積もり精度が改善したかを確認します。
もし、これでも改善しない場合は、「多変量統計(mcv: Most Common Values)」の導入を検討してください。関数従属性はあくまで「一方が決まれば他方も決まる」という強い相関に有効ですが、より複雑な分布を持つデータにはMCVリストが特効薬になります。
最後に:データベースは「正直」であるべき
クエリチューニングとは、突き詰めれば「プランナと対話すること」です。データベースエンジンの内部挙動を理解し、適切なヒント(統計情報)を与えることで、プランナは驚くほど賢く振る舞うようになります。
「なぜこのクエリは遅いのか?」と悩んだとき、まずはプランナの「計算ミス」を疑ってみてください。そして、データの中に潜む「従属関係」を解き明かし、統計情報に書き込んでやる。それだけで、システムのパフォーマンスは劇的に変わります。
皆さんのDBが、今日も健やかに動くことを祈っています。それでは、良いチューニングライフを。
コメント