【実務・中級編】 継承ベースのパーティショニング – PostgreSQL

「パーティショニング、どうやってる?」

現場で若手と話をすると、たまにこんな話題になります。PostgreSQLのパーティショニングといえば、今はもう「宣言的パーティショニング(Declarative Partitioning)」一択だよね、というのが現代の常識です。

でもね、たまに古いシステムを保守していると、「テーブル継承(Table Inheritance)」を使った、いわば”職人芸”のようなパーティショニングに出くわすことがあります。

今日は、この「旧来のパーティショニング」がなぜ生まれ、そしてなぜ今、私たちは新しい手法を選ぶべきなのか。その中身を少し深掘りしてみましょう。

—

継承ベースのパーティショニング、懐かしの裏側

PostgreSQLの「継承」機能は、元々オブジェクト指向的なテーブル設計のためにあったものですが、これを応用して巨大なテーブルを分割するのが、かつての定石でした。

仕組みはシンプルです。
1. 親テーブルを作る(これはデータを持たない「入れ物」)。
2. 子テーブルを親から継承して作る(ここが実際のデータの置き場所)。
3. 子テーブルに`CHECK制約`を付け、どの範囲のデータが入るかを明示する。
4. 親テーブルにトリガーを仕込み、INSERTされたデータを適切な子テーブルへ「ルーティング」する。

実装のイメージはこんな感じ

例えば、月次でデータを保存するログテーブルを作るなら、こんな風に書いていました。

— 親テーブル
CREATE TABLE logs (
id serial,
created_at timestamp NOT NULL,
message text
);

— 子テーブル(2023年10月分)
CREATE TABLE logs_2023_10 (
CHECK (created_at >= ‘2023-10-01’ AND created_at < '2023-11-01') ) INHERITS (logs); -- ルーティング用のトリガー関数 CREATE OR REPLACE FUNCTION logs_insert_trigger() RETURNS TRIGGER AS $$ BEGIN IF (NEW.created_at >= ‘2023-10-01’ AND NEW.created_at < '2023-11-01') THEN INSERT INTO logs_2023_10 VALUES (NEW.); -- ここに延々と月ごとのIF文やCASE文を書いていく… ELSE RAISE EXCEPTION '日付が範囲外です'; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER insert_logs_trigger BEFORE INSERT ON logs FOR EACH ROW EXECUTE FUNCTION logs_insert_trigger(); ---

なぜこの手法は「卒業」されたのか?

コードを見て気づいた人もいるでしょう。……そう、運用がとにかく面倒なんです。

  • トリガーの保守地獄: 新しい月が来るたびに、トリガー関数を書き換えるか、あるいは動的に生成するスクリプトを回し続ける必要があります。このメンテを忘れると、データが親テーブルに溜まってパフォーマンスが急落します。
  • プランナの負荷: 親テーブルに対してクエリを投げると、PostgreSQLはすべての継承テーブルをチェックして、`CHECK制約`を元に「どのテーブルを見るべきか」を判断します。テーブル数が増えると、この「制約排除(Constraint Exclusion)」のコストがバカになりません。
  • DDLの複雑さ: インデックス一つ追加するにも、親に貼れば自動で子に継承されますが、排他的なロックが長時間かかったり、意図しない挙動に悩まされることが多々ありました。

—

宣言的パーティショニングという「正解」

PostgreSQL 10で登場した「宣言的パーティショニング」は、この苦労をすべて過去のものにしました。

`PARTITION BY RANGE` と書けば、あとはPostgreSQLが内部でよしなにやってくれます。トリガーなんて書く必要はありません。

CREATE TABLE logs (
id serial,
created_at timestamp NOT NULL,
message text
) PARTITION BY RANGE (created_at);

CREATE TABLE logs_2023_10 PARTITION OF logs
FOR VALUES FROM (‘2023-10-01’) TO (‘2023-11-01’);

これだけで、ルーティングはエンジンレベルで最適化されますし、パーティションの追加・切り離しも`DETACH PARTITION`一発で完了です。ロックの競合も最小限に抑えられます。

—

最後に:先輩からのアドバイス

もしあなたが今、新規で設計をしているなら、継承ベースのパーティショニングを使う理由は1ミリもありません。 「昔のコードに似ているから」という理由で継承を使い続けるのは、ただの技術的負債の先送りです。

一方で、もしレガシーなシステムを触っているなら、この「トリガーと継承」の仕組みを理解しておくことは、非常に価値があります。「なぜ昔はこうだったのか?」「今の仕組みは何を解決したのか?」を知っているエンジニアは、トラブルシューティングの際の引き出しが違います。

もし今の現場で継承ベースの手法に苦しんでいるなら、少しずつ「宣言的パーティショニング」への移行を検討してみてください。その工数は、将来の自分たちへの一番のプレゼントになりますから。

それでは、また現場でお会いしましょう!

コメント

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