巨大なテーブルの悩みと自動化の必要性
毎日数百万行ずつ増加するデータベースの管理は、最終的にパフォーマンスの壁に突き当たります。かつて、単一の「logs」テーブルが500GBに達したプロジェクトがありました。単純なSELECTクエリに数分かかるようになり、VACUUMのようなメンテナンス作業は実質的に終わらなくなりました。これまで様々なプロジェクトでMySQL、PostgreSQL、MongoDBを扱ってきましたが、それぞれに強みがあるものの、大規模な構造化データに関してはPostgreSQLのネイティブパーティショニングが真価を発揮します。
PostgreSQLはバージョン10でネイティブな宣言的パーティショニングを導入しました。これは大きな飛躍でしたが、一つ落とし穴があります。それは、パーティション自体を管理してくれないことです。翌月分のテーブルを手動で作成し、制約を管理し、古いデータを自分で削除しなければなりません。
もし次のパーティションを作成し忘れると、データの格納先がないためアプリケーションがエラーを吐き始めます。ここで救世主となるのがpg_partmanです。これはパーティションのライフサイクルを処理する自動化エンジンとして機能し、運用担当者が夜に安心して眠れるようにしてくれます。
環境へのpg_partmanの導入
自動化を始める前に、サーバーに拡張機能をインストールする必要があります。AWS RDSやAzure Database for PostgreSQLのようなマネージドサービスを使用している場合、pg_partmanはすでに利用可能である可能性が高く、有効化するだけで済みます。独自のLinuxサーバーを管理している場合は、Postgresのバージョンに合ったパッケージをインストールする必要があります。
PostgreSQL 16を使用しているDebian/Ubuntuシステムでの手順は以下の通りです。
sudo apt-get update
sudo apt-get install postgresql-16-partman
パッケージがインストールされたら、PostgreSQLにそれをロードするように設定する必要があります。これにはpostgresql.confファイルの変更が必要です。shared_preload_libraries設定を探し、pg_partman_bgw(バックグラウンドワーカー)を追加します。これにより、拡張機能がバックグラウンドで自動的にメンテナンス・タスクを実行できるようになります。
# postgresql.confを編集
shared_preload_libraries = 'pg_partman_bgw'
変更を適用するためにPostgreSQLサービスを再起動します。再起動後、データベースにログインし、拡張機能とそれ専用のスキーマを作成します。publicスキーマを汚さないために、pg_partmanは独自のスキーマに保持することを強くお勧めします。
CREATE SCHEMA partman;
CREATE EXTENSION pg_partman SCHEMA partman;
最初の自動パーティションの設定
実践的な例を見てみましょう。毎秒数千件のエントリを受信するiot_sensor_dataテーブルがあるとします。このテーブルをcreated_atタイムスタンプでパーティショニングし、毎日新しいパーティションを作成するようにします。
まず、「テンプレート」となる親テーブルを作成します。宣言的パーティショニングでは、親テーブルが構造とパーティションキーを定義することに注意してください。
CREATE TABLE public.iot_sensor_data (
id bigint NOT NULL,
sensor_id int NOT NULL,
data_value numeric,
created_at timestamptz NOT NULL DEFAULT now()
) PARTITION BY RANGE (created_at);
ここで、手動でCREATE TABLE ... PARTITION OFを実行する代わりに、pg_partmanに任せます。create_parent関数を呼び出します。これにより、拡張機能に対してテーブルの管理を開始するよう指示し、「daily(日次)」の間隔を使用し、スペースが不足しないようにあらかじめ4つのパーティションを作成(pre-create)させます。
SELECT partman.create_parent(
p_parent_table := 'public.iot_sensor_data',
p_control := 'created_at',
p_type := 'native',
p_interval := 'daily',
p_premake := 4
);
p_premakeパラメータは非常に重要です。これにより、今日が月曜日であれば、火曜日、水曜日、木曜日、金曜日のパーティションがすでに存在することが保証されます。このバッファは、メンテナンスジョブの一時的な失敗からシステムを保護します。
メンテナンス・タスクの自動化
pg_partmanにパーティションの設定を伝えても、時間の経過とともに新しいパーティションを実際に作成するためのトリガーが必要です。これを行うには主に2つの方法があります。バックグラウンドワーカー(BGW)を使用する方法と、cronジョブを使用する方法です。
方法1:バックグラウンドワーカー(推奨)
先ほどshared_preload_librariesにpg_partman_bgwを追加したので、あとはpostgresql.confで設定するだけです。これは外部ツールに依存しないため、最も堅牢な方法です。
# postgresql.confに以下を追加
pg_partman_bgw.interval = 3600 # 1時間ごとに実行
pg_partman_bgw.role = 'postgres'
pg_partman_bgw.dbname = 'データベース名'
方法2:pg_cronの使用
より細かな制御を好む場合や、他のタスクですでにpg_cronを使用している場合は、メンテナンス関数を手動でスケジュールできます。
SELECT cron.schedule('0 * * * *', $$SELECT partman.run_maintenance()$$);
run_maintenance()が実行されるたびに、pg_partmanによって管理されているすべてのテーブルがチェックされ、p_premakeの値に基づいて必要に応じて新しいパーティションが作成され、データ保持が処理されます。
データの保持とクリーンアップ
パーティショニングの最大の利点の一つは、古いデータを瞬時に削除できることです。DELETEコマンドで1億行を削除すると、膨大な量のWALログが生成され、テーブルの肥大化(bloat)を引き起こします。パーティショニングを使用すれば、テーブル全体(パーティション)をドロップするだけで済み、これはO(1)の操作です。
これを自動化するには、特定の親テーブルのpart_configテーブルを更新します。例えば、センサーデータを30日間だけ保持したいとします。
UPDATE partman.part_config
SET retention = '30 days',
retention_keep_table = false
WHERE parent_table = 'public.iot_sensor_data';
retention_keep_table = falseに設定すると、pg_partmanは古いパーティションテーブルを物理的に削除します。これをtrueに設定すると、親テーブルからは切り離されますが、データはディスク上に残ります。これは、削除する前にS3のようなコールドストレージにアーカイブしたい場合に便利です。
検証とヘルスチェック
すべてが稼働し始めたら、単に動いていると思い込むべきではありません。私は常にpart_configと実際のテーブルリストを確認して、自動化が健全であることを確認しています。
現在拡張機能によって管理されているすべてのテーブルを表示するには:
SELECT parent_table, partition_type, partition_interval, retention
FROM partman.part_config;
パーティションが実際にファイルシステム/データベース内に作成されているか確認するには、psqlで親テーブルに対して\d+コマンドを使用します。特定の時間範囲にマッピングされた子パーティションのリストが表示されるはずです。
メンテナンスが実行されず、パーティションが不足している状況に陥った場合は、特定のテーブルに対して手動実行を強制して追いつくことができます:
SELECT partman.run_maintenance('public.iot_sensor_data');
最後に
単一のモノリシックなテーブルから自動化されたパーティション設定に移行することは、急成長するデータベースに対して行える最も効果的な変更の一つです。これにより、パフォーマンスの低下問題が解決され、重いDELETE操作のオーバーヘッドなしにデータ保持を管理するクリーンな方法が提供されます。PostgreSQLのネイティブパーティショニングとpg_partmanを組み合わせることで、初期設定さえ済ませれば、手動の介入をほとんど必要とせずに数十億行規模までスケールするシステムを構築できます。

