Fix the miss consideration of tuple_fraction during add_paths_to_append_rel
When I am working on "Pushing limit into subqueries of a union" [1]/messages/by-id/11228.1118365833@sss.pgh.pa.us, I
found we already have a great infrastructure to support this. For a query
like
subquery-1 UNION ALL subquery-2 LIMIT 3;
We have considered the root->tuple_fraction when planning the subqueries
without an extra Limit node as an overhead. But the reason it doesn't work
in my real case is flatten_simple_union_all flat the union all subqueries
into append relation and we didn't handle the root->tuple_fraction during
add_paths_to_append_rel.
Given the below query for example:
explain analyze
(select * from tenk1 order by hundred)
union all
(select * from tenk2 order by hundred)
limit 3;
Without the patch: Execution Time: 7.856 ms
with the patch: Execution Time: 0.224 ms
Any suggestion is welcome.
[1]: /messages/by-id/11228.1118365833@sss.pgh.pa.us
--
Best Regards
Andy Fan
Attachments:
v1-0001-Considering-root-tuple_fraction-during-create_app.patchapplication/octet-stream; name=v1-0001-Considering-root-tuple_fraction-during-create_app.patchDownload
From b8f851e8385d98a581fb49ed458d3b68d451158d Mon Sep 17 00:00:00 2001
From: Andy Fan <zhihui.fan1213@gmail.com>
Date: Mon, 10 Apr 2023 15:59:27 +0800
Subject: [PATCH v1] Considering root->tuple_fraction during create_append_path
---
src/backend/optimizer/path/allpaths.c | 10 ++++++++-
src/test/regress/expected/join.out | 29 +++++++++++++++++++++++++++
src/test/regress/sql/join.sql | 9 +++++++++
3 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 244957a2483..aa891157f10 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1330,7 +1330,12 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
RelOptInfo *childrel = lfirst(l);
ListCell *lcp;
Path *cheapest_partial_path = NULL;
+ Path *cheapest_startup_path = NULL;
+ if (childrel->pathlist != NIL && childrel->consider_startup)
+ {
+ cheapest_startup_path = get_cheapest_fractional_path(childrel, root->tuple_fraction);
+ }
/*
* If child has an unparameterized cheapest-total path, add that to
* the unparameterized Append path we are constructing for the parent.
@@ -1339,7 +1344,10 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
* With partitionwise aggregates, the child rel's pathlist may be
* empty, so don't assume that a path exists here.
*/
- if (childrel->pathlist != NIL &&
+ if (cheapest_startup_path && cheapest_startup_path->param_info == NULL)
+ accumulate_append_subpath(cheapest_startup_path,
+ &subpaths, NULL);
+ else if (childrel->pathlist != NIL &&
childrel->cheapest_total_path->param_info == NULL)
accumulate_append_subpath(childrel->cheapest_total_path,
&subpaths, NULL);
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 5d59ed7890f..717ec9551dc 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -7368,3 +7368,32 @@ where exists (select 1 from j3
(13 rows)
drop table j3;
+explain (costs off)
+(select * from tenk1 order by hundred) union all (select * from tenk2 order by hundred)
+limit 3;
+ QUERY PLAN
+-----------------------------------------------------
+ Limit
+ -> Append
+ -> Index Scan using tenk1_hundred on tenk1
+ -> Index Scan using tenk2_hundred on tenk2
+(4 rows)
+
+explain (costs off)
+SELECT * FROM tenk1 t1 join tenk2 t2 using (unique2)
+union all
+SELECT * FROM tenk1 t1 join tenk2 t2 using (unique2) limit 3;
+ QUERY PLAN
+----------------------------------------------------------------
+ Limit
+ -> Append
+ -> Merge Join
+ Merge Cond: (t1.unique2 = t2.unique2)
+ -> Index Scan using tenk1_unique2 on tenk1 t1
+ -> Index Scan using tenk2_unique2 on tenk2 t2
+ -> Merge Join
+ Merge Cond: (t1_1.unique2 = t2_1.unique2)
+ -> Index Scan using tenk1_unique2 on tenk1 t1_1
+ -> Index Scan using tenk2_unique2 on tenk2 t2_1
+(10 rows)
+
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index a630f58b571..42cbd922d0d 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -2666,3 +2666,12 @@ where exists (select 1 from j3
and t1.unique1 < 1;
drop table j3;
+
+explain (costs off)
+(select * from tenk1 order by hundred) union all (select * from tenk2 order by hundred)
+limit 3;
+
+explain (costs off)
+SELECT * FROM tenk1 t1 join tenk2 t2 using (unique2)
+union all
+SELECT * FROM tenk1 t1 join tenk2 t2 using (unique2) limit 3;
--
2.21.0
HI,
On Apr 10, 2023, 16:35 +0800, Andy Fan <zhihui.fan1213@gmail.com>, wrote:
When I am working on "Pushing limit into subqueries of a union" [1], I
found we already have a great infrastructure to support this. For a query
likesubquery-1 UNION ALL subquery-2 LIMIT 3;
We have considered the root->tuple_fraction when planning the subqueries
without an extra Limit node as an overhead. But the reason it doesn't work
in my real case is flatten_simple_union_all flat the union all subqueries
into append relation and we didn't handle the root->tuple_fraction during
add_paths_to_append_rel.Given the below query for example:
explain analyze
(select * from tenk1 order by hundred)
union all
(select * from tenk2 order by hundred)
limit 3;Without the patch: Execution Time: 7.856 ms
with the patch: Execution Time: 0.224 msAny suggestion is welcome.
[1] /messages/by-id/11228.1118365833@sss.pgh.pa.us
--
Best Regards
Andy Fan
There is spare indent at else if.
- if (childrel->pathlist != NIL &&
+ if (cheapest_startup_path && cheapest_startup_path->param_info == NULL)
+ accumulate_append_subpath(cheapest_startup_path,
+ &subpaths, NULL);
+ else if (childrel->pathlist != NIL &&
childrel->cheapest_total_path->param_info == NULL)
accumulate_append_subpath(childrel->cheapest_total_path,
&subpaths, NULL);
Could we also consider tuple_fraction in partial_pathlist for parallel append?
Regards,
Zhang Mingli
On Mon, Apr 10, 2023 at 9:56 PM Zhang Mingli <zmlpostgres@gmail.com> wrote:
There is spare indent at else if.
- if (childrel->pathlist != NIL && + if (cheapest_startup_path && cheapest_startup_path->param_info == NULL) + accumulate_append_subpath(cheapest_startup_path, + &subpaths, NULL); + else if (childrel->pathlist != NIL && childrel->cheapest_total_path->param_info == NULL) accumulate_append_subpath(childrel->cheapest_total_path, &subpaths, NULL);Could we also consider tuple_fraction in partial_pathlist for parallel
append?
Thanks for the suggestion, the v2 has fixed the indent issue and I did
something about parallel append. Besides that, I restrict the changes
happens under bms_equal(rel->relids, root->all_query_rels), which may
make this patch safer.
I have added this into https://commitfest.postgresql.org/43/4270/
--
Best Regards
Andy Fan
Attachments:
v2-0001-Considering-root-tuple_fraction-during-create_app.patchapplication/octet-stream; name=v2-0001-Considering-root-tuple_fraction-during-create_app.patchDownload
From e98e0a0e5be48026f972a188dba39fa061f9f4ab Mon Sep 17 00:00:00 2001
From: Andy Fan <zhihui.fan1213@gmail.com>
Date: Wed, 12 Apr 2023 01:32:04 +0800
Subject: [PATCH v2] Considering root->tuple_fraction during create_append_path
Only apply this optimization when bms_equal(rel->relids,
root->all_query_rels).
---
src/backend/optimizer/path/allpaths.c | 19 +++++++++++++--
src/backend/optimizer/plan/planner.c | 22 ++++++++++++++----
src/backend/optimizer/plan/subselect.c | 2 +-
src/backend/optimizer/prep/prepunion.c | 3 ++-
src/include/optimizer/planner.h | 3 ++-
src/test/regress/expected/join.out | 32 ++++++++++++++++++++++++++
src/test/regress/sql/join.sql | 12 ++++++++++
7 files changed, 83 insertions(+), 10 deletions(-)
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 244957a2483..a9497bfeaf2 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1312,6 +1312,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *pa_nonpartial_subpaths = NIL;
bool partial_subpaths_valid = true;
bool pa_subpaths_valid;
+ bool include_all_rels = false;
List *all_child_pathkeys = NIL;
List *all_child_outers = NIL;
ListCell *l;
@@ -1319,6 +1320,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
/* If appropriate, consider parallel append */
pa_subpaths_valid = enable_parallel_append && rel->consider_parallel;
+ include_all_rels = root && bms_equal(rel->relids, root->all_query_rels);
/*
* For every non-dummy child, remember the cheapest path. Also, identify
@@ -1330,7 +1332,12 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
RelOptInfo *childrel = lfirst(l);
ListCell *lcp;
Path *cheapest_partial_path = NULL;
+ Path *cheapest_startup_path = NULL;
+ if (childrel->pathlist != NIL && childrel->consider_startup && include_all_rels)
+ {
+ cheapest_startup_path = get_cheapest_fractional_path(childrel, root->tuple_fraction, false);
+ }
/*
* If child has an unparameterized cheapest-total path, add that to
* the unparameterized Append path we are constructing for the parent.
@@ -1339,7 +1346,10 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
* With partitionwise aggregates, the child rel's pathlist may be
* empty, so don't assume that a path exists here.
*/
- if (childrel->pathlist != NIL &&
+ if (cheapest_startup_path && cheapest_startup_path->param_info == NULL)
+ accumulate_append_subpath(cheapest_startup_path,
+ &subpaths, NULL);
+ else if (childrel->pathlist != NIL &&
childrel->cheapest_total_path->param_info == NULL)
accumulate_append_subpath(childrel->cheapest_total_path,
&subpaths, NULL);
@@ -1349,7 +1359,12 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
/* Same idea, but for a partial plan. */
if (childrel->partial_pathlist != NIL)
{
- cheapest_partial_path = linitial(childrel->partial_pathlist);
+ if (childrel->consider_startup && include_all_rels)
+ cheapest_partial_path = get_cheapest_fractional_path(childrel,
+ root->tuple_fraction,
+ true);
+ else
+ cheapest_partial_path = linitial(childrel->partial_pathlist);
accumulate_append_subpath(cheapest_partial_path,
&partial_subpaths, NULL);
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index a1873ce26d4..bfcd3519543 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -413,7 +413,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/* Select best Path and turn it into a Plan */
final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
- best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);
+ best_path = get_cheapest_fractional_path(final_rel, tuple_fraction, false);
top_plan = create_plan(root, best_path);
@@ -6272,10 +6272,22 @@ make_sort_input_target(PlannerInfo *root,
* We assume set_cheapest() has been run on the given rel.
*/
Path *
-get_cheapest_fractional_path(RelOptInfo *rel, double tuple_fraction)
+get_cheapest_fractional_path(RelOptInfo *rel, double tuple_fraction, bool is_partial)
{
- Path *best_path = rel->cheapest_total_path;
+ Path *best_path;
ListCell *l;
+ List *pathlist;
+
+ if (is_partial)
+ {
+ best_path = linitial(rel->partial_pathlist);
+ pathlist = rel->partial_pathlist;
+ }
+ else
+ {
+ best_path = rel->cheapest_total_path;
+ pathlist = rel->pathlist;
+ }
/* If all tuples will be retrieved, just return the cheapest-total path */
if (tuple_fraction <= 0.0)
@@ -6285,11 +6297,11 @@ get_cheapest_fractional_path(RelOptInfo *rel, double tuple_fraction)
if (tuple_fraction >= 1.0 && best_path->rows > 0)
tuple_fraction /= best_path->rows;
- foreach(l, rel->pathlist)
+ foreach(l, pathlist)
{
Path *path = (Path *) lfirst(l);
- if (path == rel->cheapest_total_path ||
+ if (path == best_path ||
compare_fractional_path_costs(best_path, path, tuple_fraction) <= 0)
continue;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 052263aea6d..e3224f99f53 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -231,7 +231,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
* seems no reason to postpone doing that.
*/
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
- best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);
+ best_path = get_cheapest_fractional_path(final_rel, tuple_fraction, false);
plan = create_plan(subroot, best_path);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 0c68ec011be..4cc392c7b6c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -283,7 +283,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
* set_subquery_pathlist).
*/
subpath = get_cheapest_fractional_path(final_rel,
- root->tuple_fraction);
+ root->tuple_fraction,
+ false);
/*
* Stick a SubqueryScanPath atop that.
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index fc2e15496dd..76a568b7f69 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -54,7 +54,8 @@ extern bool limit_needed(Query *parse);
extern void mark_partial_aggref(Aggref *agg, AggSplit aggsplit);
extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
- double tuple_fraction);
+ double tuple_fraction,
+ bool is_partial);
extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 5d59ed7890f..4fa63185b95 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -7368,3 +7368,35 @@ where exists (select 1 from j3
(13 rows)
drop table j3;
+explain (costs off)
+(select * from tenk1 order by hundred)
+union all
+(select * from tenk2 order by hundred)
+limit 3;
+ QUERY PLAN
+-----------------------------------------------------
+ Limit
+ -> Append
+ -> Index Scan using tenk1_hundred on tenk1
+ -> Index Scan using tenk2_hundred on tenk2
+(4 rows)
+
+explain (costs off)
+SELECT * FROM tenk1 t1 join tenk2 t2 using (unique2)
+union all
+SELECT * FROM tenk1 t1 join tenk2 t2 using (unique2)
+limit 3;
+ QUERY PLAN
+----------------------------------------------------------------
+ Limit
+ -> Append
+ -> Merge Join
+ Merge Cond: (t1.unique2 = t2.unique2)
+ -> Index Scan using tenk1_unique2 on tenk1 t1
+ -> Index Scan using tenk2_unique2 on tenk2 t2
+ -> Merge Join
+ Merge Cond: (t1_1.unique2 = t2_1.unique2)
+ -> Index Scan using tenk1_unique2 on tenk1 t1_1
+ -> Index Scan using tenk2_unique2 on tenk2 t2_1
+(10 rows)
+
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index a630f58b571..a7e5dbdaa3e 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -2666,3 +2666,15 @@ where exists (select 1 from j3
and t1.unique1 < 1;
drop table j3;
+
+explain (costs off)
+(select * from tenk1 order by hundred)
+union all
+(select * from tenk2 order by hundred)
+limit 3;
+
+explain (costs off)
+SELECT * FROM tenk1 t1 join tenk2 t2 using (unique2)
+union all
+SELECT * FROM tenk1 t1 join tenk2 t2 using (unique2)
+limit 3;
--
2.21.0