【テクニカル・上級編】 n-distinct係数統計 – PostgreSQL

そのクエリ、なぜ「そこ」で見積もりを外すのか? ―― PostgreSQLの拡張統計(n-distinct)を使いこなす

PostgreSQLのクエリオプティマイザは、基本的には非常に優秀な「数学者」です。しかし、どれほど優秀な数学者であっても、手元にあるデータが偏っていたり、列間の相関関係を隠されていたりすれば、導き出す答え(実行計画)は無惨なものになります。

特に、現場でよく遭遇する「なぜかNested Loopを選択して爆死している」クエリの多くは、単一列の統計情報の限界に起因しています。今日は、その救世主である「拡張統計(Extended Statistics)」、特に `n-distinct` 係数に焦点を当てて、その深淵を覗いてみましょう。

—

なぜ「列の独立性」という前提が崩れるのか

PostgreSQLの標準的な統計情報(`pg_statistic`)は、基本的に「列単位」で収集されます。つまり、オプティマイザは「列Aの値の分布」と「列Bの値の分布」は知っていますが、それらが組み合わさったときに何が起きるかについては、単純な掛け算(独立性の仮定)で推定を行います。

例えば、`都道府県` と `市区町村` という列があるとしましょう。「東京都」のカーディナリティ(n-distinct)と「新宿区」のカーディナリティを個別に評価し、それらを掛け合わせて「東京都新宿区のレコード数」を予測する。……当然、現実はそう甘くありませんよね。この「相関」をオプティマイザに教え込むのが、`CREATE STATISTICS` による拡張統計の役割です。

n-distinct係数の真価:GROUP BYのコスト見積もり

拡張統計にはいくつか種類がありますが、特に `n-distinct` 係数は、`GROUP BY` 句のコスト見積もりにおいて絶大な威力を発揮します。

多くのエンジニアは、結合条件の最適化のために `dependencies` を使うことには慣れています。しかし、`n-distinct` は少し毛色が違います。これは、指定した複数列の組み合わせにおいて、実際に何通りの一意な組み合わせが存在するかを直接統計として保持します。

これがないと、オプティマイザは `GROUP BY (col1, col2)` の結果セットがどの程度のサイズになるかを、各列のカーディナリティの積として過大に見積もります。その結果、ハッシュ集計(HashAggregate)を想定しているのに、メモリが足りないと判断して外部ソート(Disk spill)を選択してしまい、地獄のようなI/O待ちが発生するわけです。

パフォーマンストラブルシューティングの勘所

現場で「プランが変だ」と感じたとき、私はまず `EXPLAIN ANALYZE` を叩き、`rows`(推定値)と `actual rows`(実測値)の乖離を確認します。

もし、`GROUP BY` や `DISTINCT` の箇所でこの乖離が顕著であれば、それは統計情報が列間の相関を捉えきれていないサインです。ここで、以下のように拡張統計を作成します。

CREATE STATISTICS stats_composite_keys (ndistinct)
ON col_a, col_b FROM my_table;

これを作成した後、`ANALYZE` を実行することで、PostgreSQL内部の `pg_statistic_ext` に新たな知見が刻まれます。

注意点: 闇雲に作成するのは避けるべきです。拡張統計もまた、`ANALYZE` 時のオーバーヘッドになります。また、あまりに多くの組み合わせを定義すると、オプティマイザの計算コスト自体が肥大化します。私が推奨するのは、「クエリのボトルネックとなっている複雑なフィルタやグループ化が行われている箇所」に絞って適用することです。

内部アーキテクチャの視点から

興味深いのは、PostgreSQLがこの統計をどう利用するかです。`n-distinct` の拡張統計が存在する場合、オプティマイザは「掛け算による推定」を放棄し、保持された実測値の係数を使って `relativedistinct` を補正します。

これは、単なる「行数の推定」の問題に留まりません。行数の見積もりが正確になるということは、HashAggregateで割り当てるべき `work_mem` の見積もり精度も上がることを意味します。結果として、メモリ内に収まるか、ディスクへ溢れるかの運命的な分岐を、オプティマイザがより正確に判断できるようになるのです。

最後に:統計は「育てていくもの」

DBAの仕事は、一度設定して終わりではありません。データ量が増え、アプリのクエリパターンが変われば、最適だった統計も陳腐化します。

「なぜか遅い」というトラブルに直面したとき、インデックスを増やす前に、まずはPostgreSQLが「データの姿」を正しく認識できているかを確認してください。拡張統計は、エンジニアとオプティマイザとの間の「共通言語」のようなものです。

もし皆さんの環境で、複雑なクエリのプランに頭を抱えているのであれば、ぜひ一度 `CREATE STATISTICS` を試してみてください。その瞬間、クエリの実行計画が劇的に洗練される様を目の当たりにするはずです。

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

コメント

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