pg_partmanで大規模なデータテーブルを5分で処理する
データテーブルが500GBや数十億レコードに達すると、単純なSELECT文でさえ徐々に重くなってきます。また、DELETEによる古いデータの削除は、テーブルロックの発生やログファイル(WAL)の肥大化を招き、運用上の大きな負担となります。pg_partmanは、これらの巨大なテーブルを時間やIDに基づいて、より小さなパーティション(partitions)に完全自動で分割してくれるツールです。
Ubuntuにエクステンションがインストールされていると仮定して、以下の手順で素早く展開してみましょう。
-- 管理を容易にするため、partmanを専用のスキーマにまとめます
CREATE SCHEMA partman;
CREATE EXTENSION pg_partman SCHEMA partman;
-- 時間ベースでパーティショニングする親テーブル(parent table)を定義します
CREATE TABLE public.user_logs (
id bigserial,
log_date timestamptz not null,
data text,
PRIMARY KEY (id, log_date)
) PARTITION BY RANGE (log_date);
-- 日次でパーティションを自動生成するようにpartmanを設定します
SELECT partman.create_parent(
p_parent_table := 'public.user_logs',
p_control := 'log_date',
p_type := 'native',
p_interval := 'daily',
p_premake := 4
);
実行すると即座に、翌4日分の子テーブルが4つ作成されます。これで、日付が変わる時に子テーブルの作成を忘れてアプリケーションがクラッシュする、といった心配をする必要はもうありません。
なぜ手動での管理をやめるべきなのか?
以前は、MySQLなどでALTER TABLEを使って新しいパーティションを作成するために、PythonスクリプトやCronジョブを自作することがよくありました。PostgreSQLはバージョン10から強力なネイティブパーティショニング(Native Partitioning)を導入しましたが、それはあくまで「枠組み」を提供するに留まっています。子テーブルを作成するタイミングの計算、標準的な命名、古いデータのクリーンアップ(Retention/保持期間管理)などは、依然として手動で処理する必要があります。
私が管理していたシステムでも、ある月の1日午前0時ちょうどにシステムが停止したことがありました。原因はパーティション作成スクリプトのロジックミスで、新しいデータの保存先がなかったためです。pg_partmanは、インテリジェントな自動化メカニズムによって、このような初歩的なリスクを排除してくれます。
最も実用的な機能:
- パーティションの事前準備:
p_premakeパラメータにより、実際のデータが投入される前に子テーブルが常に準備されている状態を保証します。 - 自動クリーンアップ: 設定一行で、3ヶ月以上前の古いログテーブルを自動的に削除(drop)するように設定できます。
- バックグラウンドでの動作: Background Workerモードにより、外部ツールに依存せずPostgresのコア内で直接実行されます。
実践的なインストール手順
1. リポジトリからのインストール
Ubuntu環境のPostgreSQL 15でのインストールコマンドは以下の通りです:
sudo apt-get update
sudo apt-get install postgresql-15-partman
2. Background Workerの有効化
手動操作なしでpg_partmanを自動実行させるには、postgresql.confファイルを修正する必要があります。以下の行を探して追加してください:
shared_preload_libraries = 'pg_partman_bgw'
pg_partman_bgw.interval = 3600 -- 1時間ごとにチェック
pg_partman_bgw.role = 'postgres'
pg_partman_bgw.dbname = 'あなたのデータベース名'
保存後、PostgreSQLサービスを再起動して、この自動化エンジンを有効にします。
データライフサイクル管理(Data Retention)
DELETEを使って1,000万行を削除すると、数分かかりDBのボトルネックになることがありますが、1,000万行を含むパーティションをDROPするのは1秒もかかりません。これこそがpg_partmanの最大のメリットです。
UPDATE partman.part_config
SET retention = '3 months',
retention_keep_table = false
WHERE parent_table = 'public.user_logs';
上記のコマンドにより、1時間ごとにシステムが子テーブルをスキャンします。90日より古いデータを含むテーブルは即座に削除され、システムに負荷をかけることなくディスク容量を解放します。
通常のテーブルをパーティションテーブルに変換する方法
多くの場合、パーティショニングを検討するのは、現在のテーブルが5,000万〜1億レコードといった巨大なサイズになってからでしょう。焦る必要はありません。pg_partmanには安全な移行(migration)プロセスが用意されています。
- 元のテーブルと全く同じ構造の
PARTITION BYを持つ新しいテーブルを作成します。 partition_data_proc関数を使用して、データをバッチ(batch)ごとに移行します。
-- DBのハングアップを防ぐため、10,000行ずつのバッチでデータを移動します
CALL partman.partition_data_proc('public.user_logs', p_batch := 10000);
導入における「痛い目を見ないための」教訓
長年、金融システムや集中ログ管理システムを運用してきた中で得られた、いくつかの重要な注意点を挙げます:
パーティションを細かく分けすぎない
詳細に管理しようとして、1時間単位(hourly)で分けたくなるかもしれません。しかし、データが1日あたり数十億行に達しないのであれば、1日単位(daily)で分けるのが最適です。パーティションが多すぎると、PostgresのQuery Plannerが計算に余計な時間を費やすようになり、集計クエリなどの速度が低下します。
パーティション列へのインデックス作成は必須
PostgreSQLには、必要な子テーブルのみをスキャンする「Partition Pruning(パーティションの絞り込み)」機能があります。しかし、log_date列にインデックスを貼り忘れると、結局すべての子テーブルに対してフルスキャン(Full Scan)が発生してしまいます。この場合、クエリ速度が50msから5sに跳ね上がることも珍しくありません。
Unique制約に関する注意点
これはパーティショニングの弱点です。ユニークインデックスには必ずパーティション列を含める必要があります。id列をパーティション全体でユニークにしたい場合は、UNIQUE(id, log_date)として定義しなければなりません。
複雑なNoSQLソリューションに移行する代わりに、PostgreSQLとpg_partmanを組み合わせることは、非常にコストパフォーマンスの高い選択肢です。データの整合性(ACID)を保証しつつ、システムをプロフェッショナルな形でスケールアップさせることができます。

