【テクニカル・上級編】 N-Distinct統計 – PostgreSQL

なぜ、PostgreSQLの「見積もり」は時に残酷なほど外れるのか?

PostgreSQLのクエリプランナと格闘していると、誰しも一度は突き当たる壁があります。「統計情報は最新だし、`ANALYZE`も回した。なのに、なぜこんなに的外れな実行計画を立てるのか?」という問いです。

特に、`WHERE`句で複数の列を組み合わせたフィルタリングを行っている時、その見積もりはしばしば現実から乖離します。その原因の多くは、PostgreSQLがデフォルトで「各列の選択率は独立している」と仮定して計算してしまうことにあります。

ここで重要になるのが、「多変量統計(Multivariate Statistics)」、特に`CREATE STATISTICS`で作成できる「N-Distinct統計」です。今日は、この少しマニアックですが強力なツールについて、深く掘り下げてみましょう。

—

1. 「独立性の仮定」という罠

例えば、あるECサイトのデータベースで、`pref_code`(都道府県)と`city_code`(市区町村)という2つの列があるとします。

論理的に考えれば、`pref_code = ’13’`(東京都)と`city_code = ‘101’`(千代田区)の組み合わせは非常に稀ですが、`pref_code = ’13’`と`city_code = ‘123’`(どこかの地方都市)の組み合わせは存在し得ません。

しかし、PostgreSQLのプランナは、単一列の統計情報しか見ていない場合、これらを無関係な変数として扱います。
「都道府県が東京である確率」と「市区町村が特定のコードである確率」を単純に掛け合わせて、結果セットの行数を推定してしまうのです。結果、過小評価された行数は、最悪なことに「Nested Loop」という地獄の入り口をプランナに選ばせてしまいます。

2. N-Distinct統計が解決するもの

`CREATE STATISTICS`コマンドを使って定義する「N-Distinct」は、特定の列の組み合わせにおける「ユニーク値の数」をオプティマイザに教え込む機能です。

CREATE STATISTICS stats_pref_city (ndistinct) ON pref_code, city_code FROM users;

これを実行すると、PostgreSQLは単なる1列ごとの統計だけでなく、指定した列の組み合わせでの「本当のユニーク数」をカタログ(`pg_statistic_ext`)に保持します。

これにより、プランナは「あ、この2つの列の組み合わせは非常に限定的だ(ユニーク値が多い)」という相関関係を認識できるようになります。結果として、結合時の行数見積もりが劇的に改善され、Hash JoinやMerge Joinといった、データ量に応じた適切な実行計画が選択されるようになるのです。

3. 実践:いつ使うべきか?

この機能を導入すべきタイミングは、決して「なんとなく精度を上げたい」という時ではありません。

  • 実行計画の「Estimated rows」と「Actual rows」が数桁レベルで乖離している箇所を見つけた時
  • 複合インデックスを貼るほどではないが、頻繁に組み合わせてフィルタリングされる列がある時
  • クエリの実行計画が、パラメータの値によって極端に不安定になる時

これらに該当する場合、`EXPLAIN ANALYZE`の出力と睨めっこしながら、対象となる列の組み合わせに対して迷わずこの統計を作成すべきです。

4. 深淵を覗く:内部的な注意点

ただし、魔法の杖ではありません。いくつか注意すべき点があります。

  • メモリ消費: 統計作成時にはそれなりのメモリとCPU時間を消費します。テーブルが巨大な場合、`ANALYZE`の負荷を考慮する必要があります。
  • 過信は禁物: 統計が古くなれば、当然見積もりはまたズレます。`ANALYZE`による自動更新に頼りすぎず、データ分布が劇的に変わるバッチ処理の直後などは、手動で統計を更新する運用を組み込むべきです。
  • 相関の有無: 全く無関係なデータ同士にN-Distinctを設定しても、プランナの計算コストが増えるだけで恩恵はありません。あくまで「論理的な相関がある列」に絞って適用するのが、熟練エンジニアの流儀です。

—

最後に

PostgreSQLのプランナは、ある種「統計という断片的な情報から、データ世界の全体像を想像しようとする芸術家」のようなものです。我々エンジニアの役割は、その芸術家に、より鮮明な「現実」のヒントを与えること。

`CREATE STATISTICS`はそのための極めて強力な武器です。クエリが遅い、実行計画がおかしい。そう感じた時、ぜひ一度`pg_statistic_ext`の向こう側を想像してみてください。

データベースのチューニングに「終わり」はありません。その果てしない深淵を、これからも一緒に楽しんでいきましょう。

コメント

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