PostgreSQLの「宣言的パーティショニング」を使いこなして、巨大なテーブルと心中するのをやめよう
どうも、現場のデータベースエンジニアです。
今日はPostgreSQLの「宣言的パーティショニング」について話そうと思う。
DBを触り始めて数年経つと、必ずぶち当たる壁がある。「テーブルのデータが数億件を超えて、クエリが死ぬほど重くなった」というやつだ。インデックスをいくらチューニングしても限界がある、あの絶望的な瞬間だね。
そんなとき、昔なら「トリガーを使ってテーブルを自作して……」なんて泥臭いことをやっていたんだが、今のPostgreSQLは違う。`CREATE TABLE … PARTITION BY` を使うだけで、驚くほどスマートに解決できるんだ。
今日は、教科書的な説明はそこそこに、実務でどう使うのが一番「効く」のか、その勘所を共有したいと思う。
—
なぜ「パーティショニング」が必要なのか?
結論から言うと、「検索範囲を物理的に絞り込むため」だ。
例えば、ログデータのような時系列データを一つのテーブルにぶち込んでいると、PostgreSQLはインデックスがあっても全パーティションのインデックスを舐めようとする。でも、パーティショニングしてあれば、クエリが「先月分だけ見ればいい」と判断した瞬間、他の数年分のデータは物理的に無視(パーティションプルーニング)してくれる。
これだけで、I/Oの負荷が劇的に変わるんだ。
実践:3つの分割戦略
PostgreSQLでは主に3つの分割方法がある。どれを選ぶかで設計の良し悪しが決まるから、しっかり押さえておこう。
1. RANGE(範囲)パーティション:時系列データの王道
ログや決済データなど、時間軸でデータが増え続ける場合に最適だ。
CREATE TABLE orders (
order_id bigint NOT NULL,
order_date timestamp NOT NULL,
user_id int,
…
) PARTITION BY RANGE (order_date);
— 月ごとにパーティションを作る例
CREATE TABLE orders_2023_10 PARTITION OF orders
FOR VALUES FROM (‘2023-10-01’) TO (‘2023-11-01’);
- コツ: `pg_partman` という拡張機能を知っているか? 実務では手動でテーブルを作るのはやめたほうがいい。先々のパーティションを自動生成してくれるツールを使わないと、ある日突然データが入らなくなって夜中にアラートが鳴る羽目になるぞ。
2. LIST(リスト)パーティション:特定の属性で分ける
「地域別」や「ステータス別」など、特定の値でデータを分けるときに使う。
CREATE TABLE users (
user_id int,
region text
) PARTITION BY LIST (region);
CREATE TABLE users_tokyo PARTITION OF users FOR VALUES IN (‘Tokyo’);
CREATE TABLE users_osaka PARTITION OF users FOR VALUES IN (‘Osaka’);
- コツ: 分けすぎに注意だ。数千のパーティションを作ると、PostgreSQLのメタデータ管理が重くなって、かえってクエリが遅くなることもある。バランス感覚が大事だよ。
3. HASH(ハッシュ)パーティション:負荷分散の切り札
特定のキーに偏りがある場合や、単純に巨大なテーブルを「均等に」分割したいときに使う。
CREATE TABLE transactions (
transaction_id uuid,
amount numeric
) PARTITION BY HASH (transaction_id);
CREATE TABLE transactions_p1 PARTITION OF transactions
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
- コツ: 範囲指定ができないから、「特定の期間だけ消す」といった運用には向かない。あくまでデータの水平スケーリング用だと割り切ろう。
—
実務で必ず覚えておいてほしい「落とし穴」
僕が現場でよく見る「パーティショニングの失敗例」をいくつか挙げておく。
1. インデックスの設計をサボるな
パーティションキーをクエリの `WHERE` 句に含めないと、パーティションプルーニングが効かない。SQLを書く側も、パーティション設計を意識しないと意味がないんだ。
2. JOINの重さを甘く見るな
パーティション化されたテーブル同士をJOINするとき、キーが合致していないと全パーティションがスキャンされる「クロスジョイン」のような状況になりかねない。EXPLAIN ANALYZEで `Subplans Removed` が表示されているか、必ず確認する癖をつけよう。
3. 「デフォルトパーティション」を作っておく
想定外のデータが入った瞬間にエラーで止まるのが一番怖い。`DEFAULT` パーティションを作っておけば、あぶれたデータをそこに逃がせる。運用が安定するまでの保険だと思って必ず入れておこう。
—
まとめ:結局、どう向き合うべきか?
パーティショニングは「魔法の杖」じゃない。設計を間違えると、管理コストだけが増える「ただの面倒な巨大テーブル」になる。
まずは「データがどれくらいの速度で増えて、どの範囲でアクセスされることが多いか」を徹底的に分析してほしい。そして、運用を自動化する仕組み(pg_partman等)をセットで検討すること。これが、僕たちが「巨大なデータ」と平和に付き合っていくための唯一の道だ。
もし今、テーブルのパフォーマンスに頭を抱えているなら、まずは `PARTITION BY` から検討してみてくれ。世界が少しだけ明るく見えるはずだよ。
何か具体的な実装で詰まったら、また聞きに来てくれ。一緒にコードを叩こう。
コメント