PostgreSQL パーティショニングガイド
このドキュメントでは、PostgreSQLのテーブルパーティショニングの設計と運用を解説します。
目次
1. パーティショニングの概要
1.1 パーティショニングとは
┌──────────────────────────────────────────────────────────────┐
│ パーティショニングとは │
├──────────────────────────────────────────────────────────────┤
│ │
│ 大きなテーブルを複数の小さな部分(パーティション)に │
│ 分割する技術 │
│ │
│ 【非パーティションテーブル】 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ orders (10億行) │ │
│ │ 全データが1つのテーブルに格納 │ │
│ └─────────── ─────────────────────────────────────────────┘ │
│ ↓ │
│ 【パーティションテーブル】 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ orders (親テーブル) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ orders_2023 │ │ orders_2024 │ │ orders_2025 │ ... │
│ │ (3億行) │ │ (4億行) │ │ (3億行) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ アプリケーションからは1つのテーブルに見える │
│ 内部的には複数のパーティションに分散 │
│ │
└──────────────────────────────────────────────────────────────┘
1.2 パー ティショニングのメリット
┌──────────────────────────────────────────────────────────────┐
│ パーティショニングのメリット │
├──────────────────────────────────────────────────────────────┤
│ │
│ 【パフォーマンス向上】 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ パーティションプルーニング │ │
│ │ - クエリ条件に該当しないパーティションをスキップ │ │
│ │ - 例: WHERE created_at >= '2024-01-01' │ │
│ │ → 2023年以前のパーティションは読まない │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 【メンテナンス効率化】 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ - VACUUM/ANALYZEがパーティション単位で実行可能 │ │
│ │ - 古いデータのDROPが高速 (DELETE不要) │ │
│ │ - インデックス再構築がパーティション単位 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 【データ管理】 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ - 古いデータのアーカイブが容易 │ │
│ │ - パーティション単位でテーブルスペースを分離可能 │ │
│ │ - データ保持ポリシーの実装が容易 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
1.3 パーティショニングが有効なケース
┌──────────────────────────────────────────────────────────────┐
│ パーティショニングが有効なケース │
├────────────────────────────── ────────────────────────────────┤
│ │
│ 【推奨】 │
│ ✅ テーブルサイズが数十GB以上 │
│ ✅ 時系列データ (ログ、イベント、トランザクション) │
│ ✅ 定期的に古いデータを削除/アーカイブする │
│ ✅ 特定の条件でのクエリが大部分 │
│ ✅ テナント別のデータ分離 │
│ │
│ 【非推奨】 │
│ ❌ テーブルサイズが小さい (数GB以下) │
│ ❌ パーティションキーでのフィルタがほとんどない │
│ ❌ 全パーティションを常にスキャンするクエリが多い │
│ ❌ トランザクションが複数パーティションにまたがる │
│ │
│ 【目安】 │
│ - 数千万行以上で検討開始 │
│ - 1億行以上で強く推奨 │
│ │
└──────────────────────────────────────────────────────────────┘
2. パーティショニング方式
2.1 方式の比較
┌──────────────────────────────────────────────────────────────┐
│ パーティショニング方式 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 【RANGE パーティショニング】 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 値の範囲で分割 │ │
│ │ 用途: 日付、ID範囲など │ │
│ │ │ │
│ │ orders_2023: created_at >= '2023-01-01' AND < '2024-01-01'│
│ │ orders_2024: created_at >= '2024-01-01' AND < '2025-01-01'│
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 【LIST パーティショニング】 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 特定の値のリストで分割 │ │
│ │ 用途: カテゴリ、リージョン、テナントなど │ │
│ │ │ │
│ │ orders_jp: country = 'JP' │ │
│ │ orders_us: country = 'US' │ │
│ │ orders_eu: country IN ('DE', 'FR', 'GB') │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 【HASH パーティショニング】 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ハッシュ値で均等に分割 │ │
│ │ 用途: 均等分散が必要な場合 │ │
│ │ │ │
│ │ orders_0: hash(user_id) % 4 = 0 │ │
│ │ orders_1: hash(user_id) % 4 = 1 │ │
│ │ orders_2: hash(user_id) % 4 = 2 │ │
│ │ orders_3: hash(user_id) % 4 = 3 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
2.2 複合パーティショニング
┌──────────────────────────────────────────────────────────────┐
│ 複合パーティショニング │
├──────────────────────────────────────────────────────────────┤
│ │
│ 複数のキーで階層的にパーティショニング │
│ │
│ orders │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ orders_2024_q1 orders_2024_q2 orders_2024_q3 │
│ (RANGE by date) │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ orders_ orders_ │
│ 2024_q1_jp 2024_q1_us │
│ (LIST by country) │
│ │
└──────────────────────────────────────────────────────────────┘
3. 宣言的パーティショニング
3.1 RANGEパーティション作成
-- 親テーブルの作成
CREATE TABLE orders (
id BIGSERIAL,
user_id BIGINT NOT NULL,
order_date DATE NOT NULL,
total_amount NUMERIC(10, 2),
status VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) PARTITION BY RANGE (order_date);
-- 月次パーティションの作成
CREATE TABLE orders_2024_01 PARTITION OF orders
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE orders_2024_02 PARTITION OF orders
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
CREATE TABLE orders_2024_03 PARTITION OF orders
FOR VALUES FROM ('2024-03-01') TO ('2024-04-01');
-- デフォルトパーティション (範囲外のデータ用)
CREATE TABLE orders_default PARTITION OF orders DEFAULT;
-- インデックスの作成 (親テーブルに作成すると全パーティションに適用)
CREATE INDEX idx_orders_user_id ON orders (user_id);
CREATE INDEX idx_orders_created_at ON orders (created_at);
3.2 LISTパーティション作成
-- 親テーブルの作成
CREATE TABLE customers (
id BIGSERIAL,
name VARCHAR(100) NOT NULL,
region VARCHAR(20) NOT NULL,
email VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) PARTITION BY LIST (region);
-- リージョン別パーティション
CREATE TABLE customers_asia PARTITION OF customers
FOR VALUES IN ('JP', 'KR', 'CN', 'TW', 'SG');
CREATE TABLE customers_americas PARTITION OF customers
FOR VALUES IN ('US', 'CA', 'BR', 'MX');
CREATE TABLE customers_europe PARTITION OF customers
FOR VALUES IN ('GB', 'DE', 'FR', 'IT', 'ES');
CREATE TABLE customers_default PARTITION OF customers DEFAULT;
3.3 HASHパーティション作成
-- 親テーブルの作成
CREATE TABLE user_activities (
id BIGSERIAL,
user_id BIGINT NOT NULL,
activity_type VARCHAR(50),
activity_data JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) PARTITION BY HASH (user_id);
-- 4つのハッシュパーティション
CREATE TABLE user_activities_0 PARTITION OF user_activities
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE user_activities_1 PARTITION OF user_activities
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE user_activities_2 PARTITION OF user_activities
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE user_activities_3 PARTITION OF user_activities
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
3.4 複合パーティション作成
-- 親テーブル (年月でRANGE)
CREATE TABLE events (
id BIGSERIAL,
event_type VARCHAR(50) NOT NULL,
event_date DATE NOT NULL,
data JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) PARTITION BY RANGE (event_date);
-- 月次パーティション (さらにイベントタイプでLIST)
CREATE TABLE events_2024_01 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01')
PARTITION BY LIST (event_type);
-- サブパーティション
CREATE TABLE events_2024_01_click PARTITION OF events_2024_01
FOR VALUES IN ('click', 'impression');
CREATE TABLE events_2024_01_conversion PARTITION OF events_2024_01
FOR VALUES IN ('purchase', 'signup');
CREATE TABLE events_2024_01_default PARTITION OF events_2024_01 DEFAULT;
3.5 パーティションキーの制約
┌──────────────────────────────────────────────────────────────┐
│ パーティションキーの制約 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 【主キー・ユニーク制約】 │
│ パーティションキーを含む必要がある │
│ │
│ ❌ 不可 │
│ CREATE TABLE orders (...) PARTITION BY RANGE (order_date); │
│ ALTER TABLE orders ADD PRIMARY KEY (id); │
│ │
│ ✅ 可能 │
│ ALTER TABLE orders ADD PRIMARY KEY (id, order_date); │
│ │
│ 【外部キー】 │
│ - 親テーブルへの外部キーは可能 │
│ - パーティションテーブルを参照する外部キーは不可 │
│ (PostgreSQL 11以降で制限緩和) │
│ │
└──────────────────────────────────────────────────────────────┘