【テクニカル・上級編】 VACUUM FULLとREINDEX – PostgreSQL

肥大化したテーブルと「禁断の果実」:VACUUM FULLとREINDEXを正しく使いこなす

PostgreSQLを長く運用していると、必ず直面する壁がある。「なぜかストレージを食いつぶす」「インデックスが異常に肥大化し、スキャン速度が落ちた」という現象だ。

MVCC(多版同時実行制御)というPostgreSQLの設計思想上、更新の多いテーブルでデッドタプルが溜まるのは宿命だ。自動VACUUMが健気に働いていても、物理的な領域(ページ)が断片化し、インデックスがスカスカになることは避けられない。

今回は、そんな「溜まったツケ」を解消するための最終兵器、`VACUUM FULL`と`REINDEX`について、現場の視点から深く掘り下げていこう。

—

1. VACUUM FULLの正体と、その「重さ」

`VACUUM FULL`は、単なる掃除ではない。テーブルを物理的に新しい領域へコピーし、不要な領域を完全に切り捨てて再構成する「作り直し」だ。

なぜこれが危険なのか?

最大の理由は、排他的ロック(AccessExclusiveLock)にある。実行中は対象テーブルへの読み書きが完全に遮断される。本番環境の巨大なマスタテーブルでこれを叩こうものなら、即座にサービス停止だ。

もし、どうしても物理的に領域を詰めたいのであれば、現在は`pg_repack`という素晴らしいツールがある。これはトリガーと一時テーブルを使って、ロック時間を極限まで短縮しながらオンラインでテーブルを再構築する代物だ。私自身、大規模環境のメンテでは必ずこちらを第一選択にする。

—

2. インデックスの劣化とREINDEXの真実

インデックスは、更新されるたびに「インデックスページ」の断片化が起きる。特にUUIDのようなランダム性の高いキーを使っている場合、B-treeの構造はあっという間にスカスカになり、検索効率が劇的に悪化する。

REINDEX CONCURRENTLYという福音

かつて、インデックスの再構築はテーブル同様に重い処理だった。しかし、PostgreSQL 12で導入された `REINDEX CONCURRENTLY` は、まさにゲームチェンジャーだ。

  • 挙動の仕組み: まず新しいインデックスを作成し、その後、元のインデックスと切り替える。
  • メリット: インデックス構築中に読み書きをブロックしない。

ただし、注意点がある。`CONCURRENTLY`は「インデックスの構築」という重いCPU/I/O処理をバックグラウンドで行うため、システム全体の負荷は跳ね上がる。監視なしに安易に実行すると、DB全体のレスポンスが壊滅する可能性がある。実行時は、必ずシステムのピークタイムを避けるのが鉄則だ。

—

3. パフォーマンストラブルシューティングの勘所

もしDBが重いと感じたら、以下の手順で「インデックスが原因か」を切り分けてほしい。

1. `pg_stat_user_indexes`を確認する
`idx_scan`と`idx_tup_fetch`の比率を見れば、インデックスがどれだけ有効に使われているか、あるいはどれだけ肥大化して無駄なI/Oを生んでいるかが透けて見える。
2. `pgstattuple`拡張を使う
`pgstattuple`関数を使えば、テーブルやインデックスの「死んでいるタプル(Dead Tuple)」の割合や、インデックスの「断片化率(fillfactorの隙間)」が数値として可視化できる。直感ではなく、数値で「再構築が必要か」を判断するんだ。

—

最後に:メンテの極意

「とりあえずVACUUM FULL」は、外科手術で例えるなら「患部を切除するために全身麻酔をかける」ようなものだ。

  • 物理的な空き領域が必要なのか?
  • それともインデックスの深さが問題なのか?
  • そもそも、fillfactorを調整して、更新の頻度に合わせてページに余白を持たせることで再構築の頻度を下げられないか?

PostgreSQLは非常に奥が深い。闇雲にコマンドを打つのではなく、アーキテクチャを理解し、ボトルネックを正確に特定する。それができるエンジニアこそが、データベースの性能を極限まで引き出せるんだ。

さて、あなたのDBは今日も軽快に動いているだろうか?ログを眺めるその目が、今日は少しだけ鋭くなることを期待している。

コメント

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