pg_repackガイド:ダウンタイムなしでPostgreSQLのBloat(肥大化)を効率的に解消する方法

Database tutorial - IT technology blog
Database tutorial - IT technology blog

数百万件のレコードを削除してもディスク容量が減らない場合

このようなシナリオを想像してみてください。データベースの空き容量が不足しているという警告が出ました。あなたはすぐに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つのステップ:

  1. 元のテーブルの全データを含む一時テーブル(シャドウテーブル)を作成します。
  2. 元のテーブルにトリガーを設定し、発生したすべての変更をログテーブルに記録します。
  3. 新しい一時テーブルにインデックスを再構築します。
  4. ログテーブルにあるすべての変更を一時テーブルに適用し、データを同期させます。
  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日稼働させ続けることができます。リソースのリスクを最小限に抑えるため、本番環境に適用する前に、必ずステージング環境でテストを行ってください。

Share: