diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index a43daa7..ef125b4 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -4785,6 +4785,179 @@ get_parallel_divisor(Path *path) } /* + * compute_index_pages + * + * compute number of pages fetched from index in index scan. + */ +double +compute_index_pages(PlannerInfo *root, IndexOptInfo *index, + List *indexQuals, int loop_count, double numIndexTuples, + double *random_page_cost, double *sa_scans, + double *indexTuples, double *indexPages, + Selectivity *indexSel, Cost *cost) +{ + List *selectivityQuals; + double pages_fetched; + double num_sa_scans; + double num_outer_scans; + double num_scans; + double spc_random_page_cost; + double numIndexPages; + Selectivity indexSelectivity; + Cost indexTotalCost; + ListCell *l; + + /* + * If the index is partial, AND the index predicate with the explicitly + * given indexquals to produce a more accurate idea of the index + * selectivity. + */ + selectivityQuals = add_predicate_to_quals(index, indexQuals); + + /* + * Check for ScalarArrayOpExpr index quals, and estimate the number of + * index scans that will be performed. + */ + num_sa_scans = 1; + foreach(l, indexQuals) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); + + if (IsA(rinfo->clause, ScalarArrayOpExpr)) + { + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause; + int alength = estimate_array_length(lsecond(saop->args)); + + if (alength > 1) + num_sa_scans *= alength; + } + } + + /* Estimate the fraction of main-table tuples that will be visited */ + indexSelectivity = clauselist_selectivity(root, selectivityQuals, + index->rel->relid, + JOIN_INNER, + NULL); + + /* + * If caller didn't give us an estimate, estimate the number of index + * tuples that will be visited. We do it in this rather peculiar-looking + * way in order to get the right answer for partial indexes. + */ + if (numIndexTuples <= 0.0) + { + numIndexTuples = indexSelectivity * index->rel->tuples; + + /* + * The above calculation counts all the tuples visited across all + * scans induced by ScalarArrayOpExpr nodes. We want to consider the + * average per-indexscan number, so adjust. This is a handy place to + * round to integer, too. (If caller supplied tuple estimate, it's + * responsible for handling these considerations.) + */ + numIndexTuples = rint(numIndexTuples / num_sa_scans); + } + + /* + * We can bound the number of tuples by the index size in any case. Also, + * always estimate at least one tuple is touched, even when + * indexSelectivity estimate is tiny. + */ + if (numIndexTuples > index->tuples) + numIndexTuples = index->tuples; + if (numIndexTuples < 1.0) + numIndexTuples = 1.0; + + /* + * Estimate the number of index pages that will be retrieved. + * + * We use the simplistic method of taking a pro-rata fraction of the total + * number of index pages. In effect, this counts only leaf pages and not + * any overhead such as index metapage or upper tree levels. + * + * In practice access to upper index levels is often nearly free because + * those tend to stay in cache under load; moreover, the cost involved is + * highly dependent on index type. We therefore ignore such costs here + * and leave it to the caller to add a suitable charge if needed. + */ + if (index->pages > 1 && index->tuples > 1) + numIndexPages = ceil(numIndexTuples * index->pages / index->tuples); + else + numIndexPages = 1.0; + + /* fetch estimated page cost for tablespace containing index */ + get_tablespace_page_costs(index->reltablespace, + &spc_random_page_cost, + NULL); + + /* + * Now compute the disk access costs. + * + * The above calculations are all per-index-scan. However, if we are in a + * nestloop inner scan, we can expect the scan to be repeated (with + * different search keys) for each row of the outer relation. Likewise, + * ScalarArrayOpExpr quals result in multiple index scans. This creates + * the potential for cache effects to reduce the number of disk page + * fetches needed. We want to estimate the average per-scan I/O cost in + * the presence of caching. + * + * We use the Mackert-Lohman formula (see costsize.c for details) to + * estimate the total number of page fetches that occur. While this + * wasn't what it was designed for, it seems a reasonable model anyway. + * Note that we are counting pages not tuples anymore, so we take N = T = + * index size, as if there were one "tuple" per page. + */ + num_outer_scans = loop_count; + num_scans = num_sa_scans * num_outer_scans; + + if (num_scans > 1) + { + /* total page fetches ignoring cache effects */ + pages_fetched = numIndexPages * num_scans; + + /* use Mackert and Lohman formula to adjust for cache effects */ + pages_fetched = index_pages_fetched(pages_fetched, + index->pages, + (double) index->pages, + root); + + /* + * Now compute the total disk access cost, and then report a pro-rated + * share for each outer scan. (Don't pro-rate for ScalarArrayOpExpr, + * since that's internal to the indexscan.) + */ + indexTotalCost = (pages_fetched * spc_random_page_cost) + / num_outer_scans; + } + else + { + pages_fetched = numIndexPages; + + /* + * For a single index scan, we just charge spc_random_page_cost per + * page touched. + */ + indexTotalCost = numIndexPages * spc_random_page_cost; + } + + /* fill the output parameters */ + if (random_page_cost) + *random_page_cost = spc_random_page_cost; + if (sa_scans) + *sa_scans = num_sa_scans; + if (indexTuples) + *indexTuples = numIndexTuples; + if (indexSel) + *indexSel = indexSelectivity; + if (indexPages) + *indexPages = numIndexPages; + if (cost) + *cost = indexTotalCost; + + return pages_fetched; +} + +/* * compute_bitmap_pages * * compute number of pages fetched from heap in bitmap heap scan. diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 5283468..5d22496 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -863,6 +863,8 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, IndexPath *ipath; List *index_clauses; List *clause_columns; + List *indexquals; + List *indexqualcols; Relids outer_relids; double loop_count; List *orderbyclauses; @@ -1014,6 +1016,11 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, orderbyclausecols = NIL; } + /* Convert clauses to indexquals the executor can handle */ + expand_indexqual_conditions(index, index_clauses, clause_columns, + &indexquals, &indexqualcols); + + /* * 3. Check if an index-only scan is possible. If we're not building * plain indexscans, this isn't relevant since bitmap scans don't support @@ -1034,6 +1041,8 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, ipath = create_index_path(root, index, index_clauses, clause_columns, + indexquals, + indexqualcols, orderbyclauses, orderbyclausecols, useful_pathkeys, @@ -1060,6 +1069,8 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, ipath = create_index_path(root, index, index_clauses, clause_columns, + indexquals, + indexqualcols, NIL, NIL, useful_pathkeys, diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 881742f..f5366d8 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -5377,7 +5377,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid) /* Estimate the cost of index scan */ indexScanPath = create_index_path(root, indexInfo, - NIL, NIL, NIL, NIL, NIL, + NIL, NIL, NIL, NIL, NIL, NIL, NIL, ForwardScanDirection, false, NULL, 1.0); diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index f440875..8cd80db 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1013,6 +1013,8 @@ create_index_path(PlannerInfo *root, IndexOptInfo *index, List *indexclauses, List *indexclausecols, + List *indexquals, + List *indexqualcols, List *indexorderbys, List *indexorderbycols, List *pathkeys, @@ -1023,8 +1025,6 @@ create_index_path(PlannerInfo *root, { IndexPath *pathnode = makeNode(IndexPath); RelOptInfo *rel = index->rel; - List *indexquals, - *indexqualcols; pathnode->path.pathtype = indexonly ? T_IndexOnlyScan : T_IndexScan; pathnode->path.parent = rel; @@ -1036,10 +1036,6 @@ create_index_path(PlannerInfo *root, pathnode->path.parallel_workers = 0; pathnode->path.pathkeys = pathkeys; - /* Convert clauses to indexquals the executor can handle */ - expand_indexqual_conditions(index, indexclauses, indexclausecols, - &indexquals, &indexqualcols); - /* Fill in the pathnode */ pathnode->indexinfo = index; pathnode->indexclauses = indexclauses; diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index fa32e9e..6762bf9 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -205,7 +205,6 @@ static Selectivity regex_selectivity(const char *patt, int pattlen, static Datum string_to_datum(const char *str, Oid datatype); static Const *string_to_const(const char *str, Oid datatype); static Const *string_to_bytea_const(const char *str, size_t str_len); -static List *add_predicate_to_quals(IndexOptInfo *index, List *indexQuals); /* @@ -6245,146 +6244,13 @@ genericcostestimate(PlannerInfo *root, double numIndexTuples; double spc_random_page_cost; double num_sa_scans; - double num_outer_scans; - double num_scans; double qual_op_cost; double qual_arg_cost; - List *selectivityQuals; - ListCell *l; - - /* - * If the index is partial, AND the index predicate with the explicitly - * given indexquals to produce a more accurate idea of the index - * selectivity. - */ - selectivityQuals = add_predicate_to_quals(index, indexQuals); - - /* - * Check for ScalarArrayOpExpr index quals, and estimate the number of - * index scans that will be performed. - */ - num_sa_scans = 1; - foreach(l, indexQuals) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); - - if (IsA(rinfo->clause, ScalarArrayOpExpr)) - { - ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause; - int alength = estimate_array_length(lsecond(saop->args)); - - if (alength > 1) - num_sa_scans *= alength; - } - } - - /* Estimate the fraction of main-table tuples that will be visited */ - indexSelectivity = clauselist_selectivity(root, selectivityQuals, - index->rel->relid, - JOIN_INNER, - NULL); - - /* - * If caller didn't give us an estimate, estimate the number of index - * tuples that will be visited. We do it in this rather peculiar-looking - * way in order to get the right answer for partial indexes. - */ - numIndexTuples = costs->numIndexTuples; - if (numIndexTuples <= 0.0) - { - numIndexTuples = indexSelectivity * index->rel->tuples; - - /* - * The above calculation counts all the tuples visited across all - * scans induced by ScalarArrayOpExpr nodes. We want to consider the - * average per-indexscan number, so adjust. This is a handy place to - * round to integer, too. (If caller supplied tuple estimate, it's - * responsible for handling these considerations.) - */ - numIndexTuples = rint(numIndexTuples / num_sa_scans); - } - - /* - * We can bound the number of tuples by the index size in any case. Also, - * always estimate at least one tuple is touched, even when - * indexSelectivity estimate is tiny. - */ - if (numIndexTuples > index->tuples) - numIndexTuples = index->tuples; - if (numIndexTuples < 1.0) - numIndexTuples = 1.0; - - /* - * Estimate the number of index pages that will be retrieved. - * - * We use the simplistic method of taking a pro-rata fraction of the total - * number of index pages. In effect, this counts only leaf pages and not - * any overhead such as index metapage or upper tree levels. - * - * In practice access to upper index levels is often nearly free because - * those tend to stay in cache under load; moreover, the cost involved is - * highly dependent on index type. We therefore ignore such costs here - * and leave it to the caller to add a suitable charge if needed. - */ - if (index->pages > 1 && index->tuples > 1) - numIndexPages = ceil(numIndexTuples * index->pages / index->tuples); - else - numIndexPages = 1.0; - /* fetch estimated page cost for tablespace containing index */ - get_tablespace_page_costs(index->reltablespace, - &spc_random_page_cost, - NULL); - - /* - * Now compute the disk access costs. - * - * The above calculations are all per-index-scan. However, if we are in a - * nestloop inner scan, we can expect the scan to be repeated (with - * different search keys) for each row of the outer relation. Likewise, - * ScalarArrayOpExpr quals result in multiple index scans. This creates - * the potential for cache effects to reduce the number of disk page - * fetches needed. We want to estimate the average per-scan I/O cost in - * the presence of caching. - * - * We use the Mackert-Lohman formula (see costsize.c for details) to - * estimate the total number of page fetches that occur. While this - * wasn't what it was designed for, it seems a reasonable model anyway. - * Note that we are counting pages not tuples anymore, so we take N = T = - * index size, as if there were one "tuple" per page. - */ - num_outer_scans = loop_count; - num_scans = num_sa_scans * num_outer_scans; - - if (num_scans > 1) - { - double pages_fetched; - - /* total page fetches ignoring cache effects */ - pages_fetched = numIndexPages * num_scans; - - /* use Mackert and Lohman formula to adjust for cache effects */ - pages_fetched = index_pages_fetched(pages_fetched, - index->pages, - (double) index->pages, - root); - - /* - * Now compute the total disk access cost, and then report a pro-rated - * share for each outer scan. (Don't pro-rate for ScalarArrayOpExpr, - * since that's internal to the indexscan.) - */ - indexTotalCost = (pages_fetched * spc_random_page_cost) - / num_outer_scans; - } - else - { - /* - * For a single index scan, we just charge spc_random_page_cost per - * page touched. - */ - indexTotalCost = numIndexPages * spc_random_page_cost; - } + (void) compute_index_pages(root, index, indexQuals, loop_count, + costs->numIndexTuples, &spc_random_page_cost, + &num_sa_scans, &numIndexTuples, &numIndexPages, + &indexSelectivity, &indexTotalCost); /* * CPU cost: any complex expressions in the indexquals will need to be @@ -6446,7 +6312,7 @@ genericcostestimate(PlannerInfo *root, * predicate_implied_by() and clauselist_selectivity(), but might be * problematic if the result were passed to other things. */ -static List * +List * add_predicate_to_quals(IndexOptInfo *index, List *indexQuals) { List *predExtraQuals = NIL; diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 0e68264..8525917 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -183,6 +183,10 @@ extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, double cte_rows); extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel); extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target); +extern double compute_index_pages(PlannerInfo *root, IndexOptInfo *index, + List *indexclauses, int loop_count, double numIndexTuples, + double *random_page_cost, double *sa_scans, double *indexTuples, + double *indexPages, Selectivity *indexSel, Cost *cost); extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel, Path *bitmapqual, int loop_count, Cost *cost, double *tuple); diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 7b41317..44e143c 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -41,6 +41,8 @@ extern IndexPath *create_index_path(PlannerInfo *root, IndexOptInfo *index, List *indexclauses, List *indexclausecols, + List *indexquals, + List *indexqualcols, List *indexorderbys, List *indexorderbycols, List *pathkeys, diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h index 9f9d2dc..b1ffca3 100644 --- a/src/include/utils/selfuncs.h +++ b/src/include/utils/selfuncs.h @@ -212,6 +212,7 @@ extern void genericcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, List *qinfos, GenericCosts *costs); +extern List *add_predicate_to_quals(IndexOptInfo *index, List *indexQuals); /* Functions in array_selfuncs.c */