From d3a5b3116653582a2f90c750d54869709c012585 Mon Sep 17 00:00:00 2001
From: Ilia Evdokimov <ilya.evdokimov@tantorlabs.com>
Date: Wed, 10 Jun 2026 18:35:16 +0300
Subject: [PATCH v1] Show estimated number of groups for IncrementalSort in
 EXPLAIN

IncrementalSort cost heavily depends on the estimated number of groups
with equal presorted key values. A bad estimate can cause the planner
to choose IncrementalSort over Sort even when it would actually be
slower, with no visible indication of why.
---
 src/backend/commands/explain.c          |  5 +++++
 src/backend/optimizer/path/costsize.c   | 12 +++++++++---
 src/backend/optimizer/plan/createplan.c |  4 +++-
 src/backend/optimizer/util/pathnode.c   |  6 ++++--
 src/include/nodes/pathnodes.h           |  1 +
 src/include/nodes/plannodes.h           |  2 ++
 src/include/optimizer/cost.h            |  3 ++-
 7 files changed, 26 insertions(+), 7 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 112c17b0d64..11d2b06bcaa 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3306,11 +3306,16 @@ static void
 show_incremental_sort_info(IncrementalSortState *incrsortstate,
 						   ExplainState *es)
 {
+	IncrementalSort *plan = (IncrementalSort *) incrsortstate->ss.ps.plan;
 	IncrementalSortGroupInfo *fullsortGroupInfo;
 	IncrementalSortGroupInfo *prefixsortGroupInfo;
 
 	fullsortGroupInfo = &incrsortstate->incsort_info.fullsortGroupInfo;
 
+	if (es->costs)
+		ExplainPropertyFloat("Estimated Groups", NULL,
+							 plan->est_input_groups, 0, es);
+
 	if (!es->analyze)
 		return;
 
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1c575e56ff6..080fab3686b 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -2055,7 +2055,8 @@ cost_incremental_sort(Path *path,
 					  int input_disabled_nodes,
 					  Cost input_startup_cost, Cost input_total_cost,
 					  double input_tuples, int width, Cost comparison_cost, int sort_mem,
-					  double limit_tuples)
+					  double limit_tuples,
+					  double *p_input_groups)
 {
 	Cost		startup_cost,
 				run_cost,
@@ -2171,6 +2172,9 @@ cost_incremental_sort(Path *path,
 	 */
 	run_cost += 2.0 * cpu_tuple_cost * input_groups;
 
+	if (p_input_groups)
+		*p_input_groups = input_groups;
+
 	path->rows = input_tuples;
 
 	/*
@@ -2406,7 +2410,8 @@ cost_append(AppendPath *apath, PlannerInfo *root)
 											  subpath->pathtarget->width,
 											  0.0,
 											  work_mem,
-											  apath->limit_tuples);
+											  apath->limit_tuples,
+											  NULL);
 					}
 					else
 					{
@@ -3827,7 +3832,8 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace,
 								  outer_path->pathtarget->width,
 								  0.0,
 								  work_mem,
-								  -1.0);
+								  -1.0,
+								  NULL);
 		}
 		else
 		{
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index de6a183da79..5c5791b4f2f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -2067,6 +2067,7 @@ create_incrementalsort_plan(PlannerInfo *root, IncrementalSortPath *best_path,
 											  best_path->nPresortedCols);
 
 	copy_generic_path_info(&plan->sort.plan, (Path *) best_path);
+	plan->est_input_groups = best_path->est_input_groups;
 
 	return plan;
 }
@@ -5441,7 +5442,8 @@ label_incrementalsort_with_costsize(PlannerInfo *root, IncrementalSort *plan,
 						  lefttree->plan_width,
 						  0.0,
 						  work_mem,
-						  limit_tuples);
+						  limit_tuples,
+						  &plan->est_input_groups);
 	plan->sort.plan.startup_cost = sort_path.startup_cost;
 	plan->sort.plan.total_cost = sort_path.total_cost;
 	plan->sort.plan.plan_rows = lefttree->plan_rows;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 73518c8f870..665e8859a28 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1605,7 +1605,8 @@ create_merge_append_path(PlannerInfo *root,
 									  subpath->pathtarget->width,
 									  0.0,
 									  work_mem,
-									  pathnode->limit_tuples);
+									  pathnode->limit_tuples,
+									  NULL);
 			}
 			else
 			{
@@ -2883,7 +2884,8 @@ create_incremental_sort_path(PlannerInfo *root,
 						  subpath->rows,
 						  subpath->pathtarget->width,
 						  0.0,	/* XXX comparison_cost shouldn't be 0? */
-						  work_mem, limit_tuples);
+						  work_mem, limit_tuples,
+						  &sort->est_input_groups);
 
 	sort->nPresortedCols = presorted_keys;
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 27a2c6815b7..abfc3c9aad6 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -2548,6 +2548,7 @@ typedef struct IncrementalSortPath
 {
 	SortPath	spath;
 	int			nPresortedCols; /* number of presorted columns */
+	double		est_input_groups;	/* estimated number of groups */
 } IncrementalSortPath;
 
 /*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 14a1dfed2b9..49e66965fe6 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -1169,6 +1169,8 @@ typedef struct IncrementalSort
 	Sort		sort;
 	/* number of presorted columns */
 	int			nPresortedCols;
+	/* estimated number of groups (groups with equal presorted keys) */
+	double		est_input_groups;
 } IncrementalSort;
 
 /* ---------------
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index f2fd5d31507..8fa0b0ecad7 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -117,7 +117,8 @@ extern void cost_incremental_sort(Path *path,
 								  int input_disabled_nodes,
 								  Cost input_startup_cost, Cost input_total_cost,
 								  double input_tuples, int width, Cost comparison_cost, int sort_mem,
-								  double limit_tuples);
+								  double limit_tuples,
+								  double *p_input_groups);
 extern void cost_append(AppendPath *apath, PlannerInfo *root);
 extern void cost_merge_append(Path *path, PlannerInfo *root,
 							  List *pathkeys, int n_streams,
-- 
2.34.1

