【テクニカル・上級編】 一意な値の数(n_distinct) – PostgreSQL

「なぜプランナは間違えるのか?」― n_distinct が支配するクエリ最適化の深淵

PostgreSQLのオプティマイザと対話していると、時折「なぜそんな非効率なプランを選んだんだ?」と問い詰めたくなる瞬間がありますよね。特に結合の順序が不自然だったり、想定外のNested Loopが選ばれたりした時。その裏側を覗くと、大抵の場合は統計情報の不整合、特に `n_distinct` の推定のズレが元凶です。

今日は、PostgreSQLのクエリチューニングにおいて最も重要でありながら、意外とブラックボックス視されがちな「n_distinct」について、少し深い話をしようと思います。

n_distinct とは何者か

`pg_stats` を覗くと、`n_distinct` という列があります。これは「その列にどれだけユニークな値が存在するか」という統計値です。

  • 正の数: そのままユニーク値の個数を表します。
  • 負の数: -0.5 なら「テーブル全行数の50%がユニークな値である」といった割合を指します。

この値がなぜ重要か。それはプランナが「どれくらいの行数が絞り込まれるか(セレクティビティ)」を計算する際の、もっとも基本的な係数だからです。

結合(JOIN)やグループ化(GROUP BY)のコスト見積もりは、この `n_distinct` をベースに計算されます。もしここが実態と乖離していれば、プランナは「どの結合アルゴリズムが最適か」「どの順序でテーブルを叩くか」という前提条件そのものを誤ることになります。

プランナが見ている景色

例えば、非常に高いカーディナリティ(ユニーク値が多い)を持つID列なのに、`n_distinct` が低く見積もられているとしましょう。プランナは「この列でフィルタリングしても、あまり行数は減らないだろう」と判断します。結果として、Index Scanではなく、強引なSequential Scanや、Nested LoopよりもHash Joinを優先するような判断を下すことになります。

逆に、実際には重複だらけの列なのに `n_distinct` が高く見積もられていると、プランナは「この列で絞り込めばほぼ1行に特定できるはずだ」と過信し、無謀なIndex Scanを選択してランダムI/Oの嵐を招きます。

大規模なデータセットを扱うとき、この1つの数値の狂いが「ミリ秒で終わるはずのクエリ」を「数分かかる重いバッチ」に変えてしまうのです。

なぜ数値がズレるのか:サンプリングの限界

ここでエンジニアとして知っておくべきは、`n_distinct` はあくまで「推定値」であるという点です。

PostgreSQLは、`ANALYZE` 実行時にテーブル全体をスキャンするわけではありません(そんなことをしたら本番環境は止まってしまいます)。`default_statistics_target` に基づいて、テーブルの一部をランダムサンプリングして統計を算出します。

データが偏っている(Skewがある)場合や、データの投入パターンに規則性がある場合、サンプリングは「たまたま特定の範囲ばかりを拾ってしまう」リスクを常に抱えています。これが、`n_distinct` が実態と乖離する根本的な理由です。

トラブルシューティングの処方箋

もし、特定のクエリがどうしても遅いと感じたら、まずは `EXPLAIN ANALYZE` を叩いてください。`actual rows`(実際)と `estimated rows`(見積もり)が大きく乖離していれば、犯人は統計情報です。

1. まずは基本の再計算:
単純に `ANALYZE table_name;` を実行する。まずはここからです。
2. 統計ターゲットを上げる:
特定の列のデータ分布が複雑なら、その列だけ統計情報の精度を上げます。
`ALTER TABLE table_name ALTER COLUMN column_name SET STATISTICS 500;`
デフォルトの100から大きく引き上げることで、サンプリング数が増え、推定精度が飛躍的に向上することがあります。
3. 相関関係の考慮:
もし「A列とB列の値は強く相関している」といったケースであれば、個別の `n_distinct` だけでなく、拡張統計(Extended Statistics)を検討してください。
`CREATE STATISTICS stats_name ON col_a, col_b FROM table_name;`
これを設定することで、複数の列を組み合わせた時のカーディナリティをプランナが正しく認識できるようになります。

最後に:データベースと対話するということ

結局のところ、クエリチューニングは「データベースの脳内イメージ」を「現実のデータ構造」に近づけていく作業です。

`n_distinct` を弄るというのは、プランナという優秀な助手に対して「このデータはこういう顔をしているんだよ」と教えてあげるようなもの。内部アーキテクチャの挙動を理解し、統計情報の裏側にある「サンプリングの確率論」まで想像できるようになれば、PostgreSQLはこれまで以上に頼もしいパートナーになります。

皆さんのデータベースが、今日も最適な実行計画を立ててくれることを願っています。さて、そろそろログを掘りに行ってきますか。

コメント

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