Skip to content

Commit b027861

Browse files
committed
Tune costs of nodeCustomTempScan: use analogy with Append.
1 parent 7e13f7d commit b027861

File tree

3 files changed

+133
-117
lines changed

3 files changed

+133
-117
lines changed

contrib/tempscan/expected/basic.out

Lines changed: 55 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,15 @@ SELECT count(*) FROM parallel_test;
2828
-> Parallel Seq Scan on parallel_test
2929
(5 rows)
3030

31-
-- Should also utilise parallel workers like scanning of a plain table
31+
-- Do not use parallel scan of temporary table so far. As a result, shouldn't
32+
-- find nodeCustomTempScan here
3233
EXPLAIN (COSTS OFF)
3334
SELECT count(*) FROM parallel_test_tmp;
34-
QUERY PLAN
35-
----------------------------------------------------------------
36-
Finalize Aggregate
37-
-> Gather
38-
Workers Planned: 1
39-
-> Partial Aggregate
40-
-> Custom Scan (nodeCustomTempScan)
41-
-> Parallel Seq Scan on parallel_test_tmp
42-
(6 rows)
35+
QUERY PLAN
36+
-------------------------------------
37+
Aggregate
38+
-> Seq Scan on parallel_test_tmp
39+
(2 rows)
4340

4441
-- Want to see here partial aggregate over parallel join
4542
EXPLAIN (COSTS OFF)
@@ -59,18 +56,18 @@ SELECT count(*) FROM parallel_test t1 NATURAL JOIN parallel_test t2;
5956

6057
EXPLAIN (COSTS OFF)
6158
SELECT count(*) FROM parallel_test_tmp t1 NATURAL JOIN parallel_test t2;
62-
QUERY PLAN
63-
-------------------------------------------------------------------------
59+
QUERY PLAN
60+
----------------------------------------------------------------------
6461
Finalize Aggregate
6562
-> Gather
6663
Workers Planned: 1
6764
-> Partial Aggregate
68-
-> Parallel Hash Join
69-
Hash Cond: (t1.x = t2.x)
70-
-> Custom Scan (nodeCustomTempScan)
71-
-> Parallel Seq Scan on parallel_test_tmp t1
72-
-> Parallel Hash
73-
-> Parallel Seq Scan on parallel_test t2
65+
-> Hash Join
66+
Hash Cond: (t2.x = t1.x)
67+
-> Parallel Seq Scan on parallel_test t2
68+
-> Hash
69+
-> Custom Scan (nodeCustomTempScan)
70+
-> Seq Scan on parallel_test_tmp t1
7471
(10 rows)
7572

7673
-- Just see how merge join manages custom parallel scan path
@@ -95,100 +92,81 @@ SELECT count(*) FROM parallel_test t1 NATURAL JOIN parallel_test t2;
9592

9693
EXPLAIN (COSTS OFF)
9794
SELECT count(*) FROM parallel_test_tmp t1 NATURAL JOIN parallel_test t2;
98-
QUERY PLAN
99-
-------------------------------------------------------------------------------
95+
QUERY PLAN
96+
----------------------------------------------------------------------
10097
Finalize Aggregate
10198
-> Gather
10299
Workers Planned: 1
103100
-> Partial Aggregate
104101
-> Merge Join
105-
Merge Cond: (t1.x = t2.x)
102+
Merge Cond: (t2.x = t1.x)
103+
-> Sort
104+
Sort Key: t2.x
105+
-> Parallel Seq Scan on parallel_test t2
106106
-> Sort
107107
Sort Key: t1.x
108108
-> Custom Scan (nodeCustomTempScan)
109-
-> Parallel Seq Scan on parallel_test_tmp t1
110-
-> Sort
111-
Sort Key: t2.x
112-
-> Seq Scan on parallel_test t2
109+
-> Seq Scan on parallel_test_tmp t1
113110
(13 rows)
114111

115112
RESET enable_hashjoin;
116113
-- Increase table size and see how indexes work
117114
ALTER TABLE parallel_test ADD COLUMN y text DEFAULT 'none';
118115
INSERT INTO parallel_test (x,y) SELECT x, 'data' || x AS y FROM generate_series(1,10000) AS x;
119-
CREATE INDEX ON parallel_test (x);
120-
ANALYZE parallel_test;
116+
CREATE INDEX ON parallel_test_tmp (x);
117+
VACUUM ANALYZE;
118+
-- Use IndexScan on temporary table
121119
EXPLAIN (COSTS OFF)
122-
SELECT count(*) FROM parallel_test t1 NATURAL JOIN parallel_test t2
120+
SELECT count(*) FROM parallel_test t1 NATURAL JOIN parallel_test_tmp t2
123121
WHERE t1.x < 10;
124-
QUERY PLAN
125-
-------------------------------------------------------------------------------------
122+
QUERY PLAN
123+
-----------------------------------------------------------------------------------------------
126124
Aggregate
127125
-> Gather
128-
Workers Planned: 1
126+
Workers Planned: 3
129127
-> Nested Loop
130-
-> Parallel Index Scan using parallel_test_x_idx on parallel_test t1
131-
Index Cond: (x < 10)
132-
-> Index Scan using parallel_test_x_idx on parallel_test t2
133-
Index Cond: (x = t1.x)
134-
Filter: (y = t1.y)
128+
-> Parallel Seq Scan on parallel_test t1
129+
Filter: (x < 10)
130+
-> Custom Scan (nodeCustomTempScan)
131+
-> Index Only Scan using parallel_test_tmp_x_idx on parallel_test_tmp t2
132+
Index Cond: (x = t1.x)
135133
(9 rows)
136134

137-
EXPLAIN (COSTS OFF)
138-
SELECT count(*) FROM parallel_test t1 NATURAL JOIN parallel_test_tmp t2
139-
WHERE t1.x < 10;
140-
QUERY PLAN
141-
---------------------------------------------------------------------------------
142-
Aggregate
143-
-> Gather
144-
Workers Planned: 1
145-
-> Merge Join
146-
Merge Cond: (t2.x = t1.x)
147-
-> Sort
148-
Sort Key: t2.x
149-
-> Custom Scan (nodeCustomTempScan)
150-
-> Parallel Seq Scan on parallel_test_tmp t2
151-
-> Index Only Scan using parallel_test_x_idx on parallel_test t1
152-
Index Cond: (x < 10)
153-
(11 rows)
154-
155135
CREATE TEMP TABLE parallel_test_tmp_2 AS (SELECT * FROM parallel_test);
156136
CREATE INDEX ON parallel_test_tmp_2 (x);
157-
ANALYZE parallel_test_tmp_2;
137+
VACUUM ANALYZE;
138+
-- Can't use parallel workers here: for now temp tables can be used as parallel
139+
-- safe source but not be read concurrently.
158140
EXPLAIN (COSTS OFF)
159141
SELECT count(*) FROM parallel_test_tmp t1 NATURAL JOIN parallel_test_tmp_2 t2
160142
WHERE t2.x < 10;
161-
QUERY PLAN
162-
---------------------------------------------------------------------------------------------
143+
QUERY PLAN
144+
---------------------------------------------------------------------------------------
163145
Aggregate
164-
-> Gather
165-
Workers Planned: 1
166-
-> Merge Join
167-
Merge Cond: (t1.x = t2.x)
168-
-> Sort
169-
Sort Key: t1.x
170-
-> Custom Scan (nodeCustomTempScan)
171-
-> Parallel Seq Scan on parallel_test_tmp t1
172-
-> Index Only Scan using parallel_test_tmp_2_x_idx on parallel_test_tmp_2 t2
173-
Index Cond: (x < 10)
174-
(11 rows)
146+
-> Merge Join
147+
Merge Cond: (t1.x = t2.x)
148+
-> Sort
149+
Sort Key: t1.x
150+
-> Seq Scan on parallel_test_tmp t1
151+
-> Index Only Scan using parallel_test_tmp_2_x_idx on parallel_test_tmp_2 t2
152+
Index Cond: (x < 10)
153+
(8 rows)
175154

176155
EXPLAIN (COSTS OFF)
177156
SELECT count(*) FROM parallel_test_tmp_2 t1 NATURAL JOIN parallel_test_tmp_2 t2
178157
WHERE t1.x < 10;
179-
QUERY PLAN
180-
-------------------------------------------------------------------------------------------------------
158+
QUERY PLAN
159+
----------------------------------------------------------------------------------------
181160
Aggregate
182-
-> Gather
183-
Workers Planned: 1
184-
-> Nested Loop
185-
-> Custom Scan (nodeCustomTempScan)
186-
-> Parallel Index Scan using parallel_test_tmp_2_x_idx on parallel_test_tmp_2 t1
187-
Index Cond: (x < 10)
161+
-> Nested Loop
162+
-> Custom Scan (nodeCustomTempScan)
163+
-> Index Scan using parallel_test_tmp_2_x_idx on parallel_test_tmp_2 t1
164+
Index Cond: (x < 10)
165+
-> Custom Scan (nodeCustomTempScan)
188166
-> Index Scan using parallel_test_tmp_2_x_idx on parallel_test_tmp_2 t2
189167
Index Cond: (x = t1.x)
190168
Filter: (y = t1.y)
191-
(10 rows)
169+
(9 rows)
192170

193171
RESET tempscan.enable;
194172
DROP TABLE parallel_test, parallel_test_tmp;

contrib/tempscan/nodeCustomTempScan.c

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "nodes/extensible.h"
1717
#include "optimizer/clauses.h"
1818
#include "optimizer/cost.h"
19+
#include "optimizer/optimizer.h"
1920
#include "optimizer/pathnode.h"
2021
#include "optimizer/paths.h"
2122
#include "utils/guc.h"
@@ -27,6 +28,9 @@ PG_MODULE_MAGIC;
2728
#define MODULENAME "tempscan"
2829
#define NODENAME "nodeCustomTempScan"
2930

31+
/* By analogy with Append */
32+
#define TEMPSCAN_CPU_COST_MULTIPLIER (0.5)
33+
3034
static Plan *create_partial_tempscan_plan(PlannerInfo *root,
3135
RelOptInfo *rel,
3236
CustomPath *best_path,
@@ -38,6 +42,7 @@ static void BeginTempScan(CustomScanState *node, EState *estate, int eflags);
3842
static TupleTableSlot *ExecTempScan(CustomScanState *node);
3943
static void EndTempScan(CustomScanState *node);
4044
static void ReScanTempScan(CustomScanState *node);
45+
static Size EstimateDSMTempScan(CustomScanState *node, ParallelContext *pcxt);
4146

4247
static CustomPathMethods path_methods =
4348
{
@@ -62,7 +67,7 @@ static CustomExecMethods exec_methods =
6267
.ReScanCustomScan = ReScanTempScan,
6368
.MarkPosCustomScan = NULL,
6469
.RestrPosCustomScan = NULL,
65-
.EstimateDSMCustomScan = NULL,
70+
.EstimateDSMCustomScan = EstimateDSMTempScan,
6671
.InitializeDSMCustomScan = NULL,
6772
.ReInitializeDSMCustomScan = NULL,
6873
.InitializeWorkerCustomScan = NULL,
@@ -93,18 +98,22 @@ create_partial_tempscan_path(PlannerInfo *root, RelOptInfo *rel,
9398
pathnode->parent = rel;
9499
pathnode->pathtarget = rel->reltarget;
95100
pathnode->rows = path->rows; /* Don't use rel->rows! Remember semantics of this field in the parallel case */
96-
97-
/* XXX: Just for now */
98-
pathnode->param_info = NULL;
101+
pathnode->param_info = path->param_info;
99102

100103
pathnode->parallel_safe = true;
101104
pathnode->parallel_aware = false;
102105
pathnode->parallel_workers = path->parallel_workers;
103106

104-
/* DEBUGGING purposes only */
105107
pathnode->startup_cost = path->startup_cost;
106108
pathnode->total_cost = path->total_cost;
107109

110+
/*
111+
* Although TempScan does not do any selection or projection, it's not free;
112+
* add a small per-tuple overhead.
113+
*/
114+
pathnode->total_cost +=
115+
cpu_tuple_cost * TEMPSCAN_CPU_COST_MULTIPLIER * path->rows;
116+
108117
cpath->custom_paths = list_make1(path);
109118
cpath->custom_private = NIL;
110119
cpath->custom_restrictinfo = NIL;
@@ -121,8 +130,7 @@ create_partial_tempscan_plan(PlannerInfo *root, RelOptInfo *rel,
121130
CustomScan *cscan = makeNode(CustomScan);
122131

123132
Assert(list_length(custom_plans) == 1);
124-
Assert(best_path->path.parallel_safe = true &&
125-
best_path->path.parallel_workers > 0);
133+
Assert(best_path->path.parallel_safe = true);
126134

127135

128136
cscan->scan.plan.targetlist = cscan->custom_scan_tlist = tlist;
@@ -205,9 +213,9 @@ static void
205213
try_partial_tempscan(PlannerInfo *root, RelOptInfo *rel, Index rti,
206214
RangeTblEntry *rte)
207215
{
208-
int parallel_workers;
209216
ListCell *lc;
210-
List *partial_pathlist_new = NIL;
217+
List *parallel_safe_lst = NIL;
218+
List *tmplst = rel->pathlist;
211219

212220
/*
213221
* Some extension intercept this hook earlier. Allow it to do a work
@@ -223,46 +231,56 @@ try_partial_tempscan(PlannerInfo *root, RelOptInfo *rel, Index rti,
223231
get_rel_persistence(rte->relid) != RELPERSISTENCE_TEMP)
224232
return;
225233

226-
/* HACK */
227234
if (!is_parallel_safe(root, (Node *) rel->baserestrictinfo) ||
228235
!is_parallel_safe(root, (Node *) rel->reltarget->exprs))
229236
return;
230237

231-
parallel_workers = compute_parallel_worker(rel, rel->pages, -1,
232-
max_parallel_workers_per_gather);
233-
234-
/* If any limit was set to zero, the user doesn't want a parallel scan. */
235-
if (parallel_workers <= 0)
236-
return;
237-
238-
/* Enable parallel paths generation for this relation */
238+
/* Enable parallel safe paths generation for this relation */
239239
Assert(rel->partial_pathlist == NIL);
240240
rel->consider_parallel = true;
241241

242-
/* Add partial sequental scan path. */
243-
add_partial_path(rel, (Path *)
244-
create_seqscan_path(root, rel, NULL, parallel_workers));
242+
/*
243+
* Now we have a problem:
244+
* should generate parallel safe paths. But they will have the same cost as
245+
* previously added non-parallel ones and, being safe, will definitely crowd
246+
* out non-safe ones.
247+
* So, we need a HACK: add new safe paths with cost of custom node.
248+
*/
245249

246-
/* Add there more specific paths too */
250+
rel->pathlist = NIL;
251+
252+
/*
253+
* Build possibly parallel paths other temporary table
254+
*/
255+
add_path(rel, create_seqscan_path(root, rel, NULL, 0));
247256
create_index_paths(root, rel);
248257
create_tidscan_paths(root, rel);
249258

250-
foreach(lc, rel->partial_pathlist)
251-
{
252-
Path *path = lfirst(lc);
259+
/*
260+
* Dangerous zone. But we assume it is strictly local. What about extension
261+
* which could call ours and may have desire to add some partial paths after
262+
* us?
263+
*/
253264

254-
partial_pathlist_new =
255-
lappend(partial_pathlist_new,
256-
(void *) create_partial_tempscan_path(root, rel, path));
257-
}
265+
list_free(rel->partial_pathlist);
266+
rel->partial_pathlist = NIL;
258267

259268
/*
260-
* Dangerous zone. But we assume it is strictly local. What about extension
261-
* which could call ours and add some paths after us?
269+
* Set guard over each parallel_safe path
262270
*/
263-
rel->partial_pathlist = partial_pathlist_new;
271+
parallel_safe_lst = rel->pathlist;
272+
rel->pathlist = tmplst;
273+
foreach(lc, parallel_safe_lst)
274+
{
275+
Path *path = lfirst(lc);
276+
277+
if (!path->parallel_safe)
278+
continue;
279+
280+
add_path(rel, (Path *) create_partial_tempscan_path(root, rel, path));
281+
}
264282

265-
Assert(IsA(linitial(rel->partial_pathlist), CustomPath));
283+
list_free(parallel_safe_lst);
266284
}
267285

268286
void
@@ -287,3 +305,21 @@ _PG_init(void)
287305

288306
MarkGUCPrefixReserved(MODULENAME);
289307
}
308+
309+
/* *****************************************************************************
310+
*
311+
* Parallel transport stuff
312+
*
313+
**************************************************************************** */
314+
315+
/* copy from execParallel.c */
316+
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
317+
318+
static Size
319+
EstimateDSMTempScan(CustomScanState *node, ParallelContext *pcxt)
320+
{
321+
Size size;
322+
323+
size = mul_size(PARALLEL_TUPLE_QUEUE_SIZE, pcxt->nworkers);
324+
return size;
325+
}

0 commit comments

Comments
 (0)