From a9782f751fde023d4934212fb8c589f56a48af84 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Mon, 7 Oct 2024 15:41:51 -0400 Subject: [PATCH] Allow extensions to control scan strategy. At the start of planning, we build a bitmask of allowable scan strategies beased on the value of the various enable_* planner GUCs. Extensions can override the behavior on a per-rel basis using get_relation_info_hook. As with the join strategy advice, this isn't sufficient for all needs. If you want to control which index is used, the same hook, get_relation_info_hook, that you use to set scan strategy can also editorialize on the index list. However, that doesn't appear to be sufficient to fully control the shape of bitmap plans. Another gap is that it's not clear what to do if you want to encourage parallel plans or non-parallel plans. It is not entirely clear to me whether that is a problem that is specific to the scan level or whether it is something more general. --- src/backend/optimizer/path/costsize.c | 25 +++++++++++++--------- src/backend/optimizer/path/indxpath.c | 4 ++-- src/backend/optimizer/path/tidpath.c | 7 ++++--- src/backend/optimizer/plan/planner.c | 22 ++++++++++++++++++++ src/backend/optimizer/util/plancat.c | 3 +++ src/backend/optimizer/util/relnode.c | 7 +++++++ src/include/nodes/pathnodes.h | 5 +++++ src/include/optimizer/paths.h | 30 +++++++++++++++++++++++++++ 8 files changed, 88 insertions(+), 15 deletions(-) diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 3b1c6e3897..9547420383 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -354,7 +354,7 @@ cost_seqscan(Path *path, PlannerInfo *root, path->rows = clamp_row_est(path->rows / parallel_divisor); } - path->disabled_nodes = enable_seqscan ? 0 : 1; + path->disabled_nodes = (baserel->ssa_mask & SSA_SEQSCAN) != 0 ? 0 : 1; path->startup_cost = startup_cost; path->total_cost = startup_cost + cpu_run_cost + disk_run_cost; } @@ -583,6 +583,7 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count, double pages_fetched; double rand_heap_pages; double index_pages; + bool enabled; /* Should only be applied to base relations */ Assert(IsA(baserel, RelOptInfo) && @@ -614,8 +615,12 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count, path->indexclauses); } - /* we don't need to check enable_indexonlyscan; indxpath.c does that */ - path->path.disabled_nodes = enable_indexscan ? 0 : 1; + /* is this scan type disabled? */ + if (indexonly) + enabled = (baserel->ssa_mask & SSA_INDEXONLYSCAN) ? 1 : 0; + else + enabled = (baserel->ssa_mask & SSA_INDEXSCAN) ? 1 : 0; + path->path.disabled_nodes = enabled ? 0 : 1; /* * Call index-access-method-specific code to estimate the processing cost @@ -1109,7 +1114,7 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel, startup_cost += path->pathtarget->cost.startup; run_cost += path->pathtarget->cost.per_tuple * path->rows; - path->disabled_nodes = enable_bitmapscan ? 0 : 1; + path->disabled_nodes = (baserel->ssa_mask & SSA_BITMAPSCAN) != 0 ? 0 : 1; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; } @@ -1287,10 +1292,10 @@ cost_tidscan(Path *path, PlannerInfo *root, /* * We must use a TID scan for CurrentOfExpr; in any other case, we - * should be generating a TID scan only if enable_tidscan=true. Also, + * should be generating a TID scan only if TID scans are allowed. Also, * if CurrentOfExpr is the qual, there should be only one. */ - Assert(enable_tidscan || IsA(qual, CurrentOfExpr)); + Assert((baserel->ssa_mask & SSA_TIDSCAN) != 0 || IsA(qual, CurrentOfExpr)); Assert(list_length(tidquals) == 1 || !IsA(qual, CurrentOfExpr)); if (IsA(qual, ScalarArrayOpExpr)) @@ -1342,8 +1347,8 @@ cost_tidscan(Path *path, PlannerInfo *root, /* * There are assertions above verifying that we only reach this function - * either when enable_tidscan=true or when the TID scan is the only legal - * path, so it's safe to set disabled_nodes to zero here. + * either when baserel->ssa_mask includes SSA_TIDSCAN or when the TID scan + * is the only legal path, so it's safe to set disabled_nodes to zero here. */ path->disabled_nodes = 0; path->startup_cost = startup_cost; @@ -1438,8 +1443,8 @@ cost_tidrangescan(Path *path, PlannerInfo *root, startup_cost += path->pathtarget->cost.startup; run_cost += path->pathtarget->cost.per_tuple * path->rows; - /* we should not generate this path type when enable_tidscan=false */ - Assert(enable_tidscan); + /* we should not generate this path type when TID scans are disabled */ + Assert((baserel->ssa_mask & SSA_TIDSCAN) != 0); path->disabled_nodes = 0; path->startup_cost = startup_cost; path->total_cost = startup_cost + run_cost; diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 601354ea3e..9e629c6c6a 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -2232,8 +2232,8 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) ListCell *lc; int i; - /* Index-only scans must be enabled */ - if (!enable_indexonlyscan) + /* If we're not allowed to consider index-only scans, give up now */ + if ((rel->ssa_mask & SSA_CONSIDER_INDEXONLY) == 0) return false; /* diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c index 2bfb338b81..3fa9dd3fa7 100644 --- a/src/backend/optimizer/path/tidpath.c +++ b/src/backend/optimizer/path/tidpath.c @@ -500,18 +500,19 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) List *tidquals; List *tidrangequals; bool isCurrentOf; + bool enabled = (rel->ssa_mask & SSA_TIDSCAN) != 0; /* * If any suitable quals exist in the rel's baserestrict list, generate a * plain (unparameterized) TidPath with them. * - * We skip this when enable_tidscan = false, except when the qual is + * We skip this when TID scans are disabled, except when the qual is * CurrentOfExpr. In that case, a TID scan is the only correct path. */ tidquals = TidQualFromRestrictInfoList(root, rel->baserestrictinfo, rel, &isCurrentOf); - if (tidquals != NIL && (enable_tidscan || isCurrentOf)) + if (tidquals != NIL && (enabled || isCurrentOf)) { /* * This path uses no join clauses, but it could still have required @@ -533,7 +534,7 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) } /* Skip the rest if TID scans are disabled. */ - if (!enable_tidscan) + if (!enabled) return false; /* diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 621ea162e0..defd0950ce 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -431,6 +431,28 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, tuple_fraction = 0.0; } + /* + * Compute the initial scan strategy advice mask. + * + * It may seem surprising that enable_indexscan sets both SSA_INDEXSCAN + * and SSA_INDEXONLYSCAN. However, the historical behavior of this GUC + * corresponds to this exactly: enable_indexscan=off disables both + * index-scan and index-only scan paths, whereas enable_indexonlyscan=off + * converts the index-only scan paths that we would have considered into + * index scan paths. + */ + glob->default_ssa_mask = 0; + if (enable_tidscan) + glob->default_ssa_mask |= SSA_TIDSCAN; + if (enable_seqscan) + glob->default_ssa_mask |= SSA_SEQSCAN; + if (enable_indexscan) + glob->default_ssa_mask |= SSA_INDEXSCAN | SSA_INDEXONLYSCAN; + if (enable_indexonlyscan) + glob->default_ssa_mask |= SSA_CONSIDER_INDEXONLY; + if (enable_bitmapscan) + glob->default_ssa_mask |= SSA_BITMAPSCAN; + /* Compute the initial join strategy advice mask. */ glob->default_jsa_mask = JSA_FOREIGN; if (enable_hashjoin) diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index c6a58afc5e..85155ae72a 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -555,6 +555,9 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * Allow a plugin to editorialize on the info we obtained from the * catalogs. Actions might include altering the assumed relation size, * removing an index, or adding a hypothetical index to the indexlist. + * + * An extension can also modify rel->ssa_mask here to control the scan + * strategy. */ if (get_relation_info_hook) (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 6e35834ca2..95b8180896 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -321,6 +321,12 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->direct_lateral_relids = parent->direct_lateral_relids; rel->lateral_relids = parent->lateral_relids; rel->lateral_referencers = parent->lateral_referencers; + + /* + * By default, a parent's scan strategy advice is preserved for each + * inheritance child. + */ + rel->ssa_mask = parent->ssa_mask; } else { @@ -331,6 +337,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->direct_lateral_relids = NULL; rel->lateral_relids = NULL; rel->lateral_referencers = NULL; + rel->ssa_mask = root->glob->default_ssa_mask; } /* Check type of rtable entry */ diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 80f8f36dd6..d5380592ec 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -186,6 +186,9 @@ typedef struct PlannerGlobal /* worst PROPARALLEL hazard level */ char maxParallelHazard; + /* default scan strategy advice, except where overrriden by hooks */ + uint32 default_ssa_mask; + /* default join strategy advice, except where overrriden by hooks */ uint32 default_jsa_mask; @@ -975,6 +978,8 @@ typedef struct RelOptInfo int32 *attr_widths pg_node_attr(read_write_ignore); /* zero-based set containing attnums of NOT NULL columns */ Bitmapset *notnullattnums; + /* scan strategy advice */ + uint32 ssa_mask; /* relids of outer joins that can null this baserel */ Relids nulling_relids; /* LATERAL Vars and PHVs referenced by rel */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index c45a63edc3..2bffe5e384 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -16,6 +16,36 @@ #include "nodes/pathnodes.h" +/* + * Scan strategy advice. + * + * If SSA_CONSIDER_INDEXONLY is not set, index-only scan paths will not even + * be generated, and we'll generated index-scan paths for the same cases + * instead. If any other bit is not set, paths of that type will still be + * generated but will be marked as disabled. + * + * So, if you want to avoid an index-only scan, you can either unset + * SSA_CONSIDER_INDEXONLY (in which case you'll get an index-scan instead, + * which may end up disabled if you also unset SSA_INDEXSCAN) or you can + * unset SSA_INDEXONLYSCAN (in which the index-only scan will be disabled + * and the cheapest non-disabled alternative, if any, will be chosen, but + * no corresponding index scan will be considered). If, on the other hand, + * you want to encourage an index-only scan, you can set just SSA_INDEXONLYSCAN + * and SSA_CONSIDER_INDEXONLY and clear all of the other bits. + * + * A default scan strategy advice mask is stored in the PlannerGlobal object + * based on the values of the various enable_* GUCs. This value is propagted + * into each RelOptInfo for a baserel, and from baserels to their inheritance + * children when partitions are expanded. In either case, the value can be + * usefully changed in get_relation_info_hook. + */ +#define SSA_TIDSCAN 0x0001 +#define SSA_SEQSCAN 0x0002 +#define SSA_INDEXSCAN 0x0004 +#define SSA_INDEXONLYSCAN 0x0008 +#define SSA_BITMAPSCAN 0x0010 +#define SSA_CONSIDER_INDEXONLY 0x0020 + /* * Join strategy advice. * -- 2.39.5