このトピックでは、PartitionedTableScan (PTS) 演算子の使用方法、この演算子の使用制限、およびこの演算子とAppend演算子のパフォーマンス比較について説明します。
前提条件
この機能は、次のエンジンを実行するPolarDB for PostgreSQLクラスターでサポートされています。
PostgreSQL 14 (バージョン14.9.15.0以降)
次のステートメントを実行して、PolarDB for PostgreSQLクラスターのリビジョンバージョンを表示できます。
select version();
背景情報
パーティションテーブルのスキャン中に、オプティマイザは各パーティションの最適なクエリプランを生成し、Append演算子を使用してこれらのプランをパーティションテーブル全体の最適なクエリプランとして並列に結合します。 パーティションの数が少ない場合、このプロセスは高速です。 ただし、PolarDB for PostgreSQL は、パーティションテーブルのパーティション数を制限しません。 パーティションの数が増加するにつれて、オプティマイザによって消費される時間とSQLステートメントの実行によって消費されるメモリが大幅に増加します。 分割テーブルを照会する性能は、同じサイズの共通テーブルを照会する性能よりも著しく低い。
クエリのパフォーマンスの要件を満たすために、PolarDB for PostgreSQL は、より効率的なソリューションとしてPTS演算子を提供しています。 PTS演算子は、Append演算子と比較して、オプティマイザがクエリプランを生成するのに必要な時間を大幅に短縮し、SQL文の実行に使用するメモリを少なくして、OOMエラーを回避します。
制限
PTS演算子は、
SELECT
文でのみ使用できます。PTS演算子は、パーティション単位の結合では使用できません。 パーティションごとの結合を有効にすると、実行プランにPTS演算子が含まれません。
Parameters
パラメーター | 説明 |
polar_num_parts_for_pts | PTS演算子を有効にするためのしきい値として使用されるパーティション分割テーブルのパーティション数。 デフォルト値: 64。 有効な値:
|
使用法
パラメーターを使用してPTS演算子を有効にする
SET polar_num_parts_for_pts TO 64;
ヒントの使用
ヒントPTScan(tablealias)
を使用します。 以下の例をご参照ください。
EXPLAIN (COSTS OFF, ANALYZE) /*+ PTScan(part_range) */ SELECT * FROM part_range;
QUERY PLAN
--------------------------------------------------------------------------------
PartitionedTableScan on part_range (actual time=86.404..86.405 rows=0 loops=1)
Scan 1000 Partitions: part_range_p0, part_range_p1, part_range_p2,...
-> Seq Scan on part_range
Planning Time: 36.613 ms
Execution Time: 89.246 ms
(5 rows)
並列クエリ
PTS演算子は並列クエリをサポートします。 パーティション間並列処理とハイブリッド並列処理がサポートされ、デフォルトで有効になっています。 さらなる構成は必要とされない。
パーティション間の並列処理: 各ワーカーは1つのパーティションのみをクエリします。
ハイブリッド並列処理: パーティション間並列クエリとパーティション内並列クエリの両方を実行できます。
例
2つのパーティションテーブルを作成し、1,000パーティションを作成します。
CREATE TABLE part_range (a INT, b VARCHAR, c NUMERIC, d INT8) PARTITION BY RANGE (a); SELECT 'CREATE TABLE part_range_p' || i || ' PARTITION OF part_range FOR VALUES FROM (' || 10 * i || ') TO (' || 10 * (i + 1) || ');' FROM generate_series(0,999) i;\gexec CREATE TABLE part_range2 (a INT, b VARCHAR, c NUMERIC, d INT8) PARTITION BY RANGE (a); SELECT 'CREATE TABLE part_range2_p' || i || ' PARTITION OF part_range2 FOR VALUES FROM (' || 10 * i || ') TO (' || 10 * (i + 1) || ');' FROM generate_series(0,999) i;\gexec
パーティションテーブルに対する完全なテーブルスキャンのための次のクエリプランが生成されます。
SET polar_num_parts_for_pts TO 0; EXPLAIN (COSTS OFF, ANALYZE) SELECT * FROM part_range; QUERY PLAN --------------------------------------------------------------------------------------------- Append (actual time=8.376..8.751 rows=0 loops=1) -> Seq Scan on part_range_p0 part_range_1 (actual time=0.035..0.036 rows=0 loops=1) -> Seq Scan on part_range_p1 part_range_2 (actual time=0.009..0.009 rows=0 loops=1) -> Seq Scan on part_range_p2 part_range_3 (actual time=0.010..0.011 rows=0 loops=1) ... ... ... -> Seq Scan on part_range_p997 part_range_998 (actual time=0.009..0.009 rows=0 loops=1) -> Seq Scan on part_range_p998 part_range_999 (actual time=0.010..0.010 rows=0 loops=1) -> Seq Scan on part_range_p999 part_range_1000 (actual time=0.009..0.009 rows=0 loops=1) Planning Time: 785.169 ms Execution Time: 163.534 ms (1003 rows)
2つのパーティションテーブルをクエリに結合すると、クエリのパフォーマンスがさらに低下し、SQLステートメントの実行によってさらに多くのメモリが消費されます。
=> SET polar_num_parts_for_pts TO 0; => EXPLAIN (COSTS OFF, ANALYZE) SELECT COUNT(*) FROM part_range a JOIN part_range2 b ON a.a = b.a WHERE b.c = '0001'; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------- Finalize Aggregate (actual time=3191.718..3212.437 rows=1 loops=1) -> Gather (actual time=2735.417..3212.288 rows=3 loops=1) Workers Planned: 2 Workers Launched: 2 -> Partial Aggregate (actual time=2667.247..2667.789 rows=1 loops=3) -> Parallel Hash Join (actual time=1.957..2.497 rows=0 loops=3) Hash Cond: (a.a = b.a) -> Parallel Append (never executed) -> Parallel Seq Scan on part_range_p0 a_1 (never executed) -> Parallel Seq Scan on part_range_p1 a_2 (never executed) -> Parallel Seq Scan on part_range_p2 a_3 (never executed) ... ... ... -> Parallel Seq Scan on part_range_p997 a_998 (never executed) -> Parallel Seq Scan on part_range_p998 a_999 (never executed) -> Parallel Seq Scan on part_range_p999 a_1000 (never executed) -> Parallel Hash (actual time=0.337..0.643 rows=0 loops=3) Buckets: 4096 Batches: 1 Memory Usage: 0kB -> Parallel Append (actual time=0.935..1.379 rows=0 loops=1) -> Parallel Seq Scan on part_range2_p0 b_1 (actual time=0.001..0.001 rows=0 loops=1) Filter: (c = '1'::numeric) -> Parallel Seq Scan on part_range2_p1 b_2 (actual time=0.001..0.001 rows=0 loops=1) Filter: (c = '1'::numeric) -> Parallel Seq Scan on part_range2_p2 b_3 (actual time=0.001..0.001 rows=0 loops=1) Filter: (c = '1'::numeric) ... ... ... -> Parallel Seq Scan on part_range2_p997 b_998 (actual time=0.001..0.001 rows=0 loops=1) Filter: (c = '1'::numeric) -> Parallel Seq Scan on part_range2_p998 b_999 (actual time=0.000..0.001 rows=0 loops=1) Filter: (c = '1'::numeric) -> Parallel Seq Scan on part_range2_p999 b_1000 (actual time=0.002..0.002 rows=0 loops=1) Filter: (c = '1'::numeric) Planning Time: 1900.615 ms Execution Time: 3694.320 ms (3013 rows)
この例は、パーティション分割テーブルのフルテーブルクエリのパフォーマンスが、共通テーブルのパフォーマンスよりも低いことを示しています。 これは、フルテーブルクエリには、特定のパーティション内のクエリを制限できる条件が含まれていないためです。 パーティションプルーニングは、フルテーブルクエリで使用して、いくつかのパーティションのみをクエリできます。 ただし、一部のオンライン分析処理 (OLAP) クエリでは、パーティションテーブル全体をスキャンする必要があります。 この場合、PTS演算子は、追加演算子よりも効率的である。
SET polar_num_parts_for_pts TO 10; EXPLAIN (COSTS OFF, ANALYZE) SELECT * FROM part_range; QUERY PLAN -------------------------------------------------------------------------------- PartitionedTableScan on part_range (actual time=86.404..86.405 rows=0 loops=1) Scan 1000 Partitions: part_range_p0, part_range_p1, part_range_p2,... -> Seq Scan on part_range Planning Time: 36.613 ms Execution Time: 89.246 ms (5 rows)
SET polar_num_parts_for_ptsを10に
SET polar_num_parts_for_pts TO 10; EXPLAIN (COSTS OFF, ANALYZE) SELECT COUNT(*) FROM part_range a JOIN part_range2 b ON a.a = b.a WHERE b.c = '0001'; QUERY PLAN ---------------------------------------------------------------------------------------------------- Aggregate (actual time=61.384..61.388 rows=1 loops=1) -> Merge Join (actual time=61.378..61.381 rows=0 loops=1) Merge Cond: (a.a = b.a) -> Sort (actual time=61.377..61.378 rows=0 loops=1) Sort Key: a.a Sort Method: quicksort Memory: 25kB -> PartitionedTableScan on part_range a (actual time=61.342..61.343 rows=0 loops=1) Scan 1000 Partitions: part_range_p0, part_range_p1, part_range_p2, ... -> Seq Scan on part_range a -> Sort (never executed) Sort Key: b.a -> PartitionedTableScan on part_range2 b (never executed) -> Seq Scan on part_range2 b Filter: (c = '1'::numeric) Planning Time: 96.675 ms Execution Time: 64.913 ms (16 rows)
結果は、PTS演算子を使用してクエリ計画を生成するのに必要な時間は、Append演算子を使用するよりもはるかに短いことを示しています。
パフォーマンス比較
次のテストデータは、テスト環境で生成され、参照用にのみ提供されます。 テストは、一貫した環境構成で実行され、AppendとPTSのパフォーマンスの違いを比較します。 パーティションの数は唯一の変数です。
1つのSQL文のクエリプランの生成に必要な時間
パーティションの数 | Append | PTS |
16 | 0.266 ms | 0.067 ms |
32 | 1.820 ms | 0.258 ms |
64 | 3.654 ms | 0.402 ms |
128 | 7.010 ms | 0.664 ms |
256 | 14.095 ms | 1.247 ms |
512 | 27.697 ms | 2.328 ms |
1024 | 73.176 ms | 4.165 ms |
単一のSQL文によるメモリ使用量
パーティションの数 | Append | PTS |
16 | 1170 KB | 1044 KB |
32 | 1240 KB | 1044 KB |
64 | 2120 KB | 1624 KB |
128 | 2244 KB | 1524 KB |
256 | 2888 KB | 2072 KB |
512 | 4720 KB | 3012 KB |
1024 | 8236 KB | 5280 KB |
PGBench QPS
パーティションの数 | Append | PTS |
16 | 25318 | 93950 |
32 | 10906 | 61879 |
64 | 5281 | 30839 |
128 | 2195 | 16684 |
256 | 920 | 8372 |
512 | 92 | 3708 |
1024 | 21 | 1190 |