Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 8ee9c25

Browse files
committed
Simplify partial path generation in GROUP BY/ORDER BY
Here we consolidate the generation of partial sort and partial incremental sort paths in a similar way to what was done in 4a29eab. Since the cost penalty for incremental sort was removed by that commit, there's no point in creating a sort path on the cheapest partial path if an incremental sort could be done instead. This has the added benefit of reducing the amount of code required to build these paths. Author: Richard Guo Reviewed-by: Etsuro Fujita, Shubham Khanna, David Rowley Discussion: https://postgr.es/m/CAMbWs49PaKxBZU9cN7k3DKB7id+YfGfOfS9H_Fo5tkqPMt=fDg@mail.gmail.com
1 parent 7b745d8 commit 8ee9c25

File tree

3 files changed

+156
-124
lines changed

3 files changed

+156
-124
lines changed

src/backend/optimizer/plan/planner.c

+77-124
Original file line numberDiff line numberDiff line change
@@ -5102,8 +5102,9 @@ create_ordered_paths(PlannerInfo *root,
51025102
* have generated order-preserving Gather Merge plans which can be used
51035103
* without sorting if they happen to match the sort_pathkeys, and the loop
51045104
* above will have handled those as well. However, there's one more
5105-
* possibility: it may make sense to sort the cheapest partial path
5106-
* according to the required output order and then use Gather Merge.
5105+
* possibility: it may make sense to sort the cheapest partial path or
5106+
* incrementally sort any partial path that is partially sorted according
5107+
* to the required output order and then use Gather Merge.
51075108
*/
51085109
if (ordered_rel->consider_parallel && root->sort_pathkeys != NIL &&
51095110
input_rel->partial_pathlist != NIL)
@@ -5112,97 +5113,65 @@ create_ordered_paths(PlannerInfo *root,
51125113

51135114
cheapest_partial_path = linitial(input_rel->partial_pathlist);
51145115

5115-
/*
5116-
* If cheapest partial path doesn't need a sort, this is redundant
5117-
* with what's already been tried.
5118-
*/
5119-
if (!pathkeys_contained_in(root->sort_pathkeys,
5120-
cheapest_partial_path->pathkeys))
5116+
foreach(lc, input_rel->partial_pathlist)
51215117
{
5122-
Path *path;
5118+
Path *input_path = (Path *) lfirst(lc);
5119+
Path *sorted_path;
5120+
bool is_sorted;
5121+
int presorted_keys;
51235122
double total_groups;
51245123

5125-
path = (Path *) create_sort_path(root,
5126-
ordered_rel,
5127-
cheapest_partial_path,
5128-
root->sort_pathkeys,
5129-
limit_tuples);
5130-
5131-
total_groups = cheapest_partial_path->rows *
5132-
cheapest_partial_path->parallel_workers;
5133-
path = (Path *)
5134-
create_gather_merge_path(root, ordered_rel,
5135-
path,
5136-
path->pathtarget,
5137-
root->sort_pathkeys, NULL,
5138-
&total_groups);
5139-
5140-
/* Add projection step if needed */
5141-
if (path->pathtarget != target)
5142-
path = apply_projection_to_path(root, ordered_rel,
5143-
path, target);
5144-
5145-
add_path(ordered_rel, path);
5146-
}
5147-
5148-
/*
5149-
* Consider incremental sort with a gather merge on partial paths.
5150-
*
5151-
* We can also skip the entire loop when we only have a single-item
5152-
* sort_pathkeys because then we can't possibly have a presorted
5153-
* prefix of the list without having the list be fully sorted.
5154-
*/
5155-
if (enable_incremental_sort && list_length(root->sort_pathkeys) > 1)
5156-
{
5157-
foreach(lc, input_rel->partial_pathlist)
5158-
{
5159-
Path *input_path = (Path *) lfirst(lc);
5160-
Path *sorted_path;
5161-
bool is_sorted;
5162-
int presorted_keys;
5163-
double total_groups;
5164-
5165-
/*
5166-
* We don't care if this is the cheapest partial path - we
5167-
* can't simply skip it, because it may be partially sorted in
5168-
* which case we want to consider adding incremental sort
5169-
* (instead of full sort, which is what happens above).
5170-
*/
5171-
5172-
is_sorted = pathkeys_count_contained_in(root->sort_pathkeys,
5173-
input_path->pathkeys,
5174-
&presorted_keys);
5124+
is_sorted = pathkeys_count_contained_in(root->sort_pathkeys,
5125+
input_path->pathkeys,
5126+
&presorted_keys);
51755127

5176-
/* No point in adding incremental sort on fully sorted paths. */
5177-
if (is_sorted)
5178-
continue;
5128+
if (is_sorted)
5129+
continue;
51795130

5180-
if (presorted_keys == 0)
5181-
continue;
5131+
/*
5132+
* Try at least sorting the cheapest path and also try
5133+
* incrementally sorting any path which is partially sorted
5134+
* already (no need to deal with paths which have presorted keys
5135+
* when incremental sort is disabled unless it's the cheapest
5136+
* partial path).
5137+
*/
5138+
if (input_path != cheapest_partial_path &&
5139+
(presorted_keys == 0 || !enable_incremental_sort))
5140+
continue;
51825141

5183-
/* Since we have presorted keys, consider incremental sort. */
5142+
/*
5143+
* We've no need to consider both a sort and incremental sort.
5144+
* We'll just do a sort if there are no presorted keys and an
5145+
* incremental sort when there are presorted keys.
5146+
*/
5147+
if (presorted_keys == 0 || !enable_incremental_sort)
5148+
sorted_path = (Path *) create_sort_path(root,
5149+
ordered_rel,
5150+
input_path,
5151+
root->sort_pathkeys,
5152+
limit_tuples);
5153+
else
51845154
sorted_path = (Path *) create_incremental_sort_path(root,
51855155
ordered_rel,
51865156
input_path,
51875157
root->sort_pathkeys,
51885158
presorted_keys,
51895159
limit_tuples);
5190-
total_groups = input_path->rows *
5191-
input_path->parallel_workers;
5192-
sorted_path = (Path *)
5193-
create_gather_merge_path(root, ordered_rel,
5194-
sorted_path,
5195-
sorted_path->pathtarget,
5196-
root->sort_pathkeys, NULL,
5197-
&total_groups);
5198-
5199-
/* Add projection step if needed */
5200-
if (sorted_path->pathtarget != target)
5201-
sorted_path = apply_projection_to_path(root, ordered_rel,
5202-
sorted_path, target);
5203-
5204-
add_path(ordered_rel, sorted_path);
5205-
}
5160+
total_groups = input_path->rows *
5161+
input_path->parallel_workers;
5162+
sorted_path = (Path *)
5163+
create_gather_merge_path(root, ordered_rel,
5164+
sorted_path,
5165+
sorted_path->pathtarget,
5166+
root->sort_pathkeys, NULL,
5167+
&total_groups);
5168+
5169+
/* Add projection step if needed */
5170+
if (sorted_path->pathtarget != target)
5171+
sorted_path = apply_projection_to_path(root, ordered_rel,
5172+
sorted_path, target);
5173+
5174+
add_path(ordered_rel, sorted_path);
52065175
}
52075176
}
52085177

@@ -7322,44 +7291,9 @@ gather_grouping_paths(PlannerInfo *root, RelOptInfo *rel)
73227291
/* Try Gather for unordered paths and Gather Merge for ordered ones. */
73237292
generate_useful_gather_paths(root, rel, true);
73247293

7325-
/* Try cheapest partial path + explicit Sort + Gather Merge. */
73267294
cheapest_partial_path = linitial(rel->partial_pathlist);
7327-
if (!pathkeys_contained_in(root->group_pathkeys,
7328-
cheapest_partial_path->pathkeys))
7329-
{
7330-
Path *path;
7331-
double total_groups;
7332-
7333-
total_groups =
7334-
cheapest_partial_path->rows * cheapest_partial_path->parallel_workers;
7335-
path = (Path *) create_sort_path(root, rel, cheapest_partial_path,
7336-
root->group_pathkeys,
7337-
-1.0);
7338-
path = (Path *)
7339-
create_gather_merge_path(root,
7340-
rel,
7341-
path,
7342-
rel->reltarget,
7343-
root->group_pathkeys,
7344-
NULL,
7345-
&total_groups);
73467295

7347-
add_path(rel, path);
7348-
}
7349-
7350-
/*
7351-
* Consider incremental sort on all partial paths, if enabled.
7352-
*
7353-
* We can also skip the entire loop when we only have a single-item
7354-
* group_pathkeys because then we can't possibly have a presorted prefix
7355-
* of the list without having the list be fully sorted.
7356-
*
7357-
* XXX Shouldn't this also consider the group-key-reordering?
7358-
*/
7359-
if (!enable_incremental_sort || list_length(root->group_pathkeys) == 1)
7360-
return;
7361-
7362-
/* also consider incremental sort on partial paths, if enabled */
7296+
/* XXX Shouldn't this also consider the group-key-reordering? */
73637297
foreach(lc, rel->partial_pathlist)
73647298
{
73657299
Path *path = (Path *) lfirst(lc);
@@ -7374,15 +7308,34 @@ gather_grouping_paths(PlannerInfo *root, RelOptInfo *rel)
73747308
if (is_sorted)
73757309
continue;
73767310

7377-
if (presorted_keys == 0)
7311+
/*
7312+
* Try at least sorting the cheapest path and also try incrementally
7313+
* sorting any path which is partially sorted already (no need to deal
7314+
* with paths which have presorted keys when incremental sort is
7315+
* disabled unless it's the cheapest input path).
7316+
*/
7317+
if (path != cheapest_partial_path &&
7318+
(presorted_keys == 0 || !enable_incremental_sort))
73787319
continue;
73797320

7380-
path = (Path *) create_incremental_sort_path(root,
7381-
rel,
7382-
path,
7383-
root->group_pathkeys,
7384-
presorted_keys,
7385-
-1.0);
7321+
total_groups = path->rows * path->parallel_workers;
7322+
7323+
/*
7324+
* We've no need to consider both a sort and incremental sort. We'll
7325+
* just do a sort if there are no presorted keys and an incremental
7326+
* sort when there are presorted keys.
7327+
*/
7328+
if (presorted_keys == 0 || !enable_incremental_sort)
7329+
path = (Path *) create_sort_path(root, rel, path,
7330+
root->group_pathkeys,
7331+
-1.0);
7332+
else
7333+
path = (Path *) create_incremental_sort_path(root,
7334+
rel,
7335+
path,
7336+
root->group_pathkeys,
7337+
presorted_keys,
7338+
-1.0);
73867339

73877340
path = (Path *)
73887341
create_gather_merge_path(root,

src/test/regress/expected/select_parallel.out

+53
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,59 @@ select string4 from tenk1 order by string4 limit 5;
937937

938938
reset parallel_leader_participation;
939939
reset max_parallel_workers;
940+
create function parallel_safe_volatile(a int) returns int as
941+
$$ begin return a; end; $$ parallel safe volatile language plpgsql;
942+
-- Test gather merge atop of a sort of a partial path
943+
explain (costs off)
944+
select * from tenk1 where four = 2
945+
order by four, hundred, parallel_safe_volatile(thousand);
946+
QUERY PLAN
947+
---------------------------------------------------------------
948+
Gather Merge
949+
Workers Planned: 4
950+
-> Sort
951+
Sort Key: hundred, (parallel_safe_volatile(thousand))
952+
-> Parallel Seq Scan on tenk1
953+
Filter: (four = 2)
954+
(6 rows)
955+
956+
-- Test gather merge atop of an incremental sort a of partial path
957+
set min_parallel_index_scan_size = 0;
958+
set enable_seqscan = off;
959+
explain (costs off)
960+
select * from tenk1 where four = 2
961+
order by four, hundred, parallel_safe_volatile(thousand);
962+
QUERY PLAN
963+
---------------------------------------------------------------
964+
Gather Merge
965+
Workers Planned: 4
966+
-> Incremental Sort
967+
Sort Key: hundred, (parallel_safe_volatile(thousand))
968+
Presorted Key: hundred
969+
-> Parallel Index Scan using tenk1_hundred on tenk1
970+
Filter: (four = 2)
971+
(7 rows)
972+
973+
reset min_parallel_index_scan_size;
974+
reset enable_seqscan;
975+
-- Test GROUP BY with a gather merge path atop of a sort of a partial path
976+
explain (costs off)
977+
select count(*) from tenk1
978+
group by twenty, parallel_safe_volatile(two);
979+
QUERY PLAN
980+
--------------------------------------------------------------------
981+
Finalize GroupAggregate
982+
Group Key: twenty, (parallel_safe_volatile(two))
983+
-> Gather Merge
984+
Workers Planned: 4
985+
-> Sort
986+
Sort Key: twenty, (parallel_safe_volatile(two))
987+
-> Partial HashAggregate
988+
Group Key: twenty, parallel_safe_volatile(two)
989+
-> Parallel Seq Scan on tenk1
990+
(9 rows)
991+
992+
drop function parallel_safe_volatile(int);
940993
SAVEPOINT settings;
941994
SET LOCAL debug_parallel_query = 1;
942995
explain (costs off)

src/test/regress/sql/select_parallel.sql

+26
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,32 @@ select string4 from tenk1 order by string4 limit 5;
343343
reset parallel_leader_participation;
344344
reset max_parallel_workers;
345345

346+
create function parallel_safe_volatile(a int) returns int as
347+
$$ begin return a; end; $$ parallel safe volatile language plpgsql;
348+
349+
-- Test gather merge atop of a sort of a partial path
350+
explain (costs off)
351+
select * from tenk1 where four = 2
352+
order by four, hundred, parallel_safe_volatile(thousand);
353+
354+
-- Test gather merge atop of an incremental sort a of partial path
355+
set min_parallel_index_scan_size = 0;
356+
set enable_seqscan = off;
357+
358+
explain (costs off)
359+
select * from tenk1 where four = 2
360+
order by four, hundred, parallel_safe_volatile(thousand);
361+
362+
reset min_parallel_index_scan_size;
363+
reset enable_seqscan;
364+
365+
-- Test GROUP BY with a gather merge path atop of a sort of a partial path
366+
explain (costs off)
367+
select count(*) from tenk1
368+
group by twenty, parallel_safe_volatile(two);
369+
370+
drop function parallel_safe_volatile(int);
371+
346372
SAVEPOINT settings;
347373
SET LOCAL debug_parallel_query = 1;
348374
explain (costs off)

0 commit comments

Comments
 (0)