「なぜかクエリが遅い」を撲滅する。PostgreSQLの統計情報と上手く付き合う技術
エンジニアの皆さん、お疲れ様です。
現場で「昨日まで爆速だったクエリが、今日になったらなぜか数秒かかるようになった」なんて経験、一度はありますよね。
特にPostgreSQLを触っていると避けて通れないのが「統計情報の陳腐化」という落とし穴です。PostgreSQLのオプティマイザは、テーブルが「今、どんな状態か」を統計情報から推測して実行計画を立てます。でも、その情報が古くなって現実と乖離した瞬間、悲劇は始まります。
今日は、そんな「悲劇」を未然に防ぐための、統計情報の管理術について話をしましょう。
—
なぜ「ANALYZE」は忘れ去られるのか
PostgreSQLには `autovacuum` という強力な味方がいます。こいつが裏で `ANALYZE` を走らせてくれるので、基本的には放置でいい……というのが教科書的な教えです。
ですが、現場で数十GB、時にはTB級の巨大なテーブルを扱っていると、`autovacuum` の閾値設定だけでは追いつかないケースが多々あります。特に、バッチ処理で一気に数百万行をガツンと更新した直後、統計情報が追いつかずにオプティマイザが迷子になる現象は「あるある」ですよね。
統計情報の「鮮度」を可視化する
まず、今のテーブルがどれくらい危険な状態かを知る必要があります。
実務で一番手っ取り早いのは、`pg_stat_user_tables` を覗くことです。ここに統計情報のヒントが詰まっています。
SELECT
relname AS table_name,
n_tup_ins, — INSERTされた数
n_tup_upd, — UPDATEされた数
n_tup_del, — DELETEされた数
n_tup_hot_upd, — HOT更新された数
last_analyze, — 最後にANALYZEした日時
last_autoanalyze — 最後に自動ANALYZEした日時
FROM pg_stat_user_tables
WHERE relname = ‘target_table_name’;
ここを見る際、僕が注目するのは 「どれだけ更新されたか(n_tup_ins + n_tup_upd + n_tup_del)」 です。
もしこの合計値が、テーブルの全行数に対して一定割合(例えば10%以上)を超えているのに、`last_analyze` が更新されていないなら……それはもう、クエリプランが劣化しているサインです。
—
「そろそろANALYZEが必要だよ」と検知する仕組み
実務では、このチェックを毎回手動でやるのは現実的じゃないですよね。
僕はよく、以下のようなSQLを監視ツールやスクリプトに組み込んで、閾値を超えたら通知を飛ばすようにしています。
SELECT
relname,
(n_tup_ins + n_tup_upd + n_tup_del) AS total_change_count,
n_live_tup AS current_live_rows,
— 更新比率が10%を超えたら「警告」
((n_tup_ins + n_tup_upd + n_tup_del)::float / NULLIF(n_live_tup, 0)) AS change_ratio
FROM pg_stat_user_tables
WHERE (n_tup_ins + n_tup_upd + n_tup_del) > 100000 — 最低10万行の変更はあったか
AND ((n_tup_ins + n_tup_upd + n_tup_del)::float / NULLIF(n_live_tup, 0)) > 0.1;
これ、単純ですがめちゃくちゃ効きます。特に「更新は激しいけど、autovacuumの走るタイミングが絶妙にズレている」ような、いわゆる「中だるみ」しているテーブルを炙り出すのに最適です。
—
先輩からのアドバイス:ただANALYZEすればいいわけじゃない
一つだけ注意点。
「じゃあ、このクエリに引っかかったテーブルを全部手動で `ANALYZE` しまくればいいんだね!」と思うかもしれませんが、それはNOです。
`ANALYZE` もリソースを食います。特に巨大テーブルで `ANALYZE` を実行すると、CPU負荷が跳ね上がって本番のパフォーマンスに悪影響を及ぼすことがあります。
- まずは設定を見直す: テーブル単位で `autovacuum_analyze_scale_factor` を小さくして、自動検知の感度を上げる。
- オフピークを狙う: どうしても手動でやるなら、負荷の低い時間帯にスクリプトを回す。
- 統計情報の質を考える: `ALTER TABLE … SET STATISTICS` を使って、特定のカラムだけヒストグラムの精度を上げるなど、「賢いチューニング」を優先する。
—
最後に
データベースのパフォーマンスチューニングは、いわば「健康診断」です。
何かが壊れてから直すのは「治療」であって、それはエンジニアとして一番コストが高い。今回紹介したような統計情報のチェックを日々のルーチンに取り入れるだけで、深夜の障害対応コールから解放される確率はグッと上がります。
「最近、なんとなくクエリが遅い気がする……」
その勘は、たいてい当たっています。まずは `pg_stat_user_tables` を見て、自分のテーブルと対話することから始めてみてください。
さて、今日はここまで。また現場でお会いしましょう!
コメント