「なぜ、あのクエリは突然遅くなったのか?」― PostgreSQLの統計情報と戦うための処方箋
PostgreSQLのチューニングにおいて、最も裏切りを感じる瞬間はいつだろうか。先週まで数ミリ秒で返っていたクエリが、ある日突然、数秒かかるようになる。「インデックスはあるし、実行計画も正しいはずなのに……」と頭を抱え、`EXPLAIN (ANALYZE, BUFFERS)`を叩いた瞬間に冷や汗を流す。
その原因の9割は、統計情報の陳腐化だ。
PostgreSQLのオプティマイザは、統計情報という「地図」を頼りに旅をする。しかし、その地図が最新の現実を反映していなければ、最速のルートを選べるはずがない。今回は、なぜ統計情報が嘘をつくのか、そして我々エンジニアがどうやってその「嘘」を検知すべきかについて、少し深い話をしよう。
—
1. 「autovacuumがやってくれる」という甘い幻想
多くのエンジニアが「autovacuumがあるから大丈夫」と高を括っている。だが、それは半分正解で、半分は危険な誤解だ。
autovacuumは、`autovacuum_vacuum_scale_factor`と`autovacuum_analyze_scale_factor`という閾値に基づいて動く。デフォルトではテーブルの20%が変更されたら……という設定だが、数千万行のテーブルで「20%」を待っていたら、統計情報は既に数日分、あるいは数週間分も過去のものになっている。
その間に、データ分布の偏り(ヒストグラムの乖離)が進行し、オプティマイザは「この値はごく稀だ」と判断してNested Loopを選択するものの、実際には数万件ヒットしてHash Joinにすべきだった……という悲劇が繰り返される。
2. 統計情報の陳腐化をどう検知するか
PostgreSQLには`pg_stat_user_tables`という優秀なビューがある。ここに格納されている`n_live_tup`(生存タプル数)と`n_mod_since_analyze`(最後のANALYZE以降の更新数)を監視するのが、検知の第一歩だ。
ただ、単純に「更新数 > 閾値」でアラートを出すだけでは甘い。私が現場で推奨しているのは、「更新比率」を監視することだ。
SELECT
relname,
n_live_tup,
n_mod_since_analyze,
(n_mod_since_analyze::float / NULLIF(n_live_tup, 0)) 100 AS mod_ratio
FROM
pg_stat_user_tables
WHERE
n_live_tup > 100000 — 小さなテーブルは無視
AND (n_mod_since_analyze::float / NULLIF(n_live_tup, 0)) > 0.05; — 5%更新されたら警告
このクエリを監視エージェントに組み込み、閾値を超えたら「ANALYZEが必要な状態」として検知する。これだけで、クエリの急激な性能劣化の多くは未然に防げるはずだ。
3. 「ANALYZE」という名の劇薬と付き合うために
検知して終わりではない。次の問題は「いつANALYZEを走らせるか」だ。
ANALYZEは共有ロックこそ取らないものの、統計情報の更新中はそのテーブルのカタログ情報にアクセスするため、高負荷時にはそれなりのインパクトがある。特に大規模なテーブルで`ANALYZE`を投げるのは、勇気が必要だ。
私が現場でよく使うテクニックはこれだ。
- 特定のカラムだけをターゲットにする:
`ANALYZE VERBOSE table_name(column_name);`
もし特定のカラムの統計情報だけが死んでいるなら、テーブル全体を再スキャンする必要はない。
- サンプリングレートを調整する:
`ALTER TABLE … SET STATISTICS`を駆使し、分布が激しいカラムには高い精度を、そうでないものには低い精度を割り当てる。これは「カタログの最適化」と呼ぶべき作業だ。
最後に:エンジニアの直感を信じるな、数値を信じろ
PostgreSQLは非常に洗練されたデータベースエンジンだが、それでも「神」ではない。オプティマイザはあくまで統計情報という「過去の記録」に基づいて未来を予測するアルゴリズムに過ぎない。
データが激しく更新されるシステムを運用しているなら、統計情報を「自動更新されるもの」ではなく、「メンテナンスが必要なインフラ」と捉え直すべきだ。
今日、あなたのDBで`n_mod_since_analyze`が暴走しているテーブルはないだろうか? ぜひ一度、`pg_stat_user_tables`を覗いてみてほしい。そこには、次のトラブルの種が静かに眠っているはずだ。
データベースをチューニングするとは、結局のところ、エンジンとの対話なのだから。
コメント