OVERVIEW
試験概要

catch-img

第2回 宣言的パーティション-1

1. パーティショニングとは

大規模データを扱う場合、データ増加に伴う処理時間の長時間化という課題に直面することが予想されます。そんなときは、パーティショニングを利用して大きなテーブルを複数の小さなテーブルに分割させて処理を実行するのがおすすめです。

パーティショニングとは物理的にデータベースのテーブルを分割することです。パーティショニングされたテーブルは論理的には一つのパーティションテーブル(親テーブル)として扱うことができます。そのため、アプリケーションはパーティションテーブルのみを処理対象にでき、分割されたパーティション(子テーブル)を意識する必要がありません。


パーティショニングの利点

▪︎検索範囲の絞込み
検索範囲が狭まるため性能が向上します。例えば、図1のように月単位でデータを分割している場合、特定月(12月)の集計処理をする場合に有効です。
このように、問い合わせや更新が一つの特定のパーティションに絞られる場合は、アクセス対象が絞り込めるためパフォーマンスを向上させることができます。

▪︎一括削除の効率化
パーティション単位でデータを一括削除することができます。DELETEを使用した場合と比較して、高速なデータ削除が可能になります。加えて、不要領域の発生とVACUUMによる回収を回避できます。


図1 パーティショニングの例


2. 宣言的パーティショニングとは

PostgreSQLはバージョン9.6までパーティショニングの専門機能はなく、トリガ関数などを使用し実現していました。そのため、パーティショニングを利用したことがある方々には難解・面倒という印象があったかと思います。

バージョン10からは、「宣言的パーティショニング」という組み込みの機能でパーティショニングが構築できるようになり、非常に使いやすくなりました。

9.6までのパーティショニングと比較した宣言的パーティショニングの運用・性能面での向上について説明します。

▪︎運用面での向上
バージョン9.6以前のパーティショニングと、宣言的パーティショニングの設定を比較してみましょう。

レンジ(範囲)パーティショニングを作成する例を示します。
設定例は、下記のように利用者履歴テーブルを登録日時に従ってパーティショニングを設定します。設定のイメージを図2に示しました。

図2 レンジ(範囲)パーティショニングの設定例


バージョン9.6以前のパーティショニング

▪︎パーティションの設定

-- 親テーブル作成
CREATE TABLE rireki (
date date,
name text
);

-- 子テーブル定義
CREATE TABLE rireki_01 (
CHECK (date>= DATE '2018-01-01' AND date < DATE '2019-02-01')
) INHERITS (rireki);

CREATE TABLE rireki_02 (
CHECK (date>= DATE '2019-02-01' AND date < DATE '2019-03-01')
) INHERITS (rireki);

CREATE TABLE rireki_03 (
CHECK (date>= DATE '2019-03-01' AND date < DATE '2019-04-01')
) INHERITS (rireki);

CREATE TABLE rireki_04 (
CHECK (date>= DATE '2019-04-01' AND date < DATE '2019-05-01')
) INHERITS (rireki);

-- トリガ関数定義
CREATE OR REPLACE FUNCTION insert_trigger_func()
RETURNS TRIGGER AS $$
BEGIN
IF ( NEW.date>= DATE '2019-01-01' AND NEW.date < DATE '2019-02-01') THEN
INSERT INTO rireki_01 VALUES (NEW.*);
ELSIF ( NEW.date>= DATE '2019-02-01' AND NEW.date < DATE '2019-03-01') THEN
INSERT INTO rireki_02 VALUES (NEW.*);
ELSIF ( NEW.date>= DATE '2019-03-01' AND NEW.date < DATE '2019-04-01') THEN
INSERT INTO rireki_03 VALUES (NEW.*);
ELSIF ( NEW.date>= DATE '2019-04-01' AND NEW.date < DATE '2019-05-01') THEN
INSERT INTO rireki_04 VALUES (NEW.*);
ELSE
RAISE EXCEPTION 'Data out of range. Fix the insert_trigger()';
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;

-- 親テーブルに対するトリガ定義
CREATE TRIGGER insert_trigger
BEFORE INSERT ON rireki
FOR EACH ROW EXECUTE PROCEDURE insert_trigger_func();

パーティション定義や、トリガ関数定義の作成が非常に面倒という印象です。


宣言的パーティショニング(バージョン10~)

▪︎パーティションの設定

-- パーティションテーブルの作成
CREATE TABLE rireki (date date, name text) PARTITION BY RANGE (date);
-- パーティションの作成
CREATE TABLE rireki_01 PARTITION OF rireki FOR VALUES FROM ('2019-01-01') TO ('2019-02-01');
CREATE TABLE rireki_02 PARTITION OF rireki FOR VALUES FROM ('2019-02-01') TO ('2019-03-01');
CREATE TABLE rireki_03 PARTITION OF rireki FOR VALUES FROM ('2019-03-01') TO ('2019-04-01');
CREATE TABLE rireki_04 PARTITION OF rireki FOR VALUES FROM ('2019-04-01') TO ('2019-05-01');

以上で設定が完了します。非常にシンプルになりました。


▪︎インデックスの作成

バージョン11以降、パーティションテーブルにインデックスを作成すれば、各パーティションに1つずつインデックスが自動作成されるようになりました。

-- インデックス作成
CREATE INDEX ON rireki (date);

インデックスを確認すると、それぞれのパーティションに作成されていることが確認できます。パーティションテーブル(rireki)には作成されません。

-- インデックスの確認
SELECT tablename ,indexname FROM pg_indexes WHERE tablename LIKE ‘rireki%’;
tablename |     indexname      
-----------+--------------------
 rireki_01 | rireki_01_date_idx
 rireki_02 | rireki_02_date_idx
 rireki_03 | rireki_03_date_idx
 rireki_04 | rireki_04_date_idx
(4 rows)

(注意)バージョン10でパーティションテーブルにインデックスを作成すると、下記エラーが出力されます。そのため、全てのパーティションに対してそれぞれインデックスを作成する必要があります。

ERROR:  cannot create index on partitioned table "rireki"


▪︎パーティションの保守

パーティションの追加/削除は、ATTACH/DETACHコマンドで簡単に実行できます。DETACHを利用すれば、パーティションテーブルからパーティションを削除する一方で、パーティション自体はテーブルとしてアクセス可能なまま残すことができます。

--パーティションの追加
CREATE TABLE rireki_05 (date date, name text);
ALTER TABLE rireki ATTACH PARTITION rireki_05 FOR VALUES FROM ('2019-05-01') TO ('2019-06-01');
--パーティションの削除
ALTER TABLE rireki DETACH PARTITION rireki_05;▪︎性能面での向上


▪︎性能面での向上

上記で示したようにバージョン9.6以前では、トリガ関数定義を用いて振り分け先のテーブルを決定します。そのためオーバーヘッドが大きく、性能が落ちるという課題がありました。

一方、バージョン10以降の宣言的パーティショニングはPostgreSQL内部で振り分け先を決定するため、パーティションへの振り分けのためのオーバーヘッドが小さくなり、INSERT性能が向上しています。

実際に100万件のINSERTを実行して、実行計画を比較してみましょう。

バージョン9.6以前のパーティショニング

est_part=# EXPLAIN ANALYZE insert into rireki values('2019-01-01',generate_series(1,1000000));
                                                  QUERY PLAN
---------------------------------------------------------------------------------------------------------------
 Insert on rireki  (cost=0.00..22.52 rows=1000 width=36) (actual time=16186.284..16186.285 rows=0 loops=1)
   ->  Result  (cost=0.00..22.52 rows=1000 width=36) (actual time=0.010..2233.190 rows=1000000 loops=1)
         ->  ProjectSet  (cost=0.00..5.02 rows=1000 width=4) (actual time=0.004..754.434 rows=1000000 loops=1)
               ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.003 rows=1 loops=1)
 Planning Time: 0.057 ms
 Trigger insert_trigger: time=12326.308 calls=1000000
 Execution Time: 16186.338 ms
(7 rows)

宣言的パーティショニング(バージョン10~)

test_new_part=# EXPLAIN ANALYZE insert into rireki values('2019-01-01',generate_series(1,1000000));
                                                  QUERY PLAN
---------------------------------------------------------------------------------------------------------------
 Insert on rireki  (cost=0.00..22.52 rows=1000 width=36) (actual time=3899.295..3899.296 rows=0 loops=1)
   ->  Result  (cost=0.00..22.52 rows=1000 width=36) (actual time=0.009..2090.489 rows=1000000 loops=1)
         ->  ProjectSet  (cost=0.00..5.02 rows=1000 width=4) (actual time=0.005..692.020 rows=1000000 loops=1)
               ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.002..0.003 rows=1 loops=1)
 Planning Time: 0.029 ms
 Execution Time: 3899.371 ms
(6 rows)

バージョン9.6以前のパーティショニングはトリガ関数によるオーバーヘッドがあり、この影響によって実行時間(Execution Time)に大きな差が出ていることがわかります。


3. 宣言的パーティショニングの種類

宣言的パーティショニングは、リストパーティショニング、レンジ(範囲)パーティショニング、ハッシュパーティショニングをサポートしています。

(注意) バージョン10まではハッシュパーティショニングはサポートされていません。

▪︎レンジ(範囲)パーティショニング

レンジ(範囲)パーティショニングでは、範囲や期間に基づいて分割します。日付データによってパーティションが区切られる場合、利用履歴や売り上げデータなどを時系列にデータ蓄積することができます。例えば今月など、特定のパーティションにアクセスが集中される場合は非常に有効です。

図3-1 レンジ(範囲)パーティショニング


設定例

-- パーティションテーブル定義
CREATE TABLE rireki (date date, name text) PARTITION BY RANGE (date);

-- パーティション定義
CREATE TABLE rireki_01 PARTITION OF rireki FOR VALUES FROM ('2019-01-01') TO ('2019-02-01');
CREATE TABLE rireki_02 PARTITION OF rireki FOR VALUES FROM ('2019-02-01') TO ('2019-03-01');
CREATE TABLE rireki_03 PARTITION OF rireki FOR VALUES FROM ('2019-03-01') TO ('2019-04-01');


▪︎リストパーティショニング

リストパーティショニングは、パーティションへ分割するグループをリストで指定します。国、地域、都道府県など離散的な値を、任意のパーティションに格納する場合に有効です。例えば、利用履歴や売り上げデータを東北、関東、中部など地域単位で分類する場合に有効です。

図3-2 リストパーティショニング


設定例

-- パーティションテーブル定義
CREATE TABLE japan (region text, prefecture text) PARTITION BY LIST (region);

-- パーティション定義
CREATE TABLE tohoku PARTITION OF japan FOR VALUES IN ('東北');
CREATE TABLE kantou PARTITION OF japan FOR VALUES IN ('関東');
CREATE TABLE kansai PARTITION OF japan FOR VALUES IN ('関西');
CREATE TABLE chubu PARTITION OF japan FOR VALUES IN ('中部');


▪︎ハッシュパーティショニング

ハッシュパーティショニングでは、ハッシュ関数を用いることで均等にデータを振り分けることができます。パーティションキーが少数の同じ値であるなどのケースを除き、パーティションはほぼ同一サイズになります。例えば、会員番号、商品番号などの分割に用いられます。

図3-3 ハッシュパーティショニング


設定例

4つのパーティションを作成し、ハッシュ関数で振り分けます。

-- パーティションテーブル定義
CREATE TABLE member (id int, name text) PARTITION BY HASH (id);

-- パーティション定義
CREATE TABLE member_0 PARTITION OF member FOR VALUES WITH (modulus 4, remainder 0);
CREATE TABLE member_1 PARTITION OF member FOR VALUES WITH (modulus 4, remainder 1);
CREATE TABLE member_2 PARTITION OF member FOR VALUES WITH (modulus 4, remainder 2);
CREATE TABLE member_3 PARTITION OF member FOR VALUES WITH (modulus 4, remainder 3);


表1 パーティショニング種類まとめ

種類
分割方法
パーティショニング例

レンジ

(範囲)
期間や範囲を指定
登録日付、売り上げ日時
リスト
任意のリストを指定
国、都道府県など離散的な値

ハッシュ

ハッシュ関数を使用し、データを均一に分割

会員番号、商品番号など

均一に分割する場合に有効



高橋 由佳(著者)/OSS-DB Gold認定者

 2017年に株式会社NTTデータ入社。システム技術本部 デジタルテクノロジ推進室所属。 性能プロフェッショナルチーム「まかせいのう」で、システムの提案/設計から開発/運用までの各工程におけるパフォーマンス最適化・トラブルシューティング業務に従事。 

​​​​​​​

鳥越 淳(監修)

2008年頃からオープンソースソフトウェアの技術調査や案件導入に従事。PostgreSQLについては、PGConf.ASIA,PostgresConf USなどに登壇。共著に『PostgreSQL徹底入門 第4版』(翔泳社)がある。