「なぜかクエリが遅い」を撲滅する。PostgreSQLの統計情報とANALYZEの付き合い方
現場で長くPostgreSQLを触っていると、必ずと言っていいほど「昨日まで爆速だったクエリが、今日急にゴミみたいな実行計画を吐き出し始めた」という怪奇現象に遭遇します。
開発環境ではサクサク動いていたのに、本番環境でデータ量が増えた瞬間に死ぬクエリ。その原因の8割以上は、PostgreSQLの「プランナ(実行計画作成者)」が現実と乖離した世界を見ていることにあります。
今日は、その「現実」をプランナに正しく教えるための道具、統計情報と`ANALYZE`について、現場の知恵を交えて話そうと思います。
—
プランナは「勘」で動いているわけじゃない
PostgreSQLのプランナは、クエリが来ると「どのテーブルを先にスキャンし、どの結合アルゴリズムを使うのが一番速いか」を計算します。でも、プランナは全レコードをイチイチ数えていたらクエリが一生終わらないので、「統計情報」という名の要約ノートを見てコストを計算しています。
このノートには、以下のような情報が記録されています。
- テーブルの総行数(`reltuples`)
- 特定の列に含まれるユニークな値の数(`n_distinct`)
- データの分布(ヒストグラム)
もし、このノートが古かったら?プランナは「100万行あるテーブル」を「100行しかない」と勘違いして、無謀なフルスキャンを選択したり、間違った結合順序を選んだりします。これが「遅延」の正体です。
統計情報の更新役:ANALYZE
この「要約ノート」を最新に書き換えるのが`ANALYZE`コマンドです。
通常、PostgreSQLには`autovacuum`という優秀な掃除屋がいて、データの変更率に応じて自動的に`ANALYZE`を走らせてくれます。しかし、現場では「自動更新を待っていられない」ケースや、「自動更新の閾値には達していないが、統計情報が不正確でプランが狂う」というケースが往々にしてあります。
具体的な使用例
例えば、バッチ処理で数百万行を一気に書き込んだ直後のクエリが遅いなら、その直後に手動で叩くのが鉄則です。
— 大量データ投入後に、プランナの目を覚まさせる
ANALYZE VERBOSE public.users;
`VERBOSE`をつけると、どのくらいの行数をスキャンして統計を更新したかが見えるので、デバッグ時には重宝します。
「統計情報が古くなる」典型的なパターン
僕の経験上、以下のケースでは自動更新が追いつかないことが多いです。
1. バッチ処理による大量更新:
トランザクション内でテーブルの大部分が書き換わると、`autovacuum`が起動する前にクエリが走り、古い統計情報を見てしまいます。
2. 偏ったデータ分布:
`WHERE status = ‘FAILED’` のような特定のステータスが異常に多い、あるいは少ない場合。デフォルトの統計情報の解像度(`default_statistics_target`)では、データの「偏り」をうまく拾えないことがあります。
そんなときは、特定の列だけ解像度を上げてやります。
— 特定の列のヒストグラムを細かく取る(デフォルトは100)
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
— その後、改めてANALYZE
ANALYZE orders;
これだけで、特定の条件での行数見積もりが劇的に改善し、実行計画が「ハッシュ結合」から「インデックススキャン」に華麗に変身することは珍しくありません。
現場からのアドバイス:魔法の杖ではない
最後に一つだけ。`ANALYZE`は便利ですが、「とりあえず`ANALYZE`すれば直る」という安易な考え方は危険です。
統計情報を更新しても遅い場合は、インデックスの設計や、クエリ自体の書き方(例えば、関数で囲んでインデックスを使えなくしていないか等)に問題があることがほとんどです。
1. `EXPLAIN (ANALYZE, BUFFERS)` で実行計画を見る。
2. 「見積もり行数(rows)」と「実際の行数(actual rows)」に巨大な乖離がないか確認する。
3. 乖離があれば`ANALYZE`を検討し、それでもダメならインデックスやクエリを見直す。
この手順を体に染み込ませてください。
データベースは嘘をつきません。プランナが見ている「世界」と「現実」を同期させること。それが、SQLチューニングの第一歩であり、奥義でもあります。
皆さんのクエリが、明日から少しでも速くなることを祈っています。また何か詰まったら相談してくださいね。
コメント