Scheduled Acceleration

Updated at:

PartitionedTableScan is a query operator for partitioned tables in PolarDB for PostgreSQL that reduces planning time and memory usage compared to the standard Append operator.

How it works

The PostgreSQL query optimizer handles partitioned tables by generating an optimal plan for each partition, then combining them with the Append operator. For tables with few partitions, this is fast. As the partition count grows, the cost compounds significantly.

Consider a two-level hash-partitioned table with 100 partitions at each level — 10,000 partitions in total. Querying it with the standard Append operator produces a plan like this:

explain analyze select * from part_hash;
                                 QUERY PLAN
-----------------------------------------------------------------------------
 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_sys10200  (cost=0.00..26.30 rows=1630 width=22)
 Planning Time: 3183.644 ms
 Execution Time: 633.779 ms
(10003 rows)

Planning time exceeds 3 seconds. On a standard table, the same query plans in under 0.1 ms — a difference of several hundred times. Beyond slow planning, the process also consumes large amounts of memory, which can cause out-of-memory (OOM) errors.

The problem is worse for join queries. A join between two 10,000-partition tables with Append produces a planning time of over 221 seconds.

PartitionedTableScan solves this by replacing the per-partition plan generation used by Append with a single unified operator. The same queries with PartitionedTableScan enabled:

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)

Planning time comparison with 10,000 partitions:

Query type Append PartitionedTableScan
Single query 3,183.644 ms 293.778 ms
Join query 221,082.616 ms 732.952 ms

Limitations

  • Supported on PolarDB for PostgreSQL 14 with a minor engine version of 2.0.11.9.32.0 or later. To enable this feature on a cluster with an earlier version, contact us.

  • Supports SELECT statements only. Data Manipulation Language (DML) statements are not supported.

  • Incompatible with partition-wise join. When partition-wise join is enabled, the optimizer does not generate a PartitionedTableScan plan.

Enable PartitionedTableScan

Set the parameter

Control PartitionedTableScan with the polar_num_parts_for_pts parameter:

Parameter Valid values Default
polar_num_parts_for_pts -1 to INT_MAX 0

The parameter sets the subpartition count threshold that triggers the operator:

  • 0 (default): PartitionedTableScan is never enabled.

  • -1: PartitionedTableScan is always enabled, regardless of partition count.

  • Greater than 0: PartitionedTableScan activates automatically when the number of subpartitions exceeds this value. For example, setting it to 64 enables the operator only when a table has more than 64 subpartitions.

Example — enable for all queries:

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)

Use a query hint

Apply PartitionedTableScan to a specific query with the PARTEDSCAN hint, without changing the parameter:

EXPLAIN SELECT /*+PARTEDSCAN(prt1) */ * 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)

The hint syntax is PARTEDSCAN(table_alias). Use the table alias as specified in the query.

Parallel query

PartitionedTableScan supports parallel query through the Parallel PartitionedTableScan operator. Two parallelism modes are available:

Inter-partition parallelism

Each worker queries a different partition, allowing the full partitioned table to be scanned in parallel across workers.

EXPLAIN (COSTS OFF) SELECT /*+PARTEDSCAN(prt1) */ * FROM prt1;
                 QUERY PLAN
---------------------------------------------
 Gather
   Workers Planned: 4
   ->  Parallel PartitionedTableScan on prt1
         ->  Seq Scan on prt1
(4 rows)

In this plan, four workers handle the scan in parallel across partitions.

Hybrid parallelism

Workers run in parallel both between partitions and within each partition, achieving the highest degree of parallelism.

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)

In this plan, eight workers run in parallel across and within partitions. The inner Parallel Seq Scan indicates intra-partition parallelism.

The optimizer selects the mode with the lower cost based on its cost models.

Partition pruning

PartitionedTableScan supports partition pruning in three stages, the same as Append. For details, see Partition pruning.

Performance benchmark

Note: The following data is from a development environment and is for reference only. Actual performance varies based on your configuration and workload. The test isolates the operator as the only variable between Append and PartitionedTableScan.

Test setup:

-- Create the test table
CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY HASH(a) PARTITIONS 16;

-- Benchmark queries
pgbench -i --scale=10
pgbench -c 64 -j 64 -n -T60

-- Append
EXPLAIN SELECT * FROM prt1 WHERE b = 10;

-- PartitionedTableScan
EXPLAIN SELECT /*+PARTEDSCAN(prt1) */ * FROM prt1 WHERE b = 10;

Planning time

Number of partitions Append PartitionedTableScan
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

Memory usage

Number of partitions 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

Queries per second (QPS)

Number of partitions Append PartitionedTableScan
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

The performance gap between Append and PartitionedTableScan widens as partition count increases. For partitioned tables with high partition counts and slow planning times, use PartitionedTableScan to reduce planning overhead.

What's next