PostgreSQLのパーティショニングを設定する方法|RANGE・LIST・HASHパーティション作成と性能改善の手順

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Linuxtips > データーベース管理 > PostgreSQLのパーティショニングを設定する方法|RANGE・LIST・HASHパーティション作成と性能改善の手順
「データが増えてテーブルスキャンが遅くなってきた」「古いデータをまとめて削除しようとしたら時間がかかりすぎる」
Linuxサーバー上でPostgreSQLを運用していると、レコードが数千万行を超えたあたりからこうした問題に直面することがあります。そのときの解決策の一つが、PostgreSQL 10以降で正式実装された宣言的パーティショニングです。

この記事では、RHEL 9系/Rocky Linux 9を想定環境として、PostgreSQLのRANGE・LIST・HASHの3方式でパーティションを設定する実践的な手順を解説します。パーティション作成から主キー・インデックスの設定、古いパーティションのDETACHアーカイブ、よくあるトラブルの対処まで順を追って説明します。

この記事のポイント

・RANGEは日付ログ、LISTはカテゴリ分類、HASHは均等負荷分散と使い分ける
・親テーブルをPARTITION BYで作成し、子テーブルをPARTITION OFで追加する
・PostgreSQL 11以降は親テーブルへのINDEX作成で全パーティションへ自動継承される
・DETACH PARTITIONで古いパーティションをクエリ対象から切り離してアーカイブできる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

PostgreSQLの宣言的パーティショニングとは

PostgreSQLのパーティショニングとは、論理的に1つのテーブルを複数の物理テーブル(パーティション)に分割する機能です。PostgreSQL 10でネイティブの宣言的パーティショニングが実装され、従来のトリガー・ルール方式よりはるかに簡潔に設定できるようになりました。

パーティショニングを使う主な目的は3つです。
・クエリの高速化: WHERE句でパーティションキーを指定すると、対象パーティションのみスキャンされる(パーティションプルーニング)
・バルク削除の高速化: 古い月次パーティションをDROP TABLEするだけで大量行を即座に削除できる
・ディスク管理の分散: パーティションごとにテーブルスペースを指定し、異なるディスクに配置できる

宣言的パーティショニングが使えるのはPostgreSQL 10以降です。バージョンが上がるにつれて機能が拡充されており、11でのインデックス自動継承、12でのパーティションプルーニング改善と段階的に強化されています。

パーティション方式の選び方

PostgreSQLのパーティション方式は3種類あります。

方式 分割基準 典型的なユースケース
RANGE 連続する値の範囲 日付・タイムスタンプ(月別・年別ログ)
LIST 値のリスト(列挙) 地域コード・ステータス・カテゴリ
HASH ハッシュ値による均等分割 均等負荷分散・ランダムIDの分散格納

RANGEパーティションを設定する手順

RANGEパーティションは、日付ログやトランザクションデータなど「時系列で増えるデータ」に最も広く使われます。ここでは注文ログテーブルを月別に分割する例を示します。

1. パーティション親テーブルを作成する

# 宣言的パーティショニング(RANGE)の親テーブル CREATE TABLE orders ( id BIGSERIAL, order_date DATE NOT NULL, customer_id INTEGER NOT NULL, amount NUMERIC(12,2) ) PARTITION BY RANGE (order_date);

親テーブルには実データが入りません。INSERTされたデータは、値に対応するパーティションへ自動的に振り分けられます。

2. 月別パーティションを追加する

# 2026年1月分のパーティション CREATE TABLE orders_2026_01 PARTITION OF orders FOR VALUES FROM ('2026-01-01') TO ('2026-02-01'); # 2026年2月分のパーティション CREATE TABLE orders_2026_02 PARTITION OF orders FOR VALUES FROM ('2026-02-01') TO ('2026-03-01'); # 2026年3月分のパーティション CREATE TABLE orders_2026_03 PARTITION OF orders FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');

範囲指定はFROM以上・TO未満です。TO値は含まないため、隙間が生じないよう連続して設定してください。

3. データを投入して確認する

# データの投入(親テーブルに挿入すると自動的に振り分けられる) INSERT INTO orders (order_date, customer_id, amount) VALUES ('2026-01-15', 100, 5000.00); # どのパーティションに入ったか確認 SELECT tableoid::regclass AS partition_name, * FROM orders WHERE order_date = '2026-01-15';

出力例(環境により異なります)

partition_name | id | order_date | customer_id | amount -----------------+----+------------+-------------+--------- orders_2026_01 | 1 | 2026-01-15 | 100 | 5000.00

4. デフォルトパーティションを設定する(推奨)

定義済みパーティションの範囲外のデータが挿入されるとエラーになります。デフォルトパーティションを設けておくと、想定外の値を受け取ってもエラーを防げます。

# デフォルトパーティション(範囲外データの受け皿) CREATE TABLE orders_default PARTITION OF orders DEFAULT;

LISTパーティションを設定する手順

LISTパーティションは、地域コードやステータス値など「取りうる値が決まっている」カラムに向いています。

1. 親テーブルとLISTパーティションを作成する

# 地域別の顧客テーブル(LIST分割) CREATE TABLE customers ( id BIGSERIAL, region VARCHAR(20) NOT NULL, name VARCHAR(100) ) PARTITION BY LIST (region); # 東日本パーティション CREATE TABLE customers_east PARTITION OF customers FOR VALUES IN ('tokyo', 'kanagawa', 'chiba', 'saitama'); # 西日本パーティション CREATE TABLE customers_west PARTITION OF customers FOR VALUES IN ('osaka', 'kyoto', 'hyogo', 'nara'); # その他のデフォルトパーティション CREATE TABLE customers_other PARTITION OF customers DEFAULT;

LISTパーティションでは、同じ値を複数のパーティションに重複して登録することはできません。値のリストは排他的に設定します。

HASHパーティションを設定する手順

HASHパーティションは、特定の値の偏りがないランダムなIDやUUIDで均等にデータを分散させたい場合に適しています。大量INSERTの負荷を複数テーブルに分散する用途でも利用されます。

1. HASHパーティションの作成

# ユーザーイベントログ(HASH分割・4分割) CREATE TABLE user_events ( id BIGSERIAL, user_id BIGINT NOT NULL, event_time TIMESTAMPTZ DEFAULT NOW(), event_type VARCHAR(50) ) PARTITION BY HASH (user_id); # MODULUS(総分割数)と REMAINDER(余り)で各パーティションの担当範囲を指定 CREATE TABLE user_events_0 PARTITION OF user_events FOR VALUES WITH (MODULUS 4, REMAINDER 0); CREATE TABLE user_events_1 PARTITION OF user_events FOR VALUES WITH (MODULUS 4, REMAINDER 1); CREATE TABLE user_events_2 PARTITION OF user_events FOR VALUES WITH (MODULUS 4, REMAINDER 2); CREATE TABLE user_events_3 PARTITION OF user_events FOR VALUES WITH (MODULUS 4, REMAINDER 3);

HASHパーティションのMODULUSは後から変更できません。分割数は最初にしっかり設計してから決めてください。

パーティションにインデックスを設定する

パーティションテーブルのインデックス設計には2つのアプローチがあります。

1. 親テーブルへのインデックス作成(PostgreSQL 11以降推奨)

PostgreSQL 11以降では、親テーブルにインデックスを作成するとすべての既存パーティション・新規追加パーティションへ自動的に継承されます。

# 親テーブルへのインデックス作成 → 全パーティションへ自動継承 CREATE INDEX idx_orders_customer_id ON orders (customer_id); CREATE INDEX idx_orders_order_date ON orders (order_date);

2. 特定パーティションのみにインデックスを追加する場合

# 1月パーティションのみに追加インデックスを作成する場合 CREATE INDEX idx_orders_2026_01_amount ON orders_2026_01 (amount);

パーティションテーブルに主キー制約を設定する場合は、パーティションキーを主キーに含める必要がある点に注意してください。

# NG: パーティションキー(order_date)が主キーに含まれていない ALTER TABLE orders ADD PRIMARY KEY (id); # OK: パーティションキーを主キーに含める ALTER TABLE orders ADD PRIMARY KEY (id, order_date);

パーティションの確認・追加・削除

1. パーティション一覧の確認

# pg_inherits でパーティション一覧を確認する SELECT parent.relname AS parent_table, child.relname AS partition_name FROM pg_inherits JOIN pg_class parent ON pg_inherits.inhparent = parent.oid JOIN pg_class child ON pg_inherits.inhrelid = child.oid WHERE parent.relname = 'orders';

出力例(環境により異なります)

parent_table | partition_name --------------+------------------ orders | orders_2026_01 orders | orders_2026_02 orders | orders_2026_03 orders | orders_default

2. 翌月のパーティションを事前追加する

RANGEパーティションでは、対応するパーティションが存在しない状態でINSERTするとエラーになります。cron等で毎月末に翌月分を追加する運用が推奨されます。

# 4月分のパーティションを事前に追加する例 CREATE TABLE orders_2026_04 PARTITION OF orders FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');

3. 古いパーティションをDETACHしてアーカイブする

DETACHすると、そのパーティションが親テーブルから切り離されて通常の独立テーブルになります。データを保持したままクエリ対象から外せるため、アーカイブ用途に最適です。

# 2026年1月パーティションを親テーブルから切り離す ALTER TABLE orders DETACH PARTITION orders_2026_01; # 切り離し後はpg_dumpで単独バックアップ可能 # 完全に削除する場合は通常のDROPを使う DROP TABLE orders_2026_01;

PostgreSQL 14以降では `DETACH PARTITION ... CONCURRENTLY` が使えるため、テーブルロックなしで切り離しが行えます。長時間書き込みが発生する本番環境では積極的に使ってください。

よくあるトラブルと対処法

「no partition of relation found for row」が出てINSERTが失敗する

対応するパーティションが定義されていない値を挿入しようとしたときに発生します。デフォルトパーティションを作成するか、エラーが発生した値の範囲に合うパーティションを追加してください。

# デフォルトパーティションを後から追加する場合 CREATE TABLE orders_default PARTITION OF orders DEFAULT;

EXPLAINでパーティションプルーニングが効いていない

全パーティションがスキャン対象になっている場合、WHERE句のカラム型とパーティションキーの型が一致しているか確認します。また `enable_partition_pruning` が off になっていないかも確認してください。

# パーティションプルーニングが効いているか確認 EXPLAIN SELECT * FROM orders WHERE order_date = '2026-01-15';

出力例(環境により異なります)

QUERY PLAN ----------------------------------------------------- Append (cost=0.00..X.XX rows=X width=XX) -> Seq Scan on orders_2026_01 (cost=...) Filter: (order_date = '2026-01-15'::date)

`orders_2026_01` のみがスキャン対象になっていれば正常です。全パーティションが列挙されている場合は型の不一致が疑われます。

「ERROR: insufficient columns in PRIMARY KEY constraint」が出る

パーティション化テーブルの主キーには、パーティションキーを必ず含める必要があります。

# NG: パーティションキーが主キーに含まれていない ALTER TABLE orders ADD PRIMARY KEY (id); # OK: パーティションキー(order_date)を主キーに含める ALTER TABLE orders ADD PRIMARY KEY (id, order_date);

PostgreSQLのDB管理スキルを体系的に習得したい方は、Linuxサーバー実務を網羅したLinuxセミナーも参考になります。

本記事のまとめ

PostgreSQLの宣言的パーティショニングをRANGE・LIST・HASHの3方式で解説しました。主なポイントをまとめます。

操作 SQLの書き方 ポイント
RANGE親テーブル作成 CREATE TABLE t (...) PARTITION BY RANGE (列) 親テーブルにはデータが入らない
パーティション追加 CREATE TABLE t_p PARTITION OF t FOR VALUES FROM (...) TO (...) TO値は含まない(未満)
デフォルトパーティション CREATE TABLE t_def PARTITION OF t DEFAULT 範囲外INSERTのエラー防止に必須
インデックス自動継承 CREATE INDEX ON 親テーブル (列) PostgreSQL 11以降で全パーティションへ自動継承
パーティション確認 SELECT ... FROM pg_inherits ... pg_classと結合して一覧取得
アーカイブ切り離し ALTER TABLE t DETACH PARTITION t_p PG14以降はCONCURRENTLYでロック回避
RANGEパーティションは毎月末に翌月分を事前作成するか、pg_cron等で自動化すると管理コストを抑えられます。デフォルトパーティションも必ず設けて、想定外のINSERTエラーを防いでください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、データベース運用を含むLinuxサーバー管理のノウハウを2日間のハンズオンで習得できるセミナーをご用意しています。
>> Linux Master Pro Seminarの詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。