数百万件のレコードを削除してもディスク容量が減らない場合
このようなシナリオを想像してみてください。データベースの空き容量が不足しているという警告が出ました。あなたはすぐにDELETEコマンドを実行し、200GBの古いログデータを削除しました。しかし、ディスク使用量は500GBのままで、全く減っていません。私は大規模なトランザクションシステムを管理していた際に、このような状況に遭遇したことがあります。実際のデータは300GBしかないのに、OS上では500GB占有されていると表示されるのです。これがPostgreSQLにおける典型的なBloat(データの肥大化)現象です。
多くの人が最初に思いつくのはVACUUM FULLの実行でしょう。しかし、本番環境(Production)でこれを行ってはいけません!このコマンドはテーブルを完全にロック(Access Exclusive Lock)します。その間、アプリケーション全体が停止し、すべての読み取り/書き込みクエリが待機状態になります。巨大なテーブルの場合、このプロセスには数時間かかることもあります。
サービスを中断することなくこの問題を根本的に解決するために、pg_repackはすべてのデータベースエンジニアが習得すべき救世主的なツールです。
なぜPostgreSQLは肥大化(Bloat)するのか?
問題の根本はMVCC(多版型同時実行制御)という仕組みにあります。UPDATEを実行した際、Postgresは古い行を上書きしません。古い行を「期限切れ」(dead tuple)としてマークし、新しい行を挿入します。DELETEコマンドも同様で、削除済みとしてマークするだけで、即座にディスク空間を回収することはありません。
Autovacuumプロセスがこれらのデッドタプルをクリーンアップしますが、それはPostgresがそのスペースを新しいデータで再利用できるようにするだけです。OSに容量を返却するわけではありません。大量のデータを急激に削除したり、更新頻度が極めて高いテーブルの場合、データファイルは制御不能なほど肥大化してしまいます。
従来の解決策との比較
pg_repackを使用する前に、従来のメソッドの制限を振り返ってみましょう。
- VACUUM: テーブル内部のクリーンアップのみを行います。ディスク容量は減りません。
- VACUUM FULL: ディスク容量を効果的に回収しますが、テーブルをロックします(ダウンタイム発生)。
- CLUSTER: インデックスに従ってデータを並べ替えますが、
VACUUM FULLと同様にテーブルをロックします。
pg_repackの仕組み
pg_repackは、テーブルを長時間ロックすることなく、テーブルとインデックスを再構築できます。実行中も通常通りSELECT、INSERT, UPDATE, DELETEが可能です。
処理プロセスの5つのステップ:
- 元のテーブルの全データを含む一時テーブル(シャドウテーブル)を作成します。
- 元のテーブルにトリガーを設定し、発生したすべての変更をログテーブルに記録します。
- 新しい一時テーブルにインデックスを再構築します。
- ログテーブルにあるすべての変更を一時テーブルに適用し、データを同期させます。
- システムカタログ内のテーブル名を入れ替え(スワップ)し、古いテーブルを削除します。このステップはわずか数ミリ秒で完了します。
インストールと実際の使用方法
pg_repackはOSレベルでのインストールと、データベース内でのエクステンションの有効化の両方が必要です。
1. ツールのインストール
Ubuntu上のPostgreSQL 15の場合は、以下のコマンドを使用します:
sudo apt-get update
sudo apt-get install postgresql-15-repack
次に、データベースにログインしてエクステンションを有効化します:
CREATE EXTENSION pg_repack;
2. 肥大化(Bloat)レベルの確認
すべてを闇雲にrepackしないでください。肥大化率が20%を超えているテーブルを優先しましょう。pg_bloat_checkスクリプトを使用してシステム全体をスキャンできます。例えば、100GBのテーブルのうち40GBが肥大化によるものであれば、対策を講じるべきタイミングです。
3. ターミナルからのコマンド実行
注意:pg_repackはpsql内ではなく、コマンドラインから実行します。
特定のテーブルをRepackする:
pg_repack -h localhost -U postgres -d shop_db -t orders
インデックスのみを最適化する(時間を短縮できます):
pg_repack -h localhost -U postgres -d shop_db -t orders --only-indexes
テスト実行(Dry run)で確認する:
pg_repack -h localhost -U postgres -d shop_db -t orders --dry-run
実運用における重要な注意点
数テラバイト規模のテーブルを何度も最適化してきた経験から、4つの重要な注意点を挙げます:
- 空きディスク容量: 元のテーブルサイズ + インデックスサイズと同等の空き容量が必要です。テーブルが300GBで空き容量が350GB未満の場合、ディスクフルを引き起こす可能性があるため、repackを実行しないでください。
- I/Oリソース: データのコピープロセスは大量のI/Oを消費します。ユーザー体験への影響を避けるため、オフピーク時(例:深夜2時など)に実行してください。
- プライマリキーの要件:
pg_repackを実行するには、テーブルにプライマリキーまたはUniqueインデックス(Not Null)が必要です。これらがない場合、ツールは動作しません。 - 長時間トランザクション: 対象テーブルに対して長時間実行されているクエリがある場合、
pg_repackは最終ステップのスワップ処理を完了できません。
まとめ
大規模なPostgreSQLを運用する上で、肥大化(Bloat)の管理は避けて通れないタスクです。pg_repackを使用することで、ディスク容量を回収し、クエリを高速化し、システムを24時間365日稼働させ続けることができます。リソースのリスクを最小限に抑えるため、本番環境に適用する前に、必ずステージング環境でテストを行ってください。
