From 27f806771780a651211912df08b95e26c650f36d Mon Sep 17 00:00:00 2001 From: Chengpeng Yan Date: Tue, 16 Dec 2025 20:15:44 +0800 Subject: [PATCH v4 2/2] add a GUC goo_greedy_strategy to choose different GOO greedy strategic to test Signed-off-by: Chengpeng Yan --- src/backend/optimizer/path/goo.c | 122 +++++++++++++++++++++- src/backend/utils/misc/guc_parameters.dat | 10 ++ src/backend/utils/misc/guc_tables.c | 7 ++ src/include/optimizer/paths.h | 7 ++ 4 files changed, 145 insertions(+), 1 deletion(-) diff --git a/src/backend/optimizer/path/goo.c b/src/backend/optimizer/path/goo.c index 247dbb5f921..bbada782960 100644 --- a/src/backend/optimizer/path/goo.c +++ b/src/backend/optimizer/path/goo.c @@ -55,6 +55,7 @@ * Configuration defaults. These are exposed as GUCs in guc_tables.c. */ bool enable_goo_join_search = false; +int goo_greedy_strategy = GOO_GREEDY_STRATEGY_RESULT_SIZE; /* * Working state for a single GOO search invocation. @@ -94,6 +95,7 @@ typedef struct GooCandidate { RelOptInfo *left; /* left input clump */ RelOptInfo *right; /* right input clump */ + double result_size; /* estimated result size in bytes */ Cost total_cost; /* total cost of cheapest path */ } GooCandidate; @@ -129,6 +131,105 @@ goo_join_search(PlannerInfo *root, int levels_needed, int base_rel_count; struct HTAB *base_hash; + /* If COMBINED mode, try both strategies and return the better one */ + if (goo_greedy_strategy == GOO_GREEDY_STRATEGY_COMBINED) + { + RelOptInfo *result_cost; + RelOptInfo *result_size; + Cost cost_result_cost; + Cost size_result_cost; + int saved_strategy; + List *saved_join_rel_list_cost; + struct HTAB *saved_join_rel_hash_cost; + + /* Save the original strategy */ + saved_strategy = goo_greedy_strategy; + + /* + * First try: COST strategy + */ + goo_greedy_strategy = GOO_GREEDY_STRATEGY_COST; + state = goo_init_state(root, initial_rels); + base_rel_count = list_length(root->join_rel_list); + base_hash = root->join_rel_hash; + + result_cost = goo_search_internal(state); + + if (result_cost == NULL) + { + root->join_rel_list = list_truncate(root->join_rel_list, base_rel_count); + root->join_rel_hash = base_hash; + elog(ERROR, "GOO join search (COST strategy) failed to find a valid join order"); + } + + cost_result_cost = result_cost->cheapest_total_path->total_cost; + goo_destroy_state(state); + + /* + * Save the COST strategy's join_rel_list and join_rel_hash. If COST + * strategy wins, we'll restore these instead of re-running. + */ + saved_join_rel_list_cost = root->join_rel_list; + saved_join_rel_hash_cost = root->join_rel_hash; + + /* + * Second try: RESULT_SIZE strategy + * + * Reset the planner state to start fresh. We need to clear all + * intermediate join relations created by the first search. + */ + root->join_rel_list = list_truncate(root->join_rel_list, base_rel_count); + root->join_rel_hash = base_hash; + + goo_greedy_strategy = GOO_GREEDY_STRATEGY_RESULT_SIZE; + state = goo_init_state(root, initial_rels); + + result_size = goo_search_internal(state); + + if (result_size == NULL) + { + root->join_rel_list = list_truncate(root->join_rel_list, base_rel_count); + root->join_rel_hash = base_hash; + elog(ERROR, "GOO join search (RESULT_SIZE strategy) failed to find a valid join order"); + } + + size_result_cost = result_size->cheapest_total_path->total_cost; + goo_destroy_state(state); + + /* Restore the original strategy */ + goo_greedy_strategy = saved_strategy; + + /* + * Compare the two results and return the one with lower cost + */ + if (cost_result_cost <= size_result_cost) + { + /* + * COST strategy won. Restore the COST strategy's join relations + * instead of re-running the search. + */ + root->join_rel_list = saved_join_rel_list_cost; + root->join_rel_hash = saved_join_rel_hash_cost; + + elog(DEBUG1, "GOO COMBINED mode: COST strategy chosen (cost: %.2f vs %.2f)", + cost_result_cost, size_result_cost); + + return result_cost; + } + else + { + /* + * RESULT_SIZE strategy won. The join relations are already in + * place, so we can return the result directly. + */ + elog(DEBUG1, "GOO COMBINED mode: RESULT_SIZE strategy chosen (cost: %.2f vs %.2f)", + size_result_cost, cost_result_cost); + + return result_size; + } + } + + /* Normal single-strategy mode */ /* Initialize search state and memory contexts */ state = goo_init_state(root, initial_rels); @@ -417,6 +518,8 @@ static GooCandidate * goo_build_candidate(GooState * state, RelOptInfo *left, int saved_rel_len; struct HTAB *saved_hash; RelOptInfo *joinrel; + double join_rows; + double result_size; Cost total_cost; GooCandidate *cand; @@ -477,6 +580,9 @@ static GooCandidate * goo_build_candidate(GooState * state, RelOptInfo *left, set_cheapest(grouped_rel); } + join_rows = joinrel->rows; + + result_size = join_rows * joinrel->reltarget->width; total_cost = joinrel->cheapest_total_path->total_cost; /* @@ -495,6 +601,7 @@ static GooCandidate * goo_build_candidate(GooState * state, RelOptInfo *left, cand = palloc(sizeof(GooCandidate)); cand->left = left; cand->right = right; + cand->result_size = result_size; cand->total_cost = total_cost; MemoryContextSwitchTo(oldcxt); @@ -608,5 +715,18 @@ goo_commit_join(GooState * state, GooCandidate * cand) static bool goo_candidate_better(GooCandidate * a, GooCandidate * b) { - return (a->total_cost < b->total_cost); + switch (goo_greedy_strategy) + { + case GOO_GREEDY_STRATEGY_COMBINED: + /* Should not be called in COMBINED mode */ + elog(ERROR, "goo_candidate_better should not be called in COMBINED mode"); + return false; + + case GOO_GREEDY_STRATEGY_COST: + return a->total_cost < b->total_cost; + + case GOO_GREEDY_STRATEGY_RESULT_SIZE: + default: + return a->result_size < b->result_size; + } } diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index a8ce31ab8a7..2a017195820 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -1154,6 +1154,16 @@ max => 'MAX_KILOBYTES', }, +/* WIP: only for testing */ +{ name => 'goo_greedy_strategy', type => 'enum', context => 'PGC_USERSET', group => 'QUERY_TUNING_GEQO', + short_desc => 'Selects the heuristic used by GOO to compare join candidates.', + long_desc => 'Valid values are cost, result_size, and combined.', + flags => 'GUC_EXPLAIN', + variable => 'goo_greedy_strategy', + boot_val => 'GOO_GREEDY_STRATEGY_RESULT_SIZE', + options => 'goo_greedy_strategy_options', +}, + { name => 'gss_accept_delegation', type => 'bool', context => 'PGC_SIGHUP', group => 'CONN_AUTH_AUTH', short_desc => 'Sets whether GSSAPI delegation should be accepted from the client.', variable => 'pg_gss_accept_delegation', diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index f87b558c2c6..11b299453fa 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -411,6 +411,13 @@ static const struct config_enum_entry plan_cache_mode_options[] = { {NULL, 0, false} }; +static const struct config_enum_entry goo_greedy_strategy_options[] = { + {"cost", GOO_GREEDY_STRATEGY_COST, false}, + {"result_size", GOO_GREEDY_STRATEGY_RESULT_SIZE, false}, + {"combined", GOO_GREEDY_STRATEGY_COMBINED, false}, + {NULL, 0, false} +}; + static const struct config_enum_entry password_encryption_options[] = { {"md5", PASSWORD_TYPE_MD5, false}, {"scram-sha-256", PASSWORD_TYPE_SCRAM_SHA_256, false}, diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 5b3ebe5f1d2..c42610b34b3 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -16,12 +16,19 @@ #include "nodes/pathnodes.h" +typedef enum GooGreedyStrategy +{ + GOO_GREEDY_STRATEGY_COST, + GOO_GREEDY_STRATEGY_RESULT_SIZE, + GOO_GREEDY_STRATEGY_COMBINED +} GooGreedyStrategy; /* * allpaths.c */ extern PGDLLIMPORT bool enable_geqo; extern PGDLLIMPORT bool enable_goo_join_search; +extern PGDLLIMPORT int goo_greedy_strategy; extern PGDLLIMPORT bool enable_eager_aggregate; extern PGDLLIMPORT int geqo_threshold; extern PGDLLIMPORT double min_eager_agg_group_size; -- 2.39.3 (Apple Git-146)