インデックスの「老い」と向き合う:PostgreSQLにおけるREINDEXの深淵
データベースを長く運用していると、必ず直面する「得体の知れないパフォーマンス低下」という壁があります。クエリプランナーの挙動も悪くない、統計情報も最新。それなのに、なぜかインデックススキャンが遅い。
そんな時、我々エンジニアが最後に辿り着くのが「インデックスの肥大化(Bloat)」という問題です。今日は、PostgreSQLにおける`REINDEX`という、ある種「外科手術」のようなコマンドについて、少し深掘りしてみたいと思います。
なぜインデックスは「太る」のか
PostgreSQLのMVCC(多版同時実行制御)アーキテクチャにおいて、更新処理は「古い行の無効化」と「新しい行の挿入」の繰り返しです。この副作用として、B-treeインデックス内には「論理的に削除されているが、物理的には残っているエントリ」が溜まり続けます。
いわゆる「デッドタプル」がインデックスページを占拠し、本来ならもっとコンパクトに収まるはずのインデックスが、スカスカの構造になってしまう。これが肥大化の正体です。こうなると、本来1回のI/Oで済むはずのインデックス探索が、余計なページ読み込みを強いられ、キャッシュ効率も劇的に低下します。
REINDEX CONCURRENTLY という救世主
かつてのPostgreSQL運用で、`REINDEX`をためらわせる最大の理由は「排他ロック」でした。テーブル全体をロックしてインデックスを張り直すなんて、24時間365日稼働するシステムでは自殺行為に近い。
しかし、PostgreSQL 12で導入された `REINDEX CONCURRENTLY` は、まさにゲームチェンジャーでした。
- 何が起きているのか: 内部的には、新しいインデックスを並行して構築し、最後に古いインデックスと入れ替えるという手法をとります。
- 注意すべき点: 当然、構築中は書き込み負荷が増大し、一時的にディスク使用量も跳ね上がります。「負荷が低い時間帯に」というのは基本ですが、それ以上に「ディスク容量に余裕があるか」を事前に確認することが、熟練エンジニアの流儀です。
「いつ」実行すべきか:カンに頼らない判断基準
「なんとなく遅いからREINDEXしよう」という運用は、あまり賢いとは言えません。重要なのは定量的な観測です。私は普段、`pgstattuple` 拡張を使って、実際にどれくらい肥大化しているかを可視化しています。
SELECT FROM pgstatindex(‘index_name’);
ここで出力される `avg_leaf_density`(リーフページの平均密度)に注目してください。これが低すぎる――例えば60%を切るようなら、再構築によって劇的な改善が見込めるサインです。闇雲にコマンドを叩くのではなく、数値で必要性を証明する。これがトラブルシューティングにおけるプロの作法だと思っています。
最後に:銀の弾丸ではないことを忘れない
誤解してほしくないのは、`REINDEX`は万能ではないということです。
もしアプリケーションのクエリがそもそも非効率(インデックスを使わないフルスキャンが走っているなど)であれば、インデックスをどれだけ最適化しても焼け石に水です。インデックスの肥大化は「システムの老朽化」のようなもの。定期的なメンテナンスとしては優秀ですが、設計そのものの欠陥を隠蔽するものではありません。
運用中のデータベースという生き物と対話する中で、REINDEXという手段は、我々に「足元のパフォーマンスを再定義する機会」を与えてくれます。皆さんのシステムでも、一度、静かにインデックスの「密度」を確認してみてはいかがでしょうか。
—
追伸:REINDEX実行時は、バックグラウンドでのI/O競合に十分注意を払ってください。パフォーマンスを回復させるための作業が、逆にサービスダウンを招く――そんな皮肉な事態だけは避けたいものですからね。
コメント