From d3a5b3116653582a2f90c750d54869709c012585 Mon Sep 17 00:00:00 2001 From: Ilia Evdokimov 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