「なぜ俺のクエリは遅いのか」を統計情報から紐解く:PostgreSQLのプランナとANALYZEの真実
PostgreSQLのクエリチューニングをしていると、必ずぶち当たる壁があります。「インデックスは貼った、結合条件も正しい。なのになぜプランナはあえてフルスキャンを選ぶんだ?」という疑問です。
経験豊富なエンジニアなら一度は体験したことがあるでしょう。その答えの多くは、実は`pg_statistic`という名の「データベースの脳内イメージ」と「現実のデータ」の解離にあります。今日は、PostgreSQLがいかにしてコスト計算を行い、そこに`ANALYZE`というスパイスがどう効いてくるのか、少し深い話をしましょう。
プランナのコスト計算、その危ういバランス
PostgreSQLのクエリオプティマイザは、基本的にはコストベースです。彼らはクエリを実行する前に、数千から数百万通りの実行パスをシミュレーションし、最も「安上がり」なルートを選択します。
しかし、その見積もりはあくまで「統計情報に基づいた推測」に過ぎません。プランナが計算に使うのは、主に以下の要素です。
- タプルの総数(reltuples)
- ページ数(relpages)
- 各カラムの分布(n_distinct, most_common_vals, histogram_boundsなど)
ここが肝です。プランナは「テーブルに何行あるか」を知りたがりますが、それを正確に数えるために毎回フルスキャンをかけるほど、データベースは暇ではありません。そこでPostgreSQLは、`ANALYZE`コマンドによってサンプリングされた統計情報を使い、確率論的に「今のデータはどうなっていそうか」を推論するのです。
ANALYZEが「嘘」をつく瞬間
現場でよくあるトラブルシューティングのひとつに、「データ分布の偏り」があります。
デフォルトの設定では、`ANALYZE`は特定のカラムに対してヒストグラムを作成します。しかし、頻繁に更新されるカラムや、特定のキーに値が集中する(データスキューがある)場合、デフォルトのサンプリング率では精度が追いつきません。
例えば、`status = ‘COMPLETED’`のようなフラグ列。最初は少なかった完了データが、運用が進むにつれて9割を占めるようになったとします。このとき、古い統計情報のままプランナが「完了データは全体の1%」だと誤解していたらどうなるか。
プランナは「インデックスを使って数件だけ拾ったほうが速い」と判断し、本来ならシーケンシャルスキャンで一気に読んだほうが効率的なクエリに対して、無駄なランダムアクセス(インデックススキャン)を連発する……これが「遅いクエリ」の完成です。
チューニングの現場で私がやっていること
トラブルシューティングの際、私はまず`EXPLAIN ANALYZE`の出力を見て、「実際の行数(actual rows)」と「見積もりの行数(rows)」を比較します。この乖離が数桁あるなら、間違いなく統計情報の問題です。
解決策はシンプルですが、奥が深いです。
- 統計情報の粒度を上げる:
`ALTER TABLE … SET STATISTICS`で、特定のカラムの統計情報取得量を増やします。デフォルトは100ですが、これを500や1000に引き上げるだけで、ヒストグラムの解像度が劇的に変わります。
- autovacuumのサンプリング率を疑う:
大規模テーブルで統計情報が荒れる場合、`autovacuum_analyze_scale_factor`の調整が必要です。デフォルトのままでは、巨大テーブルの更新を見逃すことがあります。
- 多変量統計(Extended Statistics)の活用:
もし「カラムAとカラムBの相関関係」によってクエリの挙動が変わるなら、PostgreSQL 10以降で導入された`CREATE STATISTICS`を使いましょう。単一カラムの統計では解決できない「複雑な結合条件」の精度が、劇的に改善されます。
最後に:完璧なプランナは存在しない
結局のところ、統計情報は「過去のデータに基づいた未来予測」です。どれだけ精緻にチューニングしても、データが生き物である以上、いつかはズレが生じます。
大切なのは、「統計情報は常に少しずつ嘘をついている」という前提でシステムを設計することです。クエリが遅いと叫ぶ前に、まずは`pg_stats`を覗いてみてください。そこに書かれている「見積もり」と「現実」のギャップを埋めることこそが、データベースエンジニアの腕の見せ所なのです。
チューニングとは、マシンをいじくり回すことではなく、データベースという名の「優秀だが少し思い込みの激しい助手」の思考を、現実世界と同期させる作業に近いのかもしれませんね。
それでは、また次回の深掘りでお会いしましょう。
コメント