sql >> データベース >  >> RDS >> PostgreSQL

PostgreSQL11の新しいパーティショニング機能を利用する方法

    パーティショニングとは何ですか?

    パーティション分割により、大きなテーブルが小さな部分に分割されます。これにより、クエリパフォーマンスが向上し、メンテナンスタスクが容易になり、データアーカイブの効率が向上し、データベースのバックアップが高速化されます。 PostgreSQLのパーティショニングについて詳しくは、ブログ「PostgreSQLでデータをパーティショニングするためのガイド」をご覧ください。

    PostgreSQL 11の最近のリリースでは、多くの新しい驚くべきパーティショニング機能があります。これらの新しいパーティショニング機能の詳細については、このブログでいくつかのコード例を使用して説明します。

    パーティションキーの更新

    PostgreSQL 11より前は、パーティションキーの値を変更するUpdateステートメントは制限されており、許可されていませんでした。これは、新しいバージョンで可能になりました。 Updateステートメントは、パーティションキーの値を変更できます。実際には、行を正しいパーティションテーブルに移動します。内部的には、基本的に古いパーティションからDELETE FROMを実行し、新しいパーティションにINSERTを実行します(DELETE + INSERT)。

    了解しました。これをテストしてみましょう。テーブルを作成し、パーティションキーで更新がどのように機能するかを確認します。

    CREATE TABLE customers(cust_id bigint NOT NULL,cust_name varchar(32) NOT NULL,cust_address text,
    cust_country text)PARTITION BY LIST(cust_country);
    CREATE TABLE customer_ind PARTITION OF customers FOR VALUES IN ('ind');
    CREATE TABLE customer_jap PARTITION OF customers FOR VALUES IN ('jap');
    CREATE TABLE customers_def PARTITION OF customers DEFAULT;
    severalnines_v11=# INSERT INTO customers VALUES (2039,'Puja','Hyderabad','ind');
    INSERT 0 1
    severalnines_v11=#  SELECT * FROM customer_ind;
     cust_id | cust_name | cust_address | cust_country
      2039 | Puja      | Hyderabad    | ind
    (1 row)
    severalnines_v11=# UPDATE customers SET cust_country ='jap' WHERE cust_id=2039;
    UPDATE 1
    --  it moved the row to correct  partition table.
    severalnines_v11=# SELECT * FROM customer_ind;
     cust_id | cust_name | cust_address | cust_country
    ---------+-----------+--------------+--------------
    (0 rows)
    severalnines_v11=# SELECT * FROM customer_jap;
     cust_id | cust_name | cust_address | cust_country
    ---------+-----------+--------------+--------------
        2039 | Puja      | Hyderabad    | jap
    (1 row)

    注意:デフォルトのパーティションテーブルがなく、更新された値がどの子テーブルのパーティション基準とも一致しない場合、UPDATEはエラーになります。

    severalnines_v11=#  UPDATE customers1 SET cust_country ='ypp' WHERE cust_id=2039;
    2018-11-21 00:13:54.901 IST [1479] ERROR:  no partition of relation "customers1" found for row
    2018-11-21 00:13:54.901 IST [1479] DETAIL:  Partition key of the failing row contains (cust_country) = (ypp).
    2018-11-21 00:13:54.901 IST [1479] STATEMENT:  UPDATE customers1 SET cust_country ='ypp' WHERE cust_id=2039;
    ERROR:  no partition of relation "customers1" found for row
    DETAIL:  Partition key of the failing row contains (cust_country) = (ypp).
    [ -- the value of cust_country was not mapped to any part table so it failed]

    デフォルトパーティションの作成

    PostgreSQL 11 DEFAULTパーティション機能は、他のパーティションにマップされないタプルを格納します。 PostgreSQL 11より前は、これらの行はエラーになりました。どのパーティションテーブルにもマップされていない行は、デフォルトのパーティションに挿入されます。

    ラボの例:「USA」の国コードは以下のパーティションテーブルで定義されていませんが、デフォルトのテーブルに正常に挿入されています。

    CREATE TABLE customers_def PARTITION OF customers DEFAULT;
    severalnines_v11=#  INSERT INTO customers VALUES (4499,'Tony','Arizona','USA');
    INSERT 0 1
    severalnines_v11=#  select * FROM customers_def;
     cust_id | cust_name | cust_address | cust_country
    ---------+-----------+--------------+--------------
        4499 | Tony      | Arizona      | USA

    注意:デフォルトのパーティションは、そのパーティション値がデフォルトのテーブルに存在する場合、新しいパーティションの追加を防ぎます。この場合、 `USA`はデフォルトパーティションに存在していたため、以下のようには機能しません。

    severalnines_v11=# CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');
    2018-11-21 00:46:34.890 IST [1526] ERROR:  updated partition constraint for default partition "customers_def" would be violated by some row
    2018-11-21 00:46:34.890 IST [1526] STATEMENT:  CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');ERROR:  updated partition constraint for default partition "customers_def" would be violated by some row
    severalnines_v11=#
    Resolution - You need to move/remove those rows from Default table, then it will then let you create new part table like below.
    severalnines_v11=# DELETE FROM customers_def WHERE cust_country in ('USA'); DELETE 1
    severalnines_v11=# CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');
    CREATE TABLE
    severalnines_v11=#
    Nudgets :

    HASHパーティション表にはDEFAULTパーティションを指定できません。パーティションテーブルに複数のDEFAULTテーブルを含めることはできません。

    ハッシュ分割

    範囲またはリストパーティションを決定できない場合(バケットの大きさがわからないため)、これは新しいパーティションメカニズムです。ハッシュ分割は、このデータ分散の問題を解決します。

    テーブルは、各パーティションのモジュラスと余りを指定することによってパーティション化されます。各パーティションは、パーティションキーのハッシュ値を指定された係数で割った値が指定された余りを生成する行を保持します。 HASH関数は、行がすべてのパーティションテーブルにほぼ均等に分散されることを保証します。

    まず、必要なパーティションテーブルの数を決定する必要があります。したがって、モジュラスと余りを定義できます。モジュラスが4の場合、余りは[0-3]からのみ可能です。

    [モジュラス-テーブルの数|剰余-剰余のどの値がどのバケットに送られるか]

    ハッシュパーティションを設定する方法

    -- hash partition
    CREATE TABLE part_hash_test (x int, y text) PARTITION BY hash (x);
    -- create child partitions
    CREATE TABLE part_hash_test_0 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 0);
    CREATE TABLE part_hash_test_1 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 1);
    CREATE TABLE part_hash_test_2 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 2);
    CREATE TABLE part_hash_test_3 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 3);

    親テーブルに50kレコードを挿入します:

    severalnines_v11=# INSERT INTO part_hash_test SELECT generate_series(0,50000);
    INSERT 0 50001

    子テーブルでレコードがどのように均等に分散されているかを確認してください...

    severalnines_v11=# SELECT count(1),tableoid::regclass FROM part_hash_test GROUP by 2 order by 2 ;
     count |     tableoid
    -------+------------------
     12537 | part_hash_test_0
     12473 | part_hash_test_1
     12509 | part_hash_test_2
     12482 | part_hash_test_3
    (4 rows)

    以前に`Modulus`で指定されたパーティションの数を変更することはできないため、パーティションテーブルの数の要件を十分に前もって計画する必要があります。

    残りが異なる新しいパーティションを追加しようとすると、エラーが発生します。

    severalnines_v11=# CREATE TABLE part_hash_test_5 PARTITION OF part_hash_test FOR VALUES
    WITH (MODULUS 4, REMAINDER 5);severalnines_v11-#
    2018-11-21 01:51:28.966 IST [1675] ERROR:  remainder for hash partition must be less than modulus
    2018-11-21 01:51:28.966 IST [1675] STATEMENT:  CREATE TABLE part_hash_test_5 PARTITION OF part_hash_test FOR VALUES  WITH (MODULUS 4, REMAINDER 5);

    ハッシュパーティショニングは任意のデータ型で機能し、UUID型でも機能します。テーブルの数は2の累乗にすることを常にお勧めします。また、テーブルの作成時に同じ係数を使用する必要はありません。これは、必要に応じて後でパーティションテーブルを作成するのに役立ちます。

    この実装により、バキュームが高速化され、パーティションごとの結合が可能になります。

    外部キーのサポート

    PostgreSQL 11より前は、パーティションテーブルの外部キーはサポートされていませんでした。外部キーは現在パーティションテーブルで可能であり、以下はその方法です...

    severalnines_v11=# CREATE TABLE customers2 ( cust_id integer PRIMARY KEY );
    CREATE TABLE
    severalnines_v11=# CREATE TABLE account (
        ac_date   date    NOT NULL,
        cust_id  integer REFERENCES customers2(cust_id),
         amount INTEGER NOT NULL) PARTITION BY RANGE (ac_date);
    CREATE TABLE

    子テーブルでの自動インデックス作成

    以前のバージョンのPostgreSQLでは、すべてのパーティションテーブルにインデックスを作成するのは手作業でした。 PostgreSQLバージョン11では、ユーザーにとって非常に便利です。マスターテーブルにインデックスが作成されると、既存のすべての子パーティションに同じ構成でインデックスが自動的に作成され、将来のパーティションテーブルも処理されます。

    マスターテーブルに作成されたインデックス

    severalnines_v11=# CREATE index idx_name ON customers(cust_name);
    CREATE INDEX

    以下のように、すべての子テーブルにインデックスが自動的に作成されました。 (カタログテーブルで確認)

    severalnines_v11=# SELECT tablename,indexname,indexdef FROM pg_indexes WHERE tablename ilike '%customer_%';
       tablename   |          indexname          |       indexdef
    ---------------+-----------------------------+------------------------------------------------------------------------------------------
     customer_ind  | customer_ind_cust_name_idx  | CREATE INDEX customer_ind_cust_name_idx ON public.customer_ind USING btree (cust_name)
     customer_jap  | customer_jap_cust_name_idx  | CREATE INDEX customer_jap_cust_name_idx ON public.customer_jap USING btree (cust_name)
     customer_usa  | customer_usa_cust_name_idx  | CREATE INDEX customer_usa_cust_name_idx ON public.customer_usa USING btree (cust_name)
     customers_def | customers_def_cust_name_idx | CREATE INDEX customers_def_cust_name_idx ON public.customers_def USING btree (cust_name)
    (4 rows)

    インデックスはマスターテーブルにのみ作成でき、子テーブルには作成できません。自動生成されたインデックスを個別に削除することはできません。

    子テーブルでの自動トリガーの作成

    マスターテーブルでトリガーが作成されると、すべての子テーブルでトリガーが自動的に作成されます(この動作はインデックスで見られる動作と似ています)。

    一意のインデックスを作成できる

    バージョン11では、マスターテーブルに一意のインデックスを追加して、既存のすべての子テーブルと将来のパーティションテーブルに一意の制約を作成できます。

    一意の制約を持つマスターテーブルを作成しましょう。

    CREATE TABLE uniq_customers(  cust_id bigint NOT NULL, cust_name varchar(32) NOT NULL, cust_address text, cust_country text,cust_email text, unique(cust_email,cust_id,cust_country)  )PARTITION BY LIST(cust_country);
    

    一意の制約は、以下のように子テーブルに自動的に作成されています。

    severalnines_v11=# SELECT table_name,constraint_name,constraint_type FROM information_schema.table_constraints WHERE table_name ilike '%uniq%' AND constraint_type = 'UNIQUE';
        table_name     |                    constraint_name                    | constraint_type
    -------------------+-------------------------------------------------------+-----------------
     uniq_customers    | uniq_customers_cust_email_cust_id_cust_country_key    | UNIQUE
     uniq_customer_ind | uniq_customer_ind_cust_email_cust_id_cust_country_key | UNIQUE
    (2 rows)

    注意:親テーブルの一意性制約は、パーティション階層全体にわたる一意性を実際に保証するものではありません。これはグローバルな制約ではなく、ローカルのみです。

    今日のホワイトペーパーをダウンロードするClusterControlを使用したPostgreSQLの管理と自動化PostgreSQLの導入、監視、管理、スケーリングを行うために知っておくべきことについて学ぶホワイトペーパーをダウンロードする

    より高速なクエリパフォーマンス

    動的パーティションプルーニング

    PostgreSQL 11では、バイナリ検索により、LISTまたはRANGEパーティション化されているかどうかに関係なく、必要な子テーブルをより迅速に識別できます。ハッシュ関数は、HASHパーティションに一致するパーティションを見つけます。実際には、不要なパーティションテーブルを動的に削除し、クエリのパフォーマンスを向上させます。

    動的パーティションプルーニングは、`enable_partition_pruning`パラメーターで制御できます。

    severalnines_v11=# show enable_partition_pruning;
     enable_partition_pruning
    --------------------------
     off
    (1 row)
    severalnines_v11=# EXPLAIN SELECT * from customers where cust_country = 'ind';
                                 QUERY PLAN
    ---------------------------------------------------------------------
     Append  (cost=0.00..18.54 rows=5 width=154)
       ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=154)
             Filter: (cust_country = 'ind'::text)
       ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=154)
             Filter: (cust_country = 'ind'::text)
       ->  Seq Scan on customer_usa  (cost=0.00..15.50 rows=2 width=154)
             Filter: (cust_country = 'ind'::text)
       ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=154)
             Filter: (cust_country = 'ind'::text)
    (9 rows)
    Enabled the parameter to ON.
    severalnines_v11=# set enable_partition_pruning TO on;
    SET
    severalnines_v11=# EXPLAIN SELECT * from customers where cust_country = 'ind';
                                 QUERY PLAN
    --------------------------------------------------------------------
     Append  (cost=0.00..1.02 rows=1 width=154)
       ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=154)
             Filter: (cust_country = 'ind'::text)
    (3 rows)

    他の素晴らしい実装はこのようなものです。

    実行-時間パーティションのプルーニング

    11より前のバージョンのPostgreSQLでは、パーティションのプルーニングは計画時にのみ発生します。プランナーにはパーティションキーの値が必要です 正しいパーティションを識別します。この動作はPostgreSQL11で修正されています。これは、実行時間プランナーがどの値が提供されているかを認識し、それに基づいてパーティションの選択/削除が可能であり、実行速度が大幅に向上するためです。ユースケースは、パラメーター(プリペアドステートメント)を使用するクエリ、またはパラメーターとして値を提供するサブクエリです。

    Example : cus_country is partition key and getting value from subquery
    severalnines_v11=# explain analyze  select * from customers WHERE cust_country = (select cust_count_x FROM test_execution_prun1);
                                                            QUERY PLAN
    ---------------------------------------------------------------------------------------------------------------------------
     Append  (cost=23.60..42.14 rows=5 width=154) (actual time=0.019..0.020 rows=0 loops=1)
       InitPlan 1 (returns $0)
         ->  Seq Scan on test_execution_prun1  (cost=0.00..23.60 rows=1360 width=32) (actual time=0.006..0.007 rows=1 loops=1)
       ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=154) (never executed)
             Filter: (cust_country = $0)
       ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=154) (never executed)
             Filter: (cust_country = $0)
       ->  Seq Scan on customer_usa  (cost=0.00..15.50 rows=2 width=154) (never executed)
             Filter: (cust_country = $0)
       ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=154) (actual time=0.003..0.003 rows=0 loops=1)
             Filter: (cust_country = $0)
     Planning Time: 0.237 ms
     Execution Time: 0.057 ms
    (13 rows)

    上記のプランの説明では、実行時に、プランナーがパラメーター値に基づいて正しいパーティションテーブルを識別し、はるかに高速に実行され、他のパーティションテーブルのスキャン/ループに時間を費やさなかったことがわかります(決して参照しないでください)。上記の説明計画の実行されたセクション)。これは非常に強力であり、パーティショニングにおけるパフォーマンス向上の新時代を開始しました。

    パーティションワイズアグリゲート

    パラメーター:enable_partitionwise_aggregate

    パーティションキーがグループ化キーと一致する場合、すべてのパーティションは、すべてのパーティションを一度にスキャンするのではなく、グループの個別のセットを生成します。パーティションごとに並列集計を実行し、最終結果の間にすべての結果を連結します。

    severalnines_v11=# explain SELECT count(1),cust_country FROM customers GROUP BY 2;
                                     QUERY PLAN
    ----------------------------------------------------------------------------
     HashAggregate  (cost=21.84..23.84 rows=200 width=40)
       Group Key: customer_ind.cust_country
       ->  Append  (cost=0.00..19.62 rows=443 width=32)
             ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=32)
             ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=32)
             ->  Seq Scan on customer_usa  (cost=0.00..14.40 rows=440 width=32)
             ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=32)
    (7 rows)
    severalnines_v11=# SET  enable_partitionwise_aggregate TO on;
    SET
    severalnines_v11=#  explain SELECT count(1),cust_country FROM customers GROUP BY 2;
                                     QUERY PLAN
    ----------------------------------------------------------------------------
     Append  (cost=1.01..22.67 rows=203 width=40)
       ->  HashAggregate  (cost=1.01..1.02 rows=1 width=40)
             Group Key: customer_ind.cust_country
             ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=32)
       ->  HashAggregate  (cost=1.00..1.01 rows=1 width=40)
             Group Key: customer_jap.cust_country
             ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=32)
       ->  HashAggregate  (cost=16.60..18.60 rows=200 width=40)
             Group Key: customer_usa.cust_country
             ->  Seq Scan on customer_usa  (cost=0.00..14.40 rows=440 width=32)
       ->  HashAggregate  (cost=1.00..1.01 rows=1 width=40)
             Group Key: customers_def.cust_country
             ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=32)
    (13 rows)

    並列集計処理とパーティションごとのスキャンが含まれているため、これは確かに高速です。

    カタログクエリを使用して、すべての親パーティションテーブルを知ることができます。

    SELECT relname FROM pg_class WHERE oid in (select partrelid FROM  pg_partitioned_table);

    簡単なパーティション機能マトリックス

    パーティション機能 v11 v10
    デフォルトのパーティション はい いいえ
    外部テーブルの継承 はい いいえ
    ハッシュキーによるパーティション分割 はい いいえ
    PKとFKのサポート はい いいえ
    パーティションキーの更新 はい いいえ
    CTでの自動Inexes はい いいえ
    CTでの自動トリガー はい いいえ
    実行時間パーティションのプルーニング はい いいえ
    パーティションごとの参加 はい いいえ
    動的パーティションプルーン はい いいえ

    次は?

    パーティショニングパフォーマンス

    これは、現在PostgreSQLコミュニティで最も活発な作業領域の1つです。 PostgreSQLバージョン12は、パーティショニングスペースでさらにパフォーマンスが向上したパッケージになります。バージョン12は2019年11月にリリースされる予定です。


    1. Receive-Jobによって返される予期しない変数タイプ

    2. 挿入...マージ...選択(SQL Server)

    3. レベル15,000にこぼれるソート

    4. Oracleでnull値の列を合計する