Hi,

Upon reviewing [1], I noticed an inconsistency: within the add_paths_to_append_rel function, Postgres only constructs startup_subpaths when the rel->consider_startup flag is set to true. However, the generate_ordered_append_paths function generates these paths regardless of this flag.

After examining the code, I found no scenario where a startup-optimal path would be necessary if consider_startup is false. Impact on the planning time might be noticeable in partitioned cases. Therefore, does it make sense to include startup paths only when it is necessary?

I created a simple patch to address this issue, and it has successfully passed all regression tests. If I overlooked something, it would be beneficial to add a regression test demonstrating the necessity of startup paths regardless of declared limits.

Anyway, it will be beneficial to discuss this logic in the mailing list.

[1] https://www.postgresql.org/message-id/flat/25d6a2cd161673d51373b7e07e6d9dd6%40postgrespro.ru

--
regards, Andrei Lepikhov
From 90c001d767d092784289c4853ae9ef7ecd46f391 Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepi...@gmail.com>
Date: Wed, 23 Apr 2025 14:17:22 +0200
Subject: [PATCH] Consider startup paths in MergeAppend only if fractional
 result may take place

---
 src/backend/optimizer/path/allpaths.c | 72 ++++++++++++++++-----------
 1 file changed, 43 insertions(+), 29 deletions(-)

diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905250b3325..7e8a8d789b3 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1849,17 +1849,18 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 		for (int i = first_index; i != end_index; i += direction)
 		{
 			RelOptInfo *childrel = list_nth_node(RelOptInfo, live_childrels, i);
-			Path	   *cheapest_startup,
+			Path	   *cheapest_startup = NULL,
 					   *cheapest_total,
 					   *cheapest_fractional = NULL;
 
 			/* Locate the right paths, if they are available. */
-			cheapest_startup =
-				get_cheapest_path_for_pathkeys(childrel->pathlist,
-											   pathkeys,
-											   NULL,
-											   STARTUP_COST,
-											   false);
+			if (rel->consider_startup)
+				cheapest_startup =
+					get_cheapest_path_for_pathkeys(childrel->pathlist,
+												   pathkeys,
+												   NULL,
+												   STARTUP_COST,
+												   false);
 			cheapest_total =
 				get_cheapest_path_for_pathkeys(childrel->pathlist,
 											   pathkeys,
@@ -1871,10 +1872,15 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 			 * If we can't find any paths with the right order just use the
 			 * cheapest-total path; we'll have to sort it later.
 			 */
-			if (cheapest_startup == NULL || cheapest_total == NULL)
+			if (cheapest_total == NULL)
 			{
-				cheapest_startup = cheapest_total =
-					childrel->cheapest_total_path;
+				Assert(cheapest_startup == NULL);
+
+				cheapest_total = childrel->cheapest_total_path;
+
+				if (rel->consider_startup)
+					cheapest_startup = cheapest_total;
+
 				/* Assert we do have an unparameterized path for this child */
 				Assert(cheapest_total->param_info == NULL);
 			}
@@ -1932,10 +1938,12 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 				 * just a single subpath (and hence aren't doing anything
 				 * useful).
 				 */
-				cheapest_startup = get_singleton_append_subpath(cheapest_startup);
+				if (cheapest_startup)
+				{
+					cheapest_startup = get_singleton_append_subpath(cheapest_startup);
+					startup_subpaths = lappend(startup_subpaths, cheapest_startup);
+				}
 				cheapest_total = get_singleton_append_subpath(cheapest_total);
-
-				startup_subpaths = lappend(startup_subpaths, cheapest_startup);
 				total_subpaths = lappend(total_subpaths, cheapest_total);
 
 				if (cheapest_fractional)
@@ -1950,8 +1958,10 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 				 * Otherwise, rely on accumulate_append_subpath to collect the
 				 * child paths for the MergeAppend.
 				 */
-				accumulate_append_subpath(cheapest_startup,
-										  &startup_subpaths, NULL);
+				if (cheapest_startup)
+					accumulate_append_subpath(cheapest_startup,
+											  &startup_subpaths, NULL);
+
 				accumulate_append_subpath(cheapest_total,
 										  &total_subpaths, NULL);
 
@@ -1965,15 +1975,16 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 		if (match_partition_order)
 		{
 			/* We only need Append */
-			add_path(rel, (Path *) create_append_path(root,
-													  rel,
-													  startup_subpaths,
-													  NIL,
-													  pathkeys,
-													  NULL,
-													  0,
-													  false,
-													  -1));
+			if (startup_subpaths)
+				add_path(rel, (Path *) create_append_path(root,
+														  rel,
+														  startup_subpaths,
+														  NIL,
+														  pathkeys,
+														  NULL,
+														  0,
+														  false,
+														  -1));
 			if (startup_neq_total)
 				add_path(rel, (Path *) create_append_path(root,
 														  rel,
@@ -1999,11 +2010,12 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 		else
 		{
 			/* We need MergeAppend */
-			add_path(rel, (Path *) create_merge_append_path(root,
-															rel,
-															startup_subpaths,
-															pathkeys,
-															NULL));
+			if (startup_subpaths)
+				add_path(rel, (Path *) create_merge_append_path(root,
+																rel,
+																startup_subpaths,
+																pathkeys,
+																NULL));
 			if (startup_neq_total)
 				add_path(rel, (Path *) create_merge_append_path(root,
 																rel,
@@ -2018,6 +2030,8 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 																pathkeys,
 																NULL));
 		}
+
+		Assert(!(!rel->consider_startup && startup_subpaths != NIL));
 	}
 }
 
-- 
2.39.5

Reply via email to