pg_partman ネイティブパーティションの使い方サンプル

github.com

このHowToガイドでは、シンプルなシングルレベル・パーティション設定の例をいくつか紹介します。また、既存のデータを持つテーブルからデータをパーティショニングする方法(既存のテーブルのパーティショニングを参照)と、既存のパーティション・セットのパーティショニングを元に戻す方法(ネイティブ・パーティショニングの取り消しを参照)を紹介します。それぞれの関数が何を行うのか、またこの拡張の追加機能の詳細については、pg_partman.mdドキュメントファイルを参照してください。

この文書にある例は、少なくとも4.4.1版のpg_partmanをPostgreSQL 11以降で動作させていることを前提としています。

ここにあるすべての例は、ネイティブなパーティショニングのためのものであることに注意してください。もし、ネイティブではない、トリガーベースのパーティショニングを使用する必要がある場合は、トリガーベースのHowToファイルを参照してください。

Simple Time Based: 1 Partition Per Day

ネイティブ・パーティショニングを行うには、目的のタイプでパーティショニングされるように既に設定されている親テーブルから開始する必要があります。現在、pg_partmanはRANGEタイプのパーティショニングのみをサポートしています(timeとidの両方)。パーティショニングされていないテーブルをパーティショニングされたセットの親テーブルにすることはできませんので、移行が困難になる可能性があります。このドキュメントでは、後でこれを管理するためのいくつかのテクニックを紹介します。とりあえず、この例では、まったく新しいテーブルから始めます。ユニークでないインデックスもPG11+の親テーブルに追加すれば、すべての子テーブルに自動的に作成されます。

CREATE SCHEMA IF NOT EXISTS partman_test;

CREATE TABLE partman_test.time_taptest_table 
    (col1 int, 
    col2 text default 'stuff', 
    col3 timestamptz NOT NULL DEFAULT now()) 
PARTITION BY RANGE (col3);

CREATE INDEX ON partman_test.time_taptest_table (col3);
\d+ partman_test.time_taptest_table 
                               Partitioned table "partman_test.time_taptest_table"
 Column |           Type           | Collation | Nullable |    Default    | Storage  | Stats target | Description 
--------+--------------------------+-----------+----------+---------------+----------+--------------+-------------
 col1   | integer                  |           |          |               | plain    |              | 
 col2   | text                     |           |          | 'stuff'::text | extended |              | 
 col3   | timestamp with time zone |           | not null | now()         | plain    |              | 
Partition key: RANGE (col3)
Indexes:
    "time_taptest_table_col3_idx" btree (col3)
Number of partitions: 0

ネイティブにパーティショニングされた親に対して、パーティション・キーを含まないユニークインデックス(主キーを含む)を作成することはできません。時間ベースのパーティショニングでは、各子テーブルで単一のタイムスタンプ値のみを制限することになるため、一般的にうまくいきません。pg_partmanは、テンプレートテーブルを使用して、現在ネイティブパーティショニングでサポートされていないプロパティを管理することにより、この管理を支援します。これは、制約がパーティションセット全体に渡って強制されないという問題を解決するものではないことに注意してください。PostgreSQLのバージョンによって、どのプロパティがテンプレートで管理されるかは、メインドキュメントを参照してください。

この例では、create_parent()を実行した時に、最初に作成される子テーブルが主キーを持つように、最初にテンプレートテーブルを手動で作成することにしています。pg_partman にテンプレートテーブルを指定しなかった場合、拡張機能をインストールしたスキーマにテンプレートテーブルを作成します。しかし、そのテンプレートに追加したプロパティは、その時点以降に新しく作成された子テーブルにのみ適用されます。既に存在する子テーブルには、手動でそれらのプロパティを遡及して適用する必要があります。

CREATE TABLE partman_test.time_taptest_table_template (LIKE partman_test.time_taptest_table);
ALTER TABLE partman_test.time_taptest_table_template ADD PRIMARY KEY (col1);
 \d partman_test.time_taptest_table_template
          Table "partman_test.time_taptest_table_template"
 Column |           Type           | Collation | Nullable | Default 
--------+--------------------------+-----------+----------+---------
 col1   | integer                  |           | not null | 
 col2   | text                     |           |          | 
 col3   | timestamp with time zone |           | not null | 
Indexes:
    "time_taptest_table_template_pkey" PRIMARY KEY, btree (col1)
SELECT partman.create_parent('partman_test.time_taptest_table', 'col3', 'native', 'daily', p_template_table := 'partman_test.time_taptest_table_template');
 create_parent 
---------------
 t
(1 row)
\d+ partman_test.time_taptest_table
                               Partitioned table "partman_test.time_taptest_table"
 Column |           Type           | Collation | Nullable |    Default    | Storage  | Stats target | Description 
--------+--------------------------+-----------+----------+---------------+----------+--------------+-------------
 col1   | integer                  |           |          |               | plain    |              | 
 col2   | text                     |           |          | 'stuff'::text | extended |              | 
 col3   | timestamp with time zone |           | not null | now()         | plain    |              | 
Partition key: RANGE (col3)
Indexes:
    "time_taptest_table_col3_idx" btree (col3)
Partitions: partman_test.time_taptest_table_p2020_10_26 FOR VALUES FROM ('2020-10-26 00:00:00-04') TO ('2020-10-27 00:00:00-04'),
            partman_test.time_taptest_table_p2020_10_27 FOR VALUES FROM ('2020-10-27 00:00:00-04') TO ('2020-10-28 00:00:00-04'),
            partman_test.time_taptest_table_p2020_10_28 FOR VALUES FROM ('2020-10-28 00:00:00-04') TO ('2020-10-29 00:00:00-04'),
            partman_test.time_taptest_table_p2020_10_29 FOR VALUES FROM ('2020-10-29 00:00:00-04') TO ('2020-10-30 00:00:00-04'),
            partman_test.time_taptest_table_p2020_10_30 FOR VALUES FROM ('2020-10-30 00:00:00-04') TO ('2020-10-31 00:00:00-04'),
            partman_test.time_taptest_table_p2020_10_31 FOR VALUES FROM ('2020-10-31 00:00:00-04') TO ('2020-11-01 00:00:00-04'),
            partman_test.time_taptest_table_p2020_11_01 FOR VALUES FROM ('2020-11-01 00:00:00-04') TO ('2020-11-02 00:00:00-05'),
            partman_test.time_taptest_table_p2020_11_02 FOR VALUES FROM ('2020-11-02 00:00:00-05') TO ('2020-11-03 00:00:00-05'),
            partman_test.time_taptest_table_p2020_11_03 FOR VALUES FROM ('2020-11-03 00:00:00-05') TO ('2020-11-04 00:00:00-05'),
            partman_test.time_taptest_table_default DEFAULT
\d+ partman_test.time_taptest_table_p2020_10_26
                               Table "partman_test.time_taptest_table_p2020_10_26"
 Column |           Type           | Collation | Nullable |    Default    | Storage  | Stats target | Description 
--------+--------------------------+-----------+----------+---------------+----------+--------------+-------------
 col1   | integer                  |           | not null |               | plain    |              | 
 col2   | text                     |           |          | 'stuff'::text | extended |              | 
 col3   | timestamp with time zone |           | not null | now()         | plain    |              | 
Partition of: partman_test.time_taptest_table FOR VALUES FROM ('2020-10-26 00:00:00-04') TO ('2020-10-27 00:00:00-04')
Partition constraint: ((col3 IS NOT NULL) AND (col3 >= '2020-10-26 00:00:00-04'::timestamp with time zone) AND (col3 < '2020-10-27 00:00:00-04'::timestamp with time zone))
Indexes:
    "time_taptest_table_p2020_10_26_pkey" PRIMARY KEY, btree (col1)
    "time_taptest_table_p2020_10_26_col3_idx" btree (col3)
Access method: heap

Simple Serial ID: 1 Partition Per 10 ID Values

このユースケースでは、create_parent()を呼び出す前に、テンプレート・テーブルを手動で作成することはありません。そのため、後からプライマリキーやユニークキーを追加しても、現在存在する子テーブルには適用されないことを示しています。それは手動で行う必要があります。

CREATE TABLE partman_test.id_taptest_table (
    col1 bigint 
    , col2 text not null
    , col3 timestamptz DEFAULT now()
    , col4 text) PARTITION BY RANGE (col1);

CREATE INDEX ON partman_test.id_taptest_table (col1);
\d+ partman_test.id_taptest_table 
                             Partitioned table "partman_test.id_taptest_table"
 Column |           Type           | Collation | Nullable | Default | Storage  | Stats target | Description 
--------+--------------------------+-----------+----------+---------+----------+--------------+-------------
 col1   | bigint                   |           |          |         | plain    |              | 
 col2   | text                     |           | not null |         | extended |              | 
 col3   | timestamp with time zone |           |          | now()   | plain    |              | 
 col4   | text                     |           |          |         | extended |              | 
Partition key: RANGE (col1)
Indexes:
    "id_taptest_table_col1_idx" btree (col1)
Number of partitions: 0


SELECT partman.create_parent('partman_test.id_taptest_table', 'col1', 'native', '10');
 create_parent 
---------------
 t
(1 row)
\d+ partman_test.id_taptest_table
                             Partitioned table "partman_test.id_taptest_table"
 Column |           Type           | Collation | Nullable | Default | Storage  | Stats target | Description 
--------+--------------------------+-----------+----------+---------+----------+--------------+-------------
 col1   | bigint                   |           |          |         | plain    |              | 
 col2   | text                     |           | not null |         | extended |              | 
 col3   | timestamp with time zone |           |          | now()   | plain    |              | 
 col4   | text                     |           |          |         | extended |              | 
Partition key: RANGE (col1)
Indexes:
    "id_taptest_table_col1_idx" btree (col1)
Partitions: partman_test.id_taptest_table_p0 FOR VALUES FROM ('0') TO ('10'),
            partman_test.id_taptest_table_p10 FOR VALUES FROM ('10') TO ('20'),
            partman_test.id_taptest_table_p20 FOR VALUES FROM ('20') TO ('30'),
            partman_test.id_taptest_table_p30 FOR VALUES FROM ('30') TO ('40'),
            partman_test.id_taptest_table_p40 FOR VALUES FROM ('40') TO ('50'),
            partman_test.id_taptest_table_default DEFAULT

テンプレートテーブルの名前は、その親テーブルのpg_partman設定を見ることで確認できます。

select template_table from partman.part_config where parent_table = 'partman_test.id_taptest_table';
                 template_table                 
------------------------------------------------
 partman.template_partman_test_id_taptest_table
ALTER TABLE partman.template_partman_test_id_taptest_table ADD PRIMARY KEY (col2);

ここで、データを追加して、再度メンテナンスを実行し、新しい子テーブルを作成すると...。

INSERT INTO partman_test.id_taptest_table (col1, col2) VALUES (generate_series(1,20), generate_series(1,20)::text||'stuff'::text);

CALL partman.run_maintenance_proc();

\d+ partman_test.id_taptest_table
                             Partitioned table "partman_test.id_taptest_table"
 Column |           Type           | Collation | Nullable | Default | Storage  | Stats target | Description 
--------+--------------------------+-----------+----------+---------+----------+--------------+-------------
 col1   | bigint                   |           |          |         | plain    |              | 
 col2   | text                     |           | not null |         | extended |              | 
 col3   | timestamp with time zone |           |          | now()   | plain    |              | 
 col4   | text                     |           |          |         | extended |              | 
Partition key: RANGE (col1)
Indexes:
    "id_taptest_table_col1_idx" btree (col1)
Partitions: partman_test.id_taptest_table_p0 FOR VALUES FROM ('0') TO ('10'),
            partman_test.id_taptest_table_p10 FOR VALUES FROM ('10') TO ('20'),
            partman_test.id_taptest_table_p20 FOR VALUES FROM ('20') TO ('30'),
            partman_test.id_taptest_table_p30 FOR VALUES FROM ('30') TO ('40'),
            partman_test.id_taptest_table_p40 FOR VALUES FROM ('40') TO ('50'),
            partman_test.id_taptest_table_p50 FOR VALUES FROM ('50') TO ('60'),
            partman_test.id_taptest_table_p60 FOR VALUES FROM ('60') TO ('70'),
            partman_test.id_taptest_table_default DEFAULT

...新しい子テーブル(p50とp60)だけがその主キーを持ち、元のテーブル(p40とそれ以前)は持っていないことがわかるでしょう。

\d partman_test.id_taptest_table_p40
             Table "partman_test.id_taptest_table_p40"
 Column |           Type           | Collation | Nullable | Default 
--------+--------------------------+-----------+----------+---------
 col1   | bigint                   |           |          | 
 col2   | text                     |           | not null | 
 col3   | timestamp with time zone |           |          | now()
 col4   | text                     |           |          | 
Partition of: partman_test.id_taptest_table FOR VALUES FROM ('40') TO ('50')
Indexes:
    "id_taptest_table_p40_col1_idx" btree (col1)

\d partman_test.id_taptest_table_p50
             Table "partman_test.id_taptest_table_p50"
 Column |           Type           | Collation | Nullable | Default 
--------+--------------------------+-----------+----------+---------
 col1   | bigint                   |           |          | 
 col2   | text                     |           | not null | 
 col3   | timestamp with time zone |           |          | now()
 col4   | text                     |           |          | 
Partition of: partman_test.id_taptest_table FOR VALUES FROM ('50') TO ('60')
Indexes:
    "id_taptest_table_p50_pkey" PRIMARY KEY, btree (col2)
    "id_taptest_table_p50_col1_idx" btree (col1)

\d partman_test.id_taptest_table_p60
             Table "partman_test.id_taptest_table_p60"
 Column |           Type           | Collation | Nullable | Default 
--------+--------------------------+-----------+----------+---------
 col1   | bigint                   |           |          | 
 col2   | text                     |           | not null | 
 col3   | timestamp with time zone |           |          | now()
 col4   | text                     |           |          | 
Partition of: partman_test.id_taptest_table FOR VALUES FROM ('60') TO ('70')
Indexes:
    "id_taptest_table_p60_pkey" PRIMARY KEY, btree (col2)
    "id_taptest_table_p60_col1_idx" btree (col1)

Add them manually:

ALTER TABLE partman_test.id_taptest_table_p0 ADD PRIMARY KEY (col2);
ALTER TABLE partman_test.id_taptest_table_p10 ADD PRIMARY KEY (col2);
ALTER TABLE partman_test.id_taptest_table_p20 ADD PRIMARY KEY (col2);
ALTER TABLE partman_test.id_taptest_table_p30 ADD PRIMARY KEY (col2);
ALTER TABLE partman_test.id_taptest_table_p40 ADD PRIMARY KEY (col2);

既存のテーブルのパーティショニング

既存のテーブルをネイティブ・パーティショニングでパーティショニングする方法は、従来のトリガーベースの方法ほど単純ではありません。上記のように、既存のテーブルをネイティブ・パーティション・セットの親テーブルにすることはできません。ネイティブ・パーティション分割されたテーブルの親は、その作成時にパーティション分割を宣言する必要があります。しかし、既存のテーブルを使用してネイティブにパーティショニングする方法はまだあります。そのうちの2つを以下に紹介します。

オフライン・パーティショニング

この方法を「オフライン」と呼んでいるのは、このプロセスのある時点では、1つのオブジェクトから新旧両方のテーブルにデータをアクセスできないからです。データは元のテーブルから新しいテーブルに移動されます。この方法の利点は、ターゲットパーティションサイズよりもずっと小さなバッチでデータを移動できることです。これは、非常に大きなパーティションセットに対して大きな効率上の利点となります(数千のバッチと数百万のバッチでコミットできます)。また、次のオンライン・パーティショニング・メソッドで説明するように、オブジェクトの名前を変更する手順も少なくなります。

外部キーに関する重要な注意事項

パーティション分割されたテーブルをオフラインにすることは、パーティション分割されたテーブルの外部キーがある場合に現実的に有効な唯一の方法です。新しいテーブルを作成しなければならないので、外部キーも再作成しなければならず、FK関係にあるすべてのテーブルを含む停止をしなければなりません。下記のオンライン・メソッドを使えば、より短い停止時間で済むかもしれませんが、どうしても停止しなければならない場合は、このオフライン・メソッドの方が簡単です。

以下は、元のテーブルと生成されたデータである。