【テクニカル・上級編】 インデックスのメンテナンスと肥大化 – PostgreSQL

インデックスの「見えない老化」と戦う:PostgreSQLの肥大化(Bloat)との付き合い方

PostgreSQLを長年運用していると、必ずと言っていいほど直面する問題があります。「なぜかクエリが遅い」「ディスク容量が肥大化しているのに、データ量はそれほど増えていない」。

その犯人の多くは、インデックスの肥大化(Bloat)です。

長年PostgreSQLの深淵を覗いてきた身として、今日はインデックスのメンテナンスという、地味ながらもシステムの寿命を左右するトピックについて、少し掘り下げてお話ししたいと思います。

—

なぜインデックスは「太る」のか

PostgreSQLのアーキテクチャにおいて、インデックス(特に標準のB-tree)は、データが更新・削除されるたびに、古いエントリをマークし、新しいエントリを追加するというプロセスを繰り返します。

MVCC(多版同時実行制御)の仕組み上、更新によって生じた「不要なタプル」は、すぐに物理削除されるわけではありません。インデックス側でも同様に、論理的には削除されていても、物理的なページ内には「死んだエントリ」が残り続けます。

これを放置すると何が起きるか。
1. スキャン効率の低下: インデックスの深さ(Tree Depth)が増し、本来不要なページ読み込みが発生する。
2. キャッシュ効率の悪化: 本当に必要なエントリがメモリに乗らなくなり、I/O負荷が跳ね上がる。
3. ストレージの浪費: 実際には使われていないスカスカのページが、貴重なNVMeの領域を食いつぶす。

—

まずは「敵」を可視化せざるを得ない

「なんとなく遅い気がする」で済ませてはいけません。まずは `pgstattuple` 拡張を使って、インデックスの健全性を数値化しましょう。

CREATE EXTENSION pgstattuple;

SELECT FROM pgstatindex(‘your_index_name’);

ここで注目すべきは `avg_leaf_density` です。これが低い(例えば50%を切るような)場合、そのインデックスは相当に肥大化しています。インデックスのページは通常、FILLFACTORによって余裕を持たせますが、それを考慮してもスカスカの状態であれば、メンテナンスの時期が来ている合図です。

—

VACUUMとREINDEXの使い分け

多くのエンジニアが混同しがちなのが、VACUUMとREINDEXの役割です。

1. VACUUM (Autovacuum) の限界

Autovacuumは、インデックス内の「デッドタプル」を掃除し、再利用可能な領域としてマークしてくれます。しかし、インデックスの構造そのものを再構築するわけではないため、ページ内の空間をOSに返還する(インデックスを縮小する)ことはできません。

2. REINDEXの「真の力」

インデックスを物理的に再構築し、完全にクリーンな状態に作り直すのが `REINDEX` です。
ここで重要なのは、`REINDEX CONCURRENTLY` を使うという選択です。

REINDEX INDEX CONCURRENTLY your_index_name;

従来の `REINDEX` はテーブルをロックしてしまいますが、`CONCURRENTLY` を使えば、読み書きをブロックせずに裏側で新しいインデックスを構築し、最後に切り替えてくれます。本番環境での運用において、これを選択肢に入れない手はありません。

—

実践的な「メンテナンス戦略」

私が現場でよく採用するアプローチは以下の通りです。

  • FILLFACTORの最適化:

更新頻度が高いテーブルであれば、デフォルトの100ではなく、90や80に下げて運用します。これにより、インデックスのページ内に余裕を持たせ、更新時のページ分割を抑制します。「更新はあるが追記がメイン」というケースでは、この微調整だけでBloatの進行速度が劇的に変わります。

  • 「定期的なREINDEX」を恐れない:

「最近のPostgreSQLは優秀だからREINDEXは不要」という意見も聞きますが、それは半分正解で半分間違いです。長期間運用されたテーブルでは、どうしても断片化は避けられません。メトリクス監視を行い、閾値を超えたら自動的に(あるいは定期的に)`REINDEX CONCURRENTLY` を走らせるジョブを組むのが、最も精神衛生上良い解決策です。

—

最後に:エンジニアとしての嗅覚

インデックスの肥大化は、システムの「成人病」のようなものです。急激なパフォーマンス低下を引き起こすというよりは、じわじわとリソースを蝕み、最も負荷が高い瞬間に牙を剥きます。

「クエリが遅い」と感じたら、まずは実行計画を見て `Index Scan` が何枚のページを読み込んでいるかを疑ってください。統計情報の裏側に隠れた物理層の真実を見抜くこと。それこそが、PostgreSQLを使いこなす熟練エンジニアの流儀ではないでしょうか。

皆さんのデータベースが、今日も健やかでありますように。

コメント

タイトルとURLをコピーしました