すべてのプロダクト
Search
ドキュメントセンター

PolarDB:PartitionedTableScan演算子

最終更新日:Jul 01, 2024

このトピックでは、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演算子は、パーティションの数がこの値を超える場合に使用されます。

  • 0: パーティションの数に関係なく、PTS演算子は使用されません。

  • -1: PTS演算子は、パーティションの数に関係なく使用されます。

使用法

パラメーターを使用して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つのパーティションのみをクエリします。

  • ハイブリッド並列処理: パーティション間並列クエリとパーティション内並列クエリの両方を実行できます。

image.png

  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
  2. パーティションテーブルに対する完全なテーブルスキャンのための次のクエリプランが生成されます。

    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)
  3. 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)
  4. この例は、パーティション分割テーブルのフルテーブルクエリのパフォーマンスが、共通テーブルのパフォーマンスよりも低いことを示しています。 これは、フルテーブルクエリには、特定のパーティション内のクエリを制限できる条件が含まれていないためです。 パーティションプルーニングは、フルテーブルクエリで使用して、いくつかのパーティションのみをクエリできます。 ただし、一部のオンライン分析処理 (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