【テクニカル・上級編】 NULL率(null_frac) – PostgreSQL

なぜ、NULLの割合がクエリプランを狂わせるのか:PostgreSQLの統計情報の深淵

現場で泥沼のパフォーマンストラブルに直面したとき、多くのエンジニアはまずインデックスの有無や `EXPLAIN ANALYZE` のコストを疑います。もちろんそれは正しいアプローチですが、もし「なぜかプランナがとんでもない結合順序を選択している」という事態に陥ったなら、一度 `pg_stats` の奥底を覗いてみるべきです。

特に、意外と軽視されがちなのが `null_frac` です。ただの「NULLの割合」と侮るなかれ。このわずかな数字が、クエリの実行計画を大きく歪ませるトリガーになることが多々あるのです。

null_frac がプランナの判断を変える瞬間

PostgreSQLのコストベースオプティマイザ(CBO)は、クエリを実行する前に「この条件に合致する行はどれくらいあるか(選択率)」を予測します。このとき、プランナは統計情報カタログである `pg_stats` を参照します。

例えば、`WHERE column_a IS NOT NULL` という条件があるとき、プランナは単なる直感ではなく、`null_frac` を使って次のように計算します。

— 選択率の簡易モデル
選択率 = 1.0 – null_frac

もし、統計情報が古く、`null_frac` が実際のデータと大きく乖離していたらどうなるか。プランナは「ほとんどの行がNULLだ(あるいはその逆)」と勘違いし、結果として不要な全件スキャンを選択したり、Nested Loopのループ回数を極端に見誤って悲惨なパフォーマンスを引き起こしたりします。

現場で遭遇した「統計情報の罠」

以前、数億行規模のテーブルで「特定の列がNULLではないレコードだけを集計する」というクエリが、インデックスがあるにもかかわらず全件スキャン(Seq Scan)を選択し続けるという問題に遭遇しました。

原因は、データの投入プロセスにありました。バッチ処理で大量のデータを流し込んだ直後、`ANALYZE` を実行する前にクエリが走っていたのです。

  • 発生していたこと: `null_frac` が古い統計値のまま固定され、実際にはNULLがほぼ皆無の列に対して「かなりの確率でNULLが含まれているはずだ」という誤った推定がなされていた。
  • 結果: プランナは「この条件で絞り込んでも大して行数は減らないだろう」と判断し、コストの低いインデックススキャンを捨てて、巨大なテーブルのSeq Scanを選択していた。

このとき痛感したのは、「NULLの分布は、データのライフサイクルと密接に関係している」ということです。特に、後から値が埋められるようなカラム(例:ステータス管理フラグや処理日時)において、`null_frac` は刻一刻と変動します。

トラブルシューティングの勘所

もし皆さんの環境で「妙なプラン」が出ているなら、まずは以下の手順で統計を確認してみてください。

1. pg_stats を直視する

まずは、問題のカラムの統計を疑います。

SELECT null_frac, n_distinct, most_common_vals
FROM pg_stats
WHERE tablename = ‘your_table’ AND attname = ‘your_column’;

ここで、`null_frac` が `0` なのか `1` なのか、あるいは現実離れした数値になっていないかを確認します。

2. インデックスの「NULL値」を考慮する

これは意外な落とし穴ですが、PostgreSQLのB-treeインデックスは、デフォルトでNULL値を含みません。`WHERE column IS NULL` というクエリを高速化したい場合、通常のインデックスでは役に立たず、インデックスが無視されることになります。

このようなケースでは、「部分インデックス(Partial Index)」が特効薬になります。

CREATE INDEX idx_nullable_col_is_null
ON your_table (column_a)
WHERE column_a IS NULL;

こうすることで、NULL値だけを効率的に抽出できる軽量なインデックスが生成されます。`null_frac` が高い(=NULLが多い)カラムを頻繁に検索対象にするなら、検討の価値は十分にあります。

最後に:統計は「生き物」である

データベースエンジニアとして長く仕事をしていると、「統計情報は一度作ったら終わり」ではないことを骨身に染みて理解します。

アプリケーションの仕様変更で「NULLを許容するカラム」が増えたり、あるいはデータの傾向が変わったりするたびに、データベースの「世界観(=統計情報)」と「現実」の間にズレが生じます。`null_frac` は、そのズレを測るための非常に重要なバロメーターです。

もし次にクエリチューニングで行き詰まったら、`EXPLAIN` の数字を眺めるだけでなく、データそのものがプランナにどう「見えているのか」を想像してみてください。その視点を持つだけで、ボトルネックの解決スピードは格段に上がるはずです。

それでは、良いチューニングライフを。

コメント

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