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

PostgreSQL11でのパーティショニングの改善

    PostgreSQLのパーティショニングシステムは、2ndQuadrantの創設者である Simon RiggsによってPostgreSQL8.1で最初に追加されました。 。これはリレーションの継承に基づいており、「制約の除外」と呼ばれる、クエリによるスキャンからテーブルを除外するための新しい手法を使用していました。当時は大きな前進でしたが、現在では使用が面倒で遅く、交換が必要とされています。

    バージョン10では、英雄的な努力のおかげで Amit Langoteに置き換えられました モダンスタイルの「宣言型パーティショニング」を備えています。この新しいテクノロジーにより、タプルを正しいパーティションにルーティングするために手動でコードを記述する必要がなくなり、各パーティションの正しい制約を手動で宣言する必要がなくなりました。システムがそれらを自動的に実行しました。

    悲しいことに、PostgreSQL10ではそれがほとんどすべてでした。非常に複雑で時間の制約があるため、PostgreSQL10の実装には欠けていたものがたくさんありました。 ロバートハース ワルシャワのPGConf.EUでそれについて話しました。

    多くの人がPostgreSQL11の状況の改善に取り組みました。これが私の再集計の試みです。これらを3つの領域に分割します:

    1. 新しいパーティショニング機能
    2. パーティションテーブルのDDLサポートの改善
    3. パフォーマンスの最適化。

    新しいパーティショニング機能

    PostgreSQL 10では、パーティション化されたテーブルは RANGEにある可能性があります およびLIST モード。これらは多くの実際のデータベースの基盤となる強力なツールですが、他の多くの設計では、 PostgreSQL11で追加された新しいモード: HASH が必要です。 パーティショニング 。多くのお客様がこれを必要としており、 Amul Sul それを可能にするために一生懸命働いた。簡単な例を次に示します。

    CREATE TABLE clients (
    client_id INTEGER, name TEXT
    ) PARTITION BY HASH (client_id);
    
    CREATE TABLE clients_0 PARTITION OF clients
        FOR VALUES WITH (MODULUS 3, REMAINDER 0);
    CREATE TABLE clients_1 PARTITION OF clients
        FOR VALUES WITH (MODULUS 3, REMAINDER 1);
    CREATE TABLE clients_2 PARTITION OF clients
        FOR VALUES WITH (MODULUS 3, REMAINDER 2);

    すべてのパーティションに同じモジュラス値を使用する必要はありません。これにより、後でさらにパーティションを作成し、必要に応じて、一度に1つのパーティションで行を再配布できます。

    Amit Khandekarによって書かれたもう1つの非常に便利な機能 UPDATEを許可する機能です あるパーティションから別のパーティションに行を移動するには —つまり、パーティショニング列の値に変更があった場合、行は自動的に正しいパーティションに移動されます。以前は、その操作はエラーをスローしていました。

    Amit Langoteによって作成されたもう1つの新機能 そしてあなたは本当に INSERT ON CONFLICT UPDATE パーティションテーブルに適用できます 。以前は、このコマンドがパーティションテーブルを対象とした場合は失敗していました。行が最終的にどのパーティションになるかを正確に知ることで機能させることができますが、それはあまり便利ではありません。そのコマンドの詳細については説明しませんが、 UPSERTが必要な場合は Postgresでは、これがそれです。注意点の1つは、 UPDATE アクションによって行が別のパーティションに移動されない場合があります。

    最後に、PostgreSQL 11のもう1つのかわいい新機能、今回は Jeevan Ladhe、Beena Emerson、Ashutosh Bapat、Rahila Syed、 およびロバートハース パーティションテーブルのデフォルトパーティションのサポート つまり、通常のパーティションのいずれにも収まらないすべての行を受け取るパーティションです。ただし、紙の上では便利ですが、一部の操作ではデフォルトのパーティションを使用しない場合よりも重いロックが必要になるため、この機能は実稼働環境ではあまり便利ではありません。例:新しいパーティションを作成するには、既存の行が新しいパーティションの境界に一致しないことを確認するために、デフォルトのパーティションをスキャンする必要があります。将来的にはこれらのロック要件が緩和される可能性がありますが、それまでの間は使用しないことをお勧めします。

    より優れたDDLサポート

    PostgreSQL 10では、特定のDDLがパーティションテーブルに適用されると機能しなくなり、各パーティションを個別に処理する必要がありました。 PostgreSQL 11では、Simon Riggsが以前に発表したように、これらの制限のいくつかを修正しました。まず、 CREATE INDEXを使用できるようになりました パーティションテーブル上 、本当にあなたが書いた機能。これは、面倒な作業を減らすだけの問題と見なすことができます。パーティションごとにコマンドを繰り返す代わりに(そして、新しいパーティションごとに忘れないようにするために)、親パーティションテーブルに対して1回だけ実行でき、自動的に適用されます。既存および将来のすべてのパーティションに。

    覚えておくべきクールなことの1つは、パーティション内の既存のインデックスの一致です。ご存知のように、インデックスの作成はブロックの提案であるため、時間がかからないほど良いです。この機能は、パーティション内の既存のインデックスが作成中のインデックスと比較されるように作成しました。一致するものがある場合は、パーティションをスキャンして新しいインデックスを作成する必要はありません。既存のインデックスが使用されます。

    これと一緒に、あなた自身も本当に、 UNIQUEを作成することもできます 制約、および PRIMARY KEY 制約 。 2つの注意点:最初に、パーティションキーは主キーの一部である必要があります。これにより、一意のチェックをパーティションごとにローカルで実行できるようになり、グローバルインデックスを回避できます。次に、これらの主キーを参照する外部キーをまだ持つことはできません。 PostgreSQL12用に取り組んでいます。

    あなたができるもう一つのことは(同じ人のおかげで) FOR EACH ROWを作成することです パーティションテーブルでのトリガー 、およびすべてのパーティション(既存および将来)に適用します。副作用として、一意を延期することができます パーティションテーブルの制約。注意点:のみ BEFORE の処理方法がわかるまで、トリガーは許可されます 行を別のパーティションに移動するトリガー。

    最後に、パーティションテーブルは FOREIGN KEYを持つことができます 制約 。これは、誰もが嫌うぶら下がっている参照を避けながら、大きなファクトテーブルを分割するのに非常に便利です。同僚のGabrieleBartoliniは、私がこれを書いたりコミットしたりしたことを知ったとき、私の膝をつかんで、これはゲームチェンジャーであり、どうして彼に知らせないほど鈍感になったのだろうと叫んだ。私、楽しみのためにコードをハックし続けています。

    パフォーマンスワーク

    以前は、スキャンしないパーティション(制約の除外)を見つけるためのクエリの前処理は、かなり単純で低速でした。これは、Amit Langote、David Rowley、Beena Emerson、Dilip Kumarが最初に「より速い剪定」を導入し、その後にそれに基づいて「実行時の剪定」を導入するという素晴らしいチームワークによって改善されました。その結果、はるかに強力で高速になります( David Rowley これについては前の記事ですでに説明しました。)このすべての努力の後、パーティションプルーニングはクエリの存続期間中の3つのポイントで適用されます。

    1. クエリプラン時
    2. クエリパラメータを受け取ったら
    3. あるクエリノードが値をパラメータとして別のノードに渡す各ポイントで。

    これは、クエリプラン時にしか適用できなかった元のシステムからの大幅な改善であり、多くの人に喜ばれると思います。

    enable_partition_pruning をオフにする前後のクエリのEXPLAIN出力を比較することで、この機能の動作を確認できます。 オプション。非常に単純な例として、剪定せずにこの計画を比較します。

    SET enable_partition_pruning TO off;
    EXPLAIN (ANALYZE, COSTS off)
    SELECT * FROM clientes
    WHERE cliente_id = 1234;
    
                                    QUERY PLAN                                
    -------------------------------------------------------------------------
     Append (actual time=6.658..10.549 rows=1 loops=1)
       ->  Seq Scan on clientes_1 (actual time=4.724..4.724 rows=0 loops=1)
             Filter: (cliente_id = 1234)
             Rows Removed by Filter: 24978
       ->  Seq Scan on clientes_00 (actual time=1.914..1.914 rows=0 loops=1)
             Filter: (cliente_id = 1234)
             Rows Removed by Filter: 12644
       ->  Seq Scan on clientes_2 (actual time=0.017..1.021 rows=1 loops=1)
             Filter: (cliente_id = 1234)
             Rows Removed by Filter: 12570
       ->  Seq Scan on clientes_3 (actual time=0.746..0.746 rows=0 loops=1)
             Filter: (cliente_id = 1234)
             Rows Removed by Filter: 12448
       ->  Seq Scan on clientes_01 (actual time=0.648..0.648 rows=0 loops=1)
             Filter: (cliente_id = 1234)
             Rows Removed by Filter: 12482
       ->  Seq Scan on clientes_4 (actual time=0.774..0.774 rows=0 loops=1)
             Filter: (cliente_id = 1234)
             Rows Removed by Filter: 12400
       ->  Seq Scan on clientes_5 (actual time=0.717..0.717 rows=0 loops=1)
             Filter: (cliente_id = 1234)
             Rows Removed by Filter: 12477
     Planning Time: 0.375 ms
     Execution Time: 10.603 ms
    

    剪定で生成されたもので:

    EXPLAIN (ANALYZE, COSTS off)
    SELECT * FROM clientes
    WHERE cliente_id = 1234;
    
                                    QUERY PLAN                               
    ----------------------------------------------------------------------
     Append (actual time=0.054..2.787 rows=1 loops=1)
       ->  Seq Scan on clientes_2 (actual time=0.052..2.785 rows=1 loops=1)
             Filter: (cliente_id = 1234)
             Rows Removed by Filter: 12570
     Planning Time: 0.292 ms
     Execution Time: 2.822 ms
    

    きっとあなたはそれを説得力があると思うでしょう。回帰テストの予想されるファイルを熟読することで、より洗練された例をたくさん見ることができます。

    もう1つの項目は、 Ashutosh Bapatによるパーティションワイズ結合の導入でした。 。ここでの考え方は、2つのパーティション化されたテーブルがあり、それらが同じ方法でパーティション化されている場合、それらが結合されると、一方の各パーティションをもう一方の対応するパーティションに結合できるということです。これは、一方の各パーティションをもう一方のすべてのパーティションに結合するよりもはるかに優れています。パーティションスキームが正確に一致する必要があるという事実 これが実際に使用される可能性は低いように思われるかもしれませんが、実際には、これが当てはまる状況はたくさんあります。例:ordersテーブルとそれに対応するorders_itemsテーブル。ありがたいことに、この制限を緩和するための作業はすでにたくさんあります。

    最後に言及したいのは、 Jeevan Chalke、Ashutosh Bapat、によるパーティションごとの集計です。 およびロバートハース 。この最適化は、 GROUP BYのパーティションキーを含む集計を意味します 句は、各パーティションの行を個別に集約することで実行できます。これははるかに高速です。

    最後の考え

    このサイクルでの重要な開発の後、PostgreSQLにははるかに説得力のあるパーティショニングストーリーがあります。特にパーティション分割されたテーブルに関連するさまざまな操作のパフォーマンスと同時実行性を改善するために、まだ多くの改善が必要ですが、宣言型パーティション分割は、多くのユースケースに役立つ非常に価値のあるツールになっています。 2ndQuadrantでは、8.0以降のすべてのリリースで行ってきたように、この分野やその他の分野でPostgreSQLを改善するためのコードを提供し続けます。


    1. PostgreSQLでテーブルのリスト列名とデータ型を取得するにはどうすればよいですか?

    2. SQLDATEから月と年のみを取得する

    3. MariaDBで日付に年を追加する6つの方法

    4. MariaDBでのMINUTE()のしくみ