このトピックでは、PartitionedTableScan 機能の背景、使用方法、パフォーマンスについて説明します。
背景情報
PolarDB for PostgreSQL および では、パーティションテーブル内のパーティション数に制限はありません。テーブルに 2 つ以上のレベルのパーティションがある場合、パーティション数は指数関数的に増加します。
たとえば、テーブルに 2 つのレベルのパーティションがあるとします。第 1 レベルには 100 個のハッシュパーティションがあります。各第 1 レベルのパーティションは、さらに 100 個のハッシュサブパーティションに分割されます。パーティションテーブル全体には 10,000 個のパーティションがあります。このパーティションテーブルをクエリすると、クエリプランは次のようになります。
explain analyze select * from part_hash;
クエリプラン
-----------------------------------------------------------------------------
Append (cost=0.00..344500.00 rows=16300000 width=22)
-> Seq Scan on part_hash_sys0102 (cost=0.00..26.30 rows=1630 width=22)
-> Seq Scan on part_hash_sys0103 (cost=0.00..26.30 rows=1630 width=22)
-> Seq Scan on part_hash_sys0104 (cost=0.00..26.30 rows=1630 width=22)
...
...
...
-> Seq Scan on part_hash_sys10198 (cost=0.00..26.30 rows=1630 width=22)
-> Seq Scan on part_hash_sys10199 (cost=0.00..26.30 rows=1630 width=22)
-> Seq Scan on part_hash_sys10200 (cost=0.00..26.30 rows=1630 width=22)
プランニング時間: 3183.644 ms
実行時間: 633.779 ms
(10003 行)結果から、クエリが遅いことがわかります。これは、オプティマイザーがパーティションテーブルを処理する際に、まず各パーティションに対して最適なプランを生成し、次に Append オペレーターを使用してこれらのプランを結合し、パーティションテーブルの最終的なプランを作成するためです。パーティションテーブルのパーティション数が少ない場合、このプロセスは高速であり、ユーザーは遅延に気づきません。しかし、パーティション数が増えるにつれて、プロセスは著しく遅くなります。パーティションテーブルに対するクエリは、標準テーブルに対するクエリよりもはるかに遅くなる可能性があります。
たとえば、上記の SQL 文では、テーブル part_hash には 10,000 個のパーティションがあります。その Planning Time は約 3 秒です。対照的に、標準テーブルに対するクエリの Planning Time はわずか 0.1 ミリ秒です。これは数百倍の違いです。Planning Time が長いことに加えて、クエリプロセスは大量のメモリも使用するため、メモリ不足 (OOM) エラーが発生する可能性があります。
パーティションテーブルのこの欠点は、JOIN クエリでより顕著になります。
CREATE TABLE part_hash2 (a int, b int, c varchar(10))
PARTITION BY HASH(a) SUBPARTITION BY HASH (b) PARTITIONS 100 SUBPARTITIONS 100;
explain analyze select count(*) from part_hash a join part_hash2 b on a.a=b.b where b.c = '0001';
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=48970442.90..48970442.91 rows=1 width=8) (actual time=6466.854..6859.935 rows=1 loops=1)
-> Gather (cost=48970442.68..48970442.89 rows=2 width=8) (actual time=397.780..6859.902 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=48969442.68..48969442.69 rows=1 width=8) (actual time=4.748..11.768 rows=1 loops=3)
-> Merge Join (cost=1403826.01..4217776.01 rows=2716666667 width=0) (actual time=4.736..11.756 rows=0 loops=3)
Merge Cond: (a.a = b.b)
-> Sort (cost=1093160.93..1110135.93 rows=6790000 width=4) (actual time=4.734..8.588 rows=0 loops=3)
Sort Key: a.a
Sort Method: quicksort Memory: 25kB
Worker 0: Sort Method: quicksort Memory: 25kB
Worker 1: Sort Method: quicksort Memory: 25kB
-> Parallel Append (cost=0.00..229832.35 rows=6790000 width=4) (actual time=4.665..8.518 rows=0 loops=3)
-> Parallel Seq Scan on part_hash_sys0102 a (cost=0.00..19.59 rows=959 width=4) (actual time=0.001..0.001 rows=0 loops=1)
-> Parallel Seq Scan on part_hash_sys0103 a_1 (cost=0.00..19.59 rows=959 width=4) (actual time=0.001..0.001 rows=0 loops=1)
-> Parallel Seq Scan on part_hash_sys0104 a_2 (cost=0.00..19.59 rows=959 width=4) (actual time=0.001..0.001 rows=0 loops=1)
...
-> Sort (cost=310665.08..310865.08 rows=80000 width=4) (実行されませんでした)
Sort Key: b.b
-> Append (cost=0.00..304150.00 rows=80000 width=4) (実行されませんでした)
-> Seq Scan on part_hash2_sys0102 b (cost=0.00..30.38 rows=8 width=4) (実行されませんでした)
Filter: ((c)::text = '0001'::text)
-> Seq Scan on part_hash2_sys0103 b_1 (cost=0.00..30.38 rows=8 width=4) (実行されませんでした)
Filter: ((c)::text = '0001'::text)
-> Seq Scan on part_hash2_sys0104 b_2 (cost=0.00..30.38 rows=8 width=4) (実行されませんでした)
Filter: ((c)::text = '0001'::text)
...
プランニング時間: 221082.616 ms
実行時間: 9500.148 ms
(30018 行)これは、全表スキャン中に、パーティションテーブルが標準テーブルよりも効率が低いことを示しています。これは、クエリにスキャンを特定のパーティションに制限する条件がないために発生します。パーティションテーブルのすべての利点が失われます。パーティションプルーニングを使用して、クエリを少数のパーティションに制限することができます。ただし、一部のオンライン分析処理 (OLAP) シナリオでは、パーティションテーブル全体の全表スキャンが必要です。
概要
この問題を解決するために、PolarDB for PostgreSQL および は PartitionedTableScan オペレーターを提供します。このパーティションテーブル用のクエリオペレーターは、Append オペレーターよりも効率的です。Planning Time を大幅に短縮し、メモリ消費量を削減することで、メモリ不足 (OOM) エラーの防止に役立ちます。
以下に、PartitionedTableScan オペレーターを使用して実行された 2 つの個別の SQL クエリの Planning Time とメモリ使用量を示します。
explain analyze select * from part_hash;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
PartitionedTableScan on part_hash (cost=0.00..1.00 rows=1 width=22) (actual time=134.348..134.352 rows=0 loops=1)(Iteration partition number 10000)
Scan Partitions: part_hash_sys0102, part_hash_sys0103, ...part_hash_sys10198, part_hash_sys10199, part_hash_sys10200
-> Seq Scan on part_hash (cost=0.00..1.00 rows=1 width=22)
Planning Time: 293.778 ms
Execution Time: 384.202 ms
(5 rows)
explain analyze select count(*) from part_hash a join part_hash2 b on a.a=b.b where b.c = '0001';
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=2.02..2.03 rows=1 width=8) (actual time=152.322..152.326 rows=1 loops=1)
-> Nested Loop (cost=0.00..2.02 rows=1 width=0) (actual time=152.308..152.311 rows=0 loops=1)
Join Filter: (a.a = b.b)
-> PartitionedTableScan on part_hash a (cost=0.00..1.00 rows=1 width=4) (actual time=152.305..152.306 rows=0 loops=1)(Iteration partition number 10000)
Scan Partitions: part_hash_sys0102, part_hash_sys0103,, part_hash_sys10198, part_hash_sys10199, part_hash_sys10200
-> Seq Scan on part_hash a (cost=0.00..1.00 rows=1 width=4)
-> PartitionedTableScan on part_hash2 b (cost=0.00..1.00 rows=1 width=4) (never executed)
-> Seq Scan on part_hash2 b (cost=0.00..1.00 rows=1 width=4)
Filter: ((c)::text = '0001'::text)
Planning Time: 732.952 ms
Execution Time: 436.927 ms
(11 rows)ご覧のとおり、この例では、PartitionedTableScan オペレーターを使用した後に Planning Time が大幅に短縮されています。次の表に、具体的な値の比較を示します。
タイプ |
|
|
単一クエリのプランニング時間 | 3183.644ms | 293.778ms |
JOIN クエリのプランニング時間 | 221082.616ms | 732.952ms |
適用範囲
PartitionedTableScanオペレーターは、マイナーエンジンバージョンが 2.0.11.9.32.0 以降の PolarDB for PostgreSQL 14 でのみサポートされます。PartitionedTableScanは現在、SELECT文のみをサポートしています。DML 文はサポートしていません。PartitionedTableScanは、パーティションワイズ結合をサポートしていません。パーティションワイズ結合機能を有効にすると、PartitionedTableScanプランは生成されません。
PartitionedTableScan 機能は、マイナーエンジンバージョンが 2.0.11.9.32.0 以降のクラスターでのみ利用可能です。以前のマイナーエンジンバージョンの既存のクラスターがあり、この機能を使用する必要がある場合は、お問い合わせください。
使用方法
以下の例は、PartitionedTableScan 機能の使用方法を示しています。
まず、パーティションテーブルを作成します。
CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY Hash(a) partitions 16;パラメーターを使用した PartitionedTableScan の有効化
polar_num_parts_for_pts パラメーターを設定することで、PartitionedTableScan 機能を有効または無効にできます。
パラメーター | 有効値 | デフォルト値 | 説明 |
polar_num_parts_for_pts | -1 から INT_MAX | 0 |
|
以下に例を示します。
SET polar_num_parts_for_pts to -1;
explain select * from prt1;
QUERY PLAN
-----------------------------------------------------------------
PartitionedTableScan on prt1 (cost=0.00..1.00 rows=1 width=40)
-> Seq Scan on prt1 (cost=0.00..1.00 rows=1 width=40)
(2 rows)ヒントの使用
ヒント構文 PARTEDSCAN(テーブルのエイリアス) を使用します。以下に例を示します。
EXPLAIN select /*+PARTEDSCAN(prt1) */ select * from prt1;
QUERY PLAN
-----------------------------------------------------------------
PartitionedTableScan on prt1 (cost=0.00..1.00 rows=1 width=40)
-> Seq Scan on prt1 (cost=0.00..1.00 rows=1 width=40)
(2 rows)パラレルクエリ
PolarDB for PostgreSQL および は、パーティションテーブルのパラレルクエリをサポートしています。この機能により、大規模なデータセットに対する効率的なクエリが可能になります。Append と同様に、PartitionedTableScan もパラレルクエリをサポートしています。ただし、PartitionedTableScan のパラレルクエリ実装は Parallel PartitionedTableScan と呼ばれ、パーティション間並列処理とハイブリッド並列処理の 2 つの並列モードのみをサポートします。

パーティション間並列処理
パーティション間並列処理とは、各ワーカーが異なるパーティションをクエリすることを意味します。これにより、複数のワーカーがパーティションテーブル全体を並列でクエリできます。
EXPLAIN (COSTS OFF) select /*+PARTEDSCAN(prt1) */ * from prt1;
QUERY PLAN
---------------------------------------------
Gather
Workers Planned: 4
-> Parallel PartitionedTableScan on prt1
-> Seq Scan on prt1
(4 rows)上記のプランに示されているように、パーティションテーブルに対して 4 つのパラレルワーカーが開始されます (Workers Planned: 4)。各ワーカーは 1 つのパーティションのクエリを担当します。明確な指標は、Parallel PartitionedTableScan という名前のオペレーターです。
ハイブリッド並列処理
ハイブリッド並列処理とは、パーティション間と各パーティション内の両方で実行を並列化できることを意味します。これにより、パラレルクエリで最高の並列処理の次数が提供されます。
EXPLAIN (COSTS OFF) select /*+PARTEDSCAN(prt1) */ * from prt1;
QUERY PLAN
---------------------------------------------
Gather
Workers Planned: 8
-> Parallel PartitionedTableScan on prt1
-> Parallel Seq Scan on prt1
(4 rows)上記のプランに示されているように、クエリは並列実行に 8 つのワーカーを使用します (Workers Planned: 8)。クエリはパーティション間および各パーティション内で並列に実行できます。明確な指標は、Parallel PartitionedTableScan という名前のオペレーターです。
どちらの並列モードにも独自のコストモデルがあり、オプティマイザーが最適なものを選択します。
パーティションプルーニング
Append と同様に、PartitionedTableScan は 3 つの段階でパーティションプルーニングをサポートします。詳細については、「パーティションプルーニング」をご参照ください。
パフォーマンス比較
PartitionedTableScan は Append よりも効率的です。以下のテストデータは、PartitionedTableScan と Append のパフォーマンス比較を示しています。
以下のデータは開発環境のものであり、参照用です。実際のパフォーマンスは、ご利用の構成や条件によって異なる場合があります。このテストの目的は、オペレーターが唯一の変数である一貫した環境で、Append と PartitionedTableScan のパフォーマンスを比較することです。
テストには次の SQL 文が使用されます。
explain select * from prt1 where b = 10;
explain select /*+PARTEDSCAN(prt1) */ * from prt1 where b = 10; 単一 SQL 文のプランニング時間
パーティション数 | Append のプランニング時間 | PartitionedTableScan のプランニング時間 |
16 | 0.266ms | 0.067ms |
32 | 1.820ms | 0.258ms |
64 | 3.654ms | 0.402ms |
128 | 7.010ms | 0.664ms |
256 | 14.095ms | 1.247ms |
512 | 27.697ms | 2.328ms |
1024 | 73.176ms | 4.165ms |
メモリ (単一 SQL 文のメモリ使用量)
パーティション数 | Append のメモリ | PartitionedTableScan のメモリ |
16 | 1,170 KB | 1,044 KB |
32 | 1,240 KB | 1,044 KB |
64 | 2,120 KB | 1,624 KB |
128 | 2,244 KB | 1,524 KB |
256 | 2,888 KB | 2,072 KB |
512 | 4,720 KB | 3,012 KB |
1024 | 8,236 KB | 5,280 KB |
1秒あたりのクエリ数 (QPS)
pgbench -i --scale=10
pgbench -c 64 -j 64 -n -T60
Query:
explain select * from prt1 where b = 10;
explain select /*+PARTEDSCAN(prt1) */ * from prt1 where b = 10; パーティション数 | QPS の付加 | PartitionedTableScan の QPS |
16 | 25,318 | 93,950 |
32 | 10,906 | 61,879 |
64 | 5,281 | 30,839 |
128 | 2,195 | 16,684 |
256 | 920 | 8,372 |
512 | 92 | 3,708 |
1024 | 21 | 1,190 |
結論
前述の PartitionedTableScan と Append の比較は、パーティション数が増えるにつれて、PartitionedTableScan が Append よりもパフォーマンスを大幅に向上させることを示しています。したがって、お使いのパーティションテーブルに多数のパーティションがあり、Planning Time が長い場合は、最適化のために PartitionedTableScan を使用することをお勧めします。