墓標なきインデックスを切り捨てろ:`pg_stat_user_indexes` との静かな対話
データベースのパフォーマンスチューニングにおいて、「インデックスを足す」という行為は、往々にして魔法のように思われがちだ。しかし、熟練のエンジニアなら誰しも知っているはずだ。インデックスは「タダ」ではない。書き込みのたびにB-treeのページ分割(Page Split)というコストを払い、WAL(Write Ahead Log)を膨らませ、ストレージのキャッシュ効率を間接的に蝕む。
PostgreSQLを運用していて、「このインデックス、本当に必要か?」と自問自答した夜は一度や二度ではないだろう。そんなとき、我々が頼るべき羅針盤が `pg_stat_user_indexes` だ。
今日は、この統計情報を単なる「使用頻度チェックリスト」ではなく、データベースの健康を保つための診断ツールとしてどう使いこなすか、少し深い話をしよう。
—
`idx_scan` が「0」であることの重み
`pg_stat_user_indexes` のビューを覗くと、`idx_scan` というカラムがある。これは、そのインデックスがクエリプランナによって「実際にスキャンされた回数」だ。
もし、この値が極端に低い、あるいはゼロであるなら、それはそのインデックスが運用開始以来、一度もクエリの最適化に寄与していないことを意味する。だが、ここで早まってはいけない。我々エンジニアが陥りやすい罠がある。
- バッチ処理の死角: 日次や週次のバッチでしか動かないクエリが、そのインデックスを頼りにしている可能性はないか?
- 統計情報の更新ラグ: `ANALYZE` が走る前の、奇妙なプランナの気まぐれを記録していないか?
インデックスを削除する前に、必ず `pg_stat_last_scan` などの情報も併せて確認し、少なくとも数週間、あるいはビジネスサイクルを一周する程度のスパンで観測してほしい。
インデックスの「質」を問う:ScanとTuplesの関係
`idx_scan` だけを見て安心していては、まだ甘い。真に恐ろしいのは、「インデックスは使われているのに、全く効果が出ていない」というケースだ。
ここで注目すべきは `idx_tup_read` と `idx_tup_fetch` の比率だ。
- `idx_tup_read`: インデックスを通って読み出されたエントリ数。
- `idx_tup_fetch`: インデックスを使用して、実際にヒープ(テーブル本体)まで読みに行った行数。
この二つの差が激しい場合、そのインデックスは「インデックスとしての仕事を果たせていない」可能性が高い。例えば、カーディナリティ(値の重複度)が極端に低いカラムにインデックスを貼ると、オプティマイザはインデックスをスキャンするものの、結局ヒープを大量に読みに行くことになる。これは「インデックスの無駄遣い」そのものだ。
実践的な「お掃除」戦略
僕が大規模なデータベースをリファクタリングする際、以下のステップをルーチンにしている。
1. 長期モニタリング: 運用環境の統計情報はリセットされることがある(サーバーの再起動や手動リセット)。最低でも1ヶ月間は統計を保持する仕組みを作っておくこと。
2. `pg_stat_user_indexes` のダンプ: 定期的にスナップショットを取り、インデックスの「生存率」を可視化する。
3. 未使用インデックスの「隠蔽」: 本番環境でいきなり `DROP INDEX` を打つのは博打だ。まずは `ALTER INDEX … SET (visible = false)` (PostgreSQL 15以降なら有効な戦略だ)を検討するか、あるいはインデックスを `INVALID` 状態に近づけることで、プランナから除外して挙動を観察する。
4. 検証: インデックスを外した後の `pg_stat_statements` を確認し、特定のクエリの実行計画が劇的に悪化していないかを検証する。
最後に:メンテナンスは「整理整頓」である
DBのパフォーマンスが悪化したとき、多くの人は「新しいインデックス」を足したがる。それはまるで、部屋が散らかったときに新しい収納ケースを買うようなものだ。
本当のプロフェッショナルは、不要なものを捨てることから始める。
`pg_stat_user_indexes` は、君のデータベースが抱える「過去の遺物」を教えてくれる優しい鏡だ。ぜひ、明日の朝一番にでも、君のDBの鏡を覗いてみてほしい。そこには、まだ削除できるインデックスが静かに眠っているはずだ。
データベースをスリムに保つこと。それは、コードをクリーンに保つのと同じくらい、エンジニアとしての誇りであるべきだと僕は思う。
コメント