From 3808d4ed9ed6bf9799b4840e788a84957e971b12 Mon Sep 17 00:00:00 2001 From: sohardforaname Date: Thu, 3 Sep 2026 23:24:31 +0800 Subject: [PATCH] Consider aggregate target cost for top-N sorts make_sort_input_target() postpones an expensive target expression based on its individual cost. This misses target lists containing multiple expressions that are individually cheap but expensive in aggregate. Estimate the expression work avoided for rows discarded by ORDER BY LIMIT, and postpone costful expressions when that exceeds a conservative threshold. Keep projections postponed this way above Gather and Gather Merge so that they are evaluated after the global top-N operation. --- src/backend/optimizer/plan/planner.c | 70 ++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c3c158a253d..6864f714929 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -225,6 +225,7 @@ static RelOptInfo *create_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, PathTarget *target, bool target_parallel_safe, + bool postpone_projection_in_leader, double limit_tuples); static PathTarget *make_group_input_target(PlannerInfo *root, PathTarget *final_target); @@ -243,6 +244,8 @@ static List *make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc, List *tlist); static PathTarget *make_sort_input_target(PlannerInfo *root, PathTarget *final_target, + double input_rows, + bool *postpone_projection_in_leader, bool *have_postponed_srfs); static void adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel, List *targets, List *targets_contain_srfs); @@ -1696,6 +1699,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction, int64 offset_est = 0; int64 count_est = 0; double limit_tuples = -1.0; + bool postpone_projection_in_leader = false; bool have_postponed_srfs = false; PathTarget *final_target; List *final_targets; @@ -1933,6 +1937,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction, { sort_input_target = make_sort_input_target(root, final_target, + current_rel->rows, + &postpone_projection_in_leader, &have_postponed_srfs); sort_input_target_parallel_safe = is_parallel_safe(root, (Node *) sort_input_target->exprs); @@ -2111,6 +2117,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction, current_rel, final_target, final_target_parallel_safe, + postpone_projection_in_leader, have_postponed_srfs ? -1.0 : limit_tuples); /* Fix things up if final_target contains SRFs */ @@ -5560,6 +5567,7 @@ create_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, PathTarget *target, bool target_parallel_safe, + bool postpone_projection_in_leader, double limit_tuples) { Path *cheapest_input_path = input_rel->cheapest_total_path; @@ -5638,8 +5646,17 @@ create_ordered_paths(PlannerInfo *root, * the target to be applied, a projection step is needed. */ if (!equal(sorted_path->pathtarget->exprs, target->exprs)) - sorted_path = apply_projection_to_path(root, ordered_rel, + { + if (postpone_projection_in_leader && + (IsA(sorted_path, GatherPath) || + IsA(sorted_path, GatherMergePath))) + sorted_path = (Path *) + create_projection_path(root, ordered_rel, + sorted_path, target); + else + sorted_path = apply_projection_to_path(root, ordered_rel, sorted_path, target); + } add_path(ordered_rel, sorted_path); } @@ -5719,8 +5736,15 @@ create_ordered_paths(PlannerInfo *root, * from the target to be applied, a projection step is needed. */ if (!equal(sorted_path->pathtarget->exprs, target->exprs)) - sorted_path = apply_projection_to_path(root, ordered_rel, + { + if (postpone_projection_in_leader) + sorted_path = (Path *) + create_projection_path(root, ordered_rel, + sorted_path, target); + else + sorted_path = apply_projection_to_path(root, ordered_rel, sorted_path, target); + } add_path(ordered_rel, sorted_path); } @@ -6647,6 +6671,9 @@ make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc, * computed earlier. * * 'final_target' is the query's final target list (in PathTarget form) + * 'input_rows' is the estimated number of rows before the Sort step + * 'postpone_projection_in_leader' is set if a Top-N cost decision postponed + * expressions that should not be pushed below a Gather or Gather Merge * 'have_postponed_srfs' is an output argument, see below * * The result is the PathTarget to be computed by the plan node immediately @@ -6659,6 +6686,8 @@ make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc, static PathTarget * make_sort_input_target(PlannerInfo *root, PathTarget *final_target, + double input_rows, + bool *postpone_projection_in_leader, bool *have_postponed_srfs) { Query *parse = root->parse; @@ -6666,6 +6695,8 @@ make_sort_input_target(PlannerInfo *root, int ncols; bool *col_is_srf; bool *postpone_col; + Cost *col_cost; + Cost postponable_cost = 0; bool have_srf; bool have_volatile; bool have_expensive; @@ -6679,12 +6710,14 @@ make_sort_input_target(PlannerInfo *root, /* Shouldn't get here unless query has ORDER BY */ Assert(parse->sortClause); - *have_postponed_srfs = false; /* default result */ + *postpone_projection_in_leader = false; + *have_postponed_srfs = false; /* default results */ /* Inspect tlist and collect per-column information */ ncols = list_length(final_target->exprs); col_is_srf = palloc0_array(bool, ncols); postpone_col = palloc0_array(bool, ncols); + col_cost = palloc0_array(Cost, ncols); have_srf = have_volatile = have_expensive = have_srf_sortcols = false; i = 0; @@ -6729,6 +6762,8 @@ make_sort_input_target(PlannerInfo *root, QualCost cost; cost_qual_eval_node(&cost, (Node *) expr, root); + col_cost[i] = cost.per_tuple; + postponable_cost += cost.per_tuple; /* * We arbitrarily define "expensive" as "more than 10X @@ -6754,6 +6789,35 @@ make_sort_input_target(PlannerInfo *root, i++; } + /* + * The fixed per-expression threshold above misses target lists made up of + * several individually cheap expressions when a small LIMIT means that + * very few input rows survive the Sort. In that case, compare the cost + * avoided on discarded rows with a conservative per-output-row threshold. + * + * root->limit_tuples is positive only when LIMIT/OFFSET could be + * estimated and can safely be applied to the scan/join result. Keep the + * historical factor of 10, but scale it by the number of rows expected to + * survive rather than applying it independently to every input row. + */ + if (root->limit_tuples > 0 && + input_rows > root->limit_tuples && + postponable_cost * (input_rows - root->limit_tuples) > + 10 * cpu_operator_cost * root->limit_tuples) + { + i = 0; + foreach(lc, final_target->exprs) + { + /* Zero means a Var, Const, or another cost-free expression. */ + if (col_cost[i] > 0) + postpone_col[i] = true; + i++; + } + + have_expensive = true; + *postpone_projection_in_leader = true; + } + /* * We can postpone SRFs if we have some but none are in sortgroupref cols. */ -- 2.43.0