postgreSQL 16 にて確認。
パーティションとは
1つのテーブルを論理的に複数テーブルに分割して管理する機能のこと。検索対象レコードを減らすことができるため、検索の高速化に有効。
PostgreSQL では継承機能をもとに実装されているため、パーティションテーブルには親子関係が成立して管理される。
-- パーティショニングの概念図
┌────────────────────────────┐
│ 👥 親テーブル (論理テーブル) │
│ │
│ ※実データは持たない「窓口」。 │
│ スキーマ定義やインデックス、IDENTITY列のルールを持つ │
└────────────────────────────┘
│
┌──────────────┐
│ PostgreSQLがハッシュ値を │
│ 計算して自動でルーティング│
▼ ▼
┌──────────────┐ ┌────────────┐
│ 📦 パーティション1 │ │ 📦 パーティション2 │
│ (物理的な実体テーブル) │ │ (物理的な実体テーブル) │
└──────────────┘ └────────────┘
┌──────────────┐ ┌────────────┐
│ 📦 パーティション3 │ │ 📦 パーティション4 │
│ (物理的な実体テーブル) │ │ (物理的な実体テーブル) │
└──────────────┘ └────────────┘
パーティションの概要
- 後から変更不可
- テーブル作成時にパーティションに分ける必要がある。(後から
ALTER TABLEなどで パーティション分割することはできない)
- テーブル作成時にパーティションに分ける必要がある。(後から
It is not possible to turn a regular table into a partitioned table or vice versa.
5.12. Table Partitioning5.12.Table Partitioning # 5.12.1. Overview 5.12.2. Declarative Partitioning 5.12.3. Partitioning Using Inheritance 5.12....
- 3つの分割方法
- 範囲パーティション
- 日付などの範囲によってパーティションテーブルを分ける
- リストパーティション
- 各パーティションに現れるキーの値を明示的に列挙することでテーブルをパーティションに分割
- ハッシュパーティション
- キーにハッシュ関数をかけて、指定した数(MODULUS)で割り算し、残りの値(REMAINDER)でパーティションを分ける
- 範囲パーティション
- 検索時のコードは共通
- テーブル検索のコマンドはパーティションあり/なしによらず変わらない。
-- テーブルがパーティションあってもなくても同じコマンドで検索可能
select * from テーブル
where ...パーティションのデモ、性能確認
テスト用のテーブルに1,000万レコードを投入し、検索時の性能を確認する。
- パーティションなしのテーブル
logs_no_hash - パーティションありのテーブル
logs_hashed- ハッシュパーティションを利用。4つのパーティションに分割する。 親テーブルの
user_idカラムの値に対し、4で割った余りを基準とする。(logs_hash_0~logs_hash_3)
- ハッシュパーティションを利用。4つのパーティションに分割する。 親テーブルの
-- 1. 比較用の「パーティションなし」テーブル(前回と同じ)
CREATE TABLE logs_no_hash (
id SERIAL,
log_date DATE NOT NULL,
user_id INT NOT NULL,
message TEXT
);
-- 2. 「Hashパーティションあり」の親テーブル(user_id でハッシュ分割)
CREATE TABLE logs_hashed (
id INT GENERATED ALWAYS AS IDENTITY,
log_date DATE NOT NULL,
user_id INT NOT NULL,
message TEXT
) PARTITION BY HASH (user_id);
-- 4つの子テーブル(パーティション)を作成
-- MODULUS(割る数)と REMAINDER(余り 0〜3)で割り振りを指定します
CREATE TABLE logs_hash_0 PARTITION OF logs_hashed FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE logs_hash_1 PARTITION OF logs_hashed FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE logs_hash_2 PARTITION OF logs_hashed FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE logs_hash_3 PARTITION OF logs_hashed FOR VALUES WITH (MODULUS 4, REMAINDER 3);
-- パーティションなしテーブルへ1000万件挿入
INSERT INTO logs_no_hash (log_date, user_id, message)
SELECT
'2026-01-01'::DATE + (random() * 90)::INT,
(random() * 100000)::INT + 1, -- user_id を 1〜100,000 で分散
md5(random()::TEXT)
FROM generate_series(1, 10000000);
-- Hashパーティションありテーブルへ1000万件挿入
INSERT INTO logs_hashed (log_date, user_id, message)
SELECT
'2026-01-01'::DATE + (random() * 90)::INT,
(random() * 100000)::INT + 1,
md5(random()::TEXT)
FROM generate_series(1, 10000000);結果確認
テーブルを4分割しており、検索対象も 1/4 になっているため、時間も 1/4 程度に短くなっていることを確認できた。
| テーブル構成 | 実行時間 (Execution Time) | パフォーマンス |
|---|---|---|
パーティションなしlogs_no_hash | 848.598 ms (約850ms) | 基準 |
パーティションあり (4分割)logs_hashed | 204.607 ms (約200ms) | 約4倍高速化 |
パーティションなしの場合: 実行時間は ~850ms
explain analyze select count(*) from logs_hashed where user_id=1000;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=146541.68..146541.69 rows=1 width=8) (actual time=831.226..844.273 rows=1 loops=1)
-> Gather (cost=146541.47..146541.68 rows=2 width=8) (actual time=831.050..844.258 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=145541.47..145541.48 rows=1 width=8) (actual time=798.103..798.104 rows=1 loops=3)
-> Parallel Seq Scan on logs_no_hash (cost=0.00..145541.36 rows=42 width=0) (actual time=51.954..797.948 rows=33 loops=3)
Filter: (user_id = 1000)
Rows Removed by Filter: 3333300
Planning Time: 0.447 ms
JIT:
Functions: 14
Options: Inlining false, Optimization false, Expressions true, Deforming true
Timing: Generation 6.551 ms, Inlining 0.000 ms, Optimization 4.771 ms, Emission 34.450 ms, Total 45.772 ms
Execution Time: 848.598 ms
パーティションありの場合: 実行時間は ~200ms
explain analyze select count(*) from logs_hashed where user_id=1000;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=37590.10..37590.11 rows=1 width=8) (actual time=190.976..204.210 rows=1 loops=1)
-> Gather (cost=37589.89..37590.10 rows=2 width=8) (actual time=190.865..204.201 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=36589.89..36589.90 rows=1 width=8) (actual time=76.024..76.025 rows=1 loops=3)
-> Parallel Seq Scan on logs_hash_0 logs_hashed (cost=0.00..36589.79 rows=41 width=0) (actual time=6.734..76.001 rows=33 loops=3)
Filter: (user_id = 1000)
Rows Removed by Filter: 837970
Planning Time: 2.183 ms
Execution Time: 204.607 ms
参考
公式ドキュメント

5.12. Table Partitioning
5.12.Table Partitioning # 5.12.1. Overview 5.12.2. Declarative Partitioning 5.12.3. Partitioning Using Inheritance 5.12....
