From 19f0d2b3224ca82835f5fc234e53e58f4ed0b7f4 Mon Sep 17 00:00:00 2001 From: William Bernbaum Date: Wed, 26 Aug 2026 08:05:01 -0700 Subject: [PATCH v1 03/16] Give eager deduplication its own usefulness threshold --- doc/src/sgml/config.sgml | 23 ++++++++++++++- src/backend/optimizer/path/allpaths.c | 1 + src/backend/optimizer/util/relnode.c | 29 +++++++++++++++---- src/backend/utils/misc/guc_parameters.dat | 9 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/nodes/pathnodes.h | 3 +- src/include/optimizer/paths.h | 1 + 7 files changed, 60 insertions(+), 7 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 0165eb9ec02..cbac2942abb 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -6553,7 +6553,28 @@ ANY num_sync ( for the case + where it does not. The default is 8. + + + + + + min_eager_distinct_group_size (floating point) + + min_eager_distinct_group_size configuration parameter + + + + + Sets the minimum average group size required to consider pushing a + deduplication below a join, which is what eager aggregation does for a + query that discards duplicate rows without computing any aggregates. + The threshold is lower than + because a + deduplication does not carry transition states. The default is + 2. diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 8cc702cface..a841a5ec45a 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -82,6 +82,7 @@ bool enable_geqo = false; /* just in case GUC doesn't set it */ bool enable_eager_aggregate = true; int geqo_threshold; double min_eager_agg_group_size; +double min_eager_distinct_group_size; int min_parallel_table_scan_size; int min_parallel_index_scan_size; diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 2d7b8f85fdf..6df6f2fc4d7 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -103,6 +103,7 @@ static bool init_grouping_targets(PlannerInfo *root, RelOptInfo *rel, static bool is_var_in_aggref_only(PlannerInfo *root, Var *var); static bool is_var_needed_by_join(PlannerInfo *root, Var *var, RelOptInfo *rel); static Index get_expression_sortgroupref(PlannerInfo *root, Expr *expr); +static double min_useful_group_size(PlannerInfo *root); /* @@ -2701,6 +2702,24 @@ build_child_join_reltarget(PlannerInfo *root, childrel->reltarget->width = parentrel->reltarget->width; } +/* + * min_useful_group_size + * The average group size at which pushing down starts to be worthwhile. + * + * A deduplication is judged separately from a partial aggregate. Both shrink + * the joins above them, but a deduplication only builds the groups, while a + * partial aggregate also calls a transition function for every row and holds + * state for every group. + */ +static double +min_useful_group_size(PlannerInfo *root) +{ + if (root->agg_clause_list == NIL) + return min_eager_distinct_group_size; + + return min_eager_agg_group_size; +} + /* * create_rel_agg_info * Create the RelAggInfo structure for the given relation if it can produce @@ -2758,11 +2777,11 @@ create_rel_agg_info(PlannerInfo *root, RelOptInfo *rel, /* * The grouped paths for the given relation are considered useful - * iff the average group size is no less than - * min_eager_agg_group_size. + * iff the average group size is no less than the applicable + * minimum. */ agg_info->agg_useful = - (rel->rows / agg_info->grouped_rows) >= min_eager_agg_group_size; + (rel->rows / agg_info->grouped_rows) >= min_useful_group_size(root); } return agg_info; @@ -2824,10 +2843,10 @@ create_rel_agg_info(PlannerInfo *root, RelOptInfo *rel, /* * The grouped paths for the given relation are considered useful iff - * the average group size is no less than min_eager_agg_group_size. + * the average group size is no less than the applicable minimum. */ result->agg_useful = - (rel->rows / result->grouped_rows) >= min_eager_agg_group_size; + (rel->rows / result->grouped_rows) >= min_useful_group_size(root); } return result; diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 3c5e16ad1e7..41b1a049a11 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2241,6 +2241,15 @@ max => 'DBL_MAX', }, +{ name => 'min_eager_distinct_group_size', type => 'real', context => 'PGC_USERSET', group => 'QUERY_TUNING_COST', + short_desc => 'Sets the minimum average group size required to consider applying eager deduplication.', + flags => 'GUC_EXPLAIN', + variable => 'min_eager_distinct_group_size', + boot_val => '2.0', + min => '0.0', + max => 'DBL_MAX', +}, + { name => 'min_parallel_index_scan_size', type => 'int', context => 'PGC_USERSET', group => 'QUERY_TUNING_COST', short_desc => 'Sets the minimum amount of index data for a parallel scan.', long_desc => 'If the planner estimates that it will read a number of index pages too small to reach this limit, a parallel scan will not be considered.', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index e759f06b50f..ece05204022 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -466,6 +466,7 @@ #min_parallel_index_scan_size = 512kB #effective_cache_size = 4GB #min_eager_agg_group_size = 8.0 +#min_eager_distinct_group_size = 2.0 #jit_above_cost = 100000 # perform JIT compilation if available # and query more expensive than this; diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index b0b210bd07e..158d689c377 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1282,7 +1282,8 @@ typedef struct RelOptInfo * * "agg_useful" is a flag to indicate whether the grouped paths are considered * useful. It is set true if the average partial group size is no less than - * min_eager_agg_group_size, suggesting a significant row count reduction. + * min_eager_agg_group_size (or min_eager_distinct_group_size when there are no + * aggregates), suggesting a significant row count reduction. */ typedef struct RelAggInfo { diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 17f2099ec3b..49e21fcb56c 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -24,6 +24,7 @@ extern PGDLLIMPORT bool enable_geqo; extern PGDLLIMPORT bool enable_eager_aggregate; extern PGDLLIMPORT int geqo_threshold; extern PGDLLIMPORT double min_eager_agg_group_size; +extern PGDLLIMPORT double min_eager_distinct_group_size; extern PGDLLIMPORT int min_parallel_table_scan_size; extern PGDLLIMPORT int min_parallel_index_scan_size; extern PGDLLIMPORT bool enable_group_by_reordering;