From 5de889401caa5937873656198e64e6ab29111ca1 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Fri, 17 Jul 2026 23:02:51 +0200 Subject: [PATCH v6 8/9] Add GUCs for limiting join enumaration Replace BLOOM_MAX_BUILD_RELIDS and BLOOM_MAX_BUILD_SETS with user-defined GUCs, to allow easier experimentation. --- src/backend/optimizer/path/costsize.c | 16 ++++++++ src/backend/optimizer/path/joinrels.c | 37 ++++++++----------- src/backend/utils/misc/guc_parameters.dat | 22 +++++++++++ src/backend/utils/misc/postgresql.conf.sample | 2 + src/include/optimizer/cost.h | 2 + 5 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index a29e63e8738..f650c7c3d19 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -184,6 +184,22 @@ double bloom_filter_pushdown_threshold = 0.3; */ int bloom_filter_pushdown_max = 3; +/* + * Maximum number of base relations allowed in an enumerated Bloom-filter build + * side. Bloom filters over larger joins are unlikely to be worthwhile and + * enumerating them would inflate planning time, so we keep this small. This + * bounds the *size* of each candidate build side, which is distinct from + * bloom_filter_pushdown_max that bounds how many interesting filters are + * ultimately kept per probe relation. + */ +int bloom_filter_pushdown_max_build_relids = 3; + +/* + * Overall cap on the number of enumerated build-side relid sets, as a safety + * valve against pathological join graphs. + */ +int bloom_filter_pushdown_max_build_sets = 100; + typedef struct { PlannerInfo *root; diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index c73f3e69a7c..8faba70664b 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -899,23 +899,6 @@ simple_have_relevant_joinclause(PlannerInfo *root, Relids relids1, return false; } -/* - * Maximum number of base relations allowed in an enumerated Bloom-filter - * build side. Bloom filters over larger joins are unlikely to be worthwhile - * and enumerating them would inflate planning time, so we keep this small. - * - * This bounds the *size* of each candidate build side, which is distinct from - * the bloom_filter_pushdown_max GUC that bounds how many interesting filters - * are ultimately kept per probe relation. - */ -#define BLOOM_MAX_BUILD_RELIDS 3 - -/* - * Overall cap on the number of enumerated build-side relid sets, as a safety - * valve against pathological join graphs. - */ -#define BLOOM_MAX_BUILD_SETS 100 - /* * enumerate_bloom_filter_build_relids * Enumerate the legal join relations (and single base relations) that @@ -970,14 +953,15 @@ simple_have_relevant_joinclause(PlannerInfo *root, Relids relids1, List * enumerate_bloom_filter_build_relids(PlannerInfo *root) { - List *levels[BLOOM_MAX_BUILD_RELIDS + 1]; + List **levels; List *result = NIL; int nbaserels = 0; int level; int i; /* bail out if filter pushdown disabled */ - if (!enable_hashjoin_bloom || bloom_filter_pushdown_max <= 0) + if (!enable_hashjoin_bloom || bloom_filter_pushdown_max <= 0 || + bloom_filter_pushdown_max_build_relids <= 0) return NIL; /* Return the cached answer if we've already computed it. */ @@ -987,6 +971,12 @@ enumerate_bloom_filter_build_relids(PlannerInfo *root) root->bloom_build_relids_valid = true; root->bloom_build_relids = NIL; + /* + * Join levels, indexed by number of relids (starts at 1 for baserels, + * same as in standard_join_search). + */ + levels = palloc0_array(List *, (bloom_filter_pushdown_max_build_relids + 1)); + /* Level 1: each base relation on its own. */ levels[1] = NIL; for (i = 1; i < root->simple_rel_array_size; i++) @@ -1023,7 +1013,7 @@ enumerate_bloom_filter_build_relids(PlannerInfo *root) result = list_copy(levels[1]); /* Build higher levels by adding one base relation at a time. */ - for (level = 2; level <= BLOOM_MAX_BUILD_RELIDS; level++) + for (level = 2; level <= bloom_filter_pushdown_max_build_relids; level++) { ListCell *lc; @@ -1080,7 +1070,12 @@ enumerate_bloom_filter_build_relids(PlannerInfo *root) levels[level] = lappend(levels[level], joinrelids); result = lappend(result, joinrelids); - if (list_length(result) >= BLOOM_MAX_BUILD_SETS) + /* + * Stop generating joins once we hit the maximum allowed + * number of join relids (to keep the planning effort under + * control). + */ + if (list_length(result) >= bloom_filter_pushdown_max_build_sets) { root->bloom_build_relids = result; return result; diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index aacadc8de40..3ac75bc4b38 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -388,6 +388,27 @@ boot_val => '3', min => '0', max => '10', + flags => 'GUC_EXPLAIN', +}, + +{ name => 'bloom_filter_pushdown_max_build_relids', type => 'int', context => 'PGC_USERSET', group => 'QUERY_TUNING_OTHER', + short_desc => 'Maximum number of base relations in an enumerated hash join bloom filter build side.', + long_desc => 'Bounds the size of each candidate build side the planner enumerates when looking for interesting pushed-down bloom filters.', + variable => 'bloom_filter_pushdown_max_build_relids', + boot_val => '3', + min => '1', + max => '100', + flags => 'GUC_EXPLAIN', +}, + +{ name => 'bloom_filter_pushdown_max_build_sets', type => 'int', context => 'PGC_USERSET', group => 'QUERY_TUNING_OTHER', + short_desc => 'Maximum number of enumerated hash join bloom filter build-side relation sets.', + long_desc => 'Overall cap on the number of build-side relation sets the planner enumerates, as a safety valve against pathological join graphs.', + variable => 'bloom_filter_pushdown_max_build_sets', + boot_val => '100', + min => '1', + max => 'INT_MAX', + flags => 'GUC_EXPLAIN', }, { name => 'bloom_filter_pushdown_threshold', type => 'real', context => 'PGC_USERSET', group => 'QUERY_TUNING_OTHER', @@ -398,6 +419,7 @@ boot_val => '0.3', min => '0.0', max => '1.0', + flags => 'GUC_EXPLAIN', }, { name => 'bonjour', type => 'bool', context => 'PGC_POSTMASTER', group => 'CONN_AUTH_SETTINGS', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 3247ed46e6e..9080e9e0784 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -488,6 +488,8 @@ #bloom_filter_pushdown_max = 3 # range 0-10 #bloom_filter_pushdown_threshold = 0.3 # range 0.0-1.0 +#bloom_filter_pushdown_max_build_relids = 3 # range 1-100 +#bloom_filter_pushdown_max_build_sets = 100 # range 1-INT_MAX #default_statistics_target = 100 # range 1-10000 #constraint_exclusion = partition # on, off, or partition #cursor_tuple_fraction = 0.1 # range 0.0-1.0 diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 2047f211c4c..9daa6ded3f8 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -74,6 +74,8 @@ extern PGDLLIMPORT bool enable_presorted_aggregate; extern PGDLLIMPORT bool enable_async_append; extern PGDLLIMPORT double bloom_filter_pushdown_threshold; extern PGDLLIMPORT int bloom_filter_pushdown_max; +extern PGDLLIMPORT int bloom_filter_pushdown_max_build_relids; +extern PGDLLIMPORT int bloom_filter_pushdown_max_build_sets; extern PGDLLIMPORT int constraint_exclusion; extern double index_pages_fetched(double tuples_fetched, BlockNumber pages, -- 2.55.0