Scheduled Acceleration
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
SELECTstatements 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
PartitionedTableScanplan.
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):PartitionedTableScanis never enabled. -
-1:PartitionedTableScanis always enabled, regardless of partition count. -
Greater than
0:PartitionedTableScanactivates automatically when the number of subpartitions exceeds this value. For example, setting it to64enables 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 betweenAppendandPartitionedTableScan.
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.