mirror of
https://github.com/postgres/postgres.git
synced 2025-07-12 21:01:52 +03:00
Add optimizer and executor support for parallel index scans.
In combination with 569174f1be
, which
taught the btree AM how to perform parallel index scans, this allows
parallel index scan plans on btree indexes. This infrastructure
should be general enough to support parallel index scans for other
index AMs as well, if someone updates them to support parallel
scans.
Amit Kapila, reviewed and tested by Anastasia Lubennikova, Tushar
Ahuja, and Haribabu Kommi, and me.
This commit is contained in:
@ -127,8 +127,6 @@ static void subquery_push_qual(Query *subquery,
|
||||
static void recurse_push_qual(Node *setOp, Query *topquery,
|
||||
RangeTblEntry *rte, Index rti, Node *qual);
|
||||
static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel);
|
||||
static int compute_parallel_worker(RelOptInfo *rel, BlockNumber heap_pages,
|
||||
BlockNumber index_pages);
|
||||
|
||||
|
||||
/*
|
||||
@ -2885,7 +2883,7 @@ remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel)
|
||||
* "heap_pages" is the number of pages from the table that we expect to scan.
|
||||
* "index_pages" is the number of pages from the index that we expect to scan.
|
||||
*/
|
||||
static int
|
||||
int
|
||||
compute_parallel_worker(RelOptInfo *rel, BlockNumber heap_pages,
|
||||
BlockNumber index_pages)
|
||||
{
|
||||
|
@ -391,7 +391,8 @@ cost_gather(GatherPath *path, PlannerInfo *root,
|
||||
* we have to fetch from the table, so they don't reduce the scan cost.
|
||||
*/
|
||||
void
|
||||
cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
|
||||
cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
|
||||
bool partial_path)
|
||||
{
|
||||
IndexOptInfo *index = path->indexinfo;
|
||||
RelOptInfo *baserel = index->rel;
|
||||
@ -400,6 +401,7 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
|
||||
List *qpquals;
|
||||
Cost startup_cost = 0;
|
||||
Cost run_cost = 0;
|
||||
Cost cpu_run_cost = 0;
|
||||
Cost indexStartupCost;
|
||||
Cost indexTotalCost;
|
||||
Selectivity indexSelectivity;
|
||||
@ -413,6 +415,8 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
|
||||
Cost cpu_per_tuple;
|
||||
double tuples_fetched;
|
||||
double pages_fetched;
|
||||
double rand_heap_pages;
|
||||
double index_pages;
|
||||
|
||||
/* Should only be applied to base relations */
|
||||
Assert(IsA(baserel, RelOptInfo) &&
|
||||
@ -459,7 +463,8 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
|
||||
amcostestimate = (amcostestimate_function) index->amcostestimate;
|
||||
amcostestimate(root, path, loop_count,
|
||||
&indexStartupCost, &indexTotalCost,
|
||||
&indexSelectivity, &indexCorrelation);
|
||||
&indexSelectivity, &indexCorrelation,
|
||||
&index_pages);
|
||||
|
||||
/*
|
||||
* Save amcostestimate's results for possible use in bitmap scan planning.
|
||||
@ -526,6 +531,8 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
|
||||
if (indexonly)
|
||||
pages_fetched = ceil(pages_fetched * (1.0 - baserel->allvisfrac));
|
||||
|
||||
rand_heap_pages = pages_fetched;
|
||||
|
||||
max_IO_cost = (pages_fetched * spc_random_page_cost) / loop_count;
|
||||
|
||||
/*
|
||||
@ -564,6 +571,8 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
|
||||
if (indexonly)
|
||||
pages_fetched = ceil(pages_fetched * (1.0 - baserel->allvisfrac));
|
||||
|
||||
rand_heap_pages = pages_fetched;
|
||||
|
||||
/* max_IO_cost is for the perfectly uncorrelated case (csquared=0) */
|
||||
max_IO_cost = pages_fetched * spc_random_page_cost;
|
||||
|
||||
@ -583,6 +592,29 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
|
||||
min_IO_cost = 0;
|
||||
}
|
||||
|
||||
if (partial_path)
|
||||
{
|
||||
/*
|
||||
* Estimate the number of parallel workers required to scan index. Use
|
||||
* the number of heap pages computed considering heap fetches won't be
|
||||
* sequential as for parallel scans the pages are accessed in random
|
||||
* order.
|
||||
*/
|
||||
path->path.parallel_workers = compute_parallel_worker(baserel,
|
||||
(BlockNumber) rand_heap_pages,
|
||||
(BlockNumber) index_pages);
|
||||
|
||||
/*
|
||||
* Fall out if workers can't be assigned for parallel scan, because in
|
||||
* such a case this path will be rejected. So there is no benefit in
|
||||
* doing extra computation.
|
||||
*/
|
||||
if (path->path.parallel_workers <= 0)
|
||||
return;
|
||||
|
||||
path->path.parallel_aware = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now interpolate based on estimated index order correlation to get total
|
||||
* disk I/O cost for main table accesses.
|
||||
@ -602,11 +634,24 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
|
||||
startup_cost += qpqual_cost.startup;
|
||||
cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
|
||||
|
||||
run_cost += cpu_per_tuple * tuples_fetched;
|
||||
cpu_run_cost += cpu_per_tuple * tuples_fetched;
|
||||
|
||||
/* tlist eval costs are paid per output row, not per tuple scanned */
|
||||
startup_cost += path->path.pathtarget->cost.startup;
|
||||
run_cost += path->path.pathtarget->cost.per_tuple * path->path.rows;
|
||||
cpu_run_cost += path->path.pathtarget->cost.per_tuple * path->path.rows;
|
||||
|
||||
/* Adjust costing for parallelism, if used. */
|
||||
if (path->path.parallel_workers > 0)
|
||||
{
|
||||
double parallel_divisor = get_parallel_divisor(&path->path);
|
||||
|
||||
path->path.rows = clamp_row_est(path->path.rows / parallel_divisor);
|
||||
|
||||
/* The CPU cost is divided among all the workers. */
|
||||
cpu_run_cost /= parallel_divisor;
|
||||
}
|
||||
|
||||
run_cost += cpu_run_cost;
|
||||
|
||||
path->path.startup_cost = startup_cost;
|
||||
path->path.total_cost = startup_cost + run_cost;
|
||||
|
@ -813,7 +813,7 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
||||
/*
|
||||
* build_index_paths
|
||||
* Given an index and a set of index clauses for it, construct zero
|
||||
* or more IndexPaths.
|
||||
* or more IndexPaths. It also constructs zero or more partial IndexPaths.
|
||||
*
|
||||
* We return a list of paths because (1) this routine checks some cases
|
||||
* that should cause us to not generate any IndexPath, and (2) in some
|
||||
@ -1042,8 +1042,41 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
||||
NoMovementScanDirection,
|
||||
index_only_scan,
|
||||
outer_relids,
|
||||
loop_count);
|
||||
loop_count,
|
||||
false);
|
||||
result = lappend(result, ipath);
|
||||
|
||||
/*
|
||||
* If appropriate, consider parallel index scan. We don't allow
|
||||
* parallel index scan for bitmap or index only scans.
|
||||
*/
|
||||
if (index->amcanparallel && !index_only_scan &&
|
||||
rel->consider_parallel && outer_relids == NULL &&
|
||||
scantype != ST_BITMAPSCAN)
|
||||
{
|
||||
ipath = create_index_path(root, index,
|
||||
index_clauses,
|
||||
clause_columns,
|
||||
orderbyclauses,
|
||||
orderbyclausecols,
|
||||
useful_pathkeys,
|
||||
index_is_ordered ?
|
||||
ForwardScanDirection :
|
||||
NoMovementScanDirection,
|
||||
index_only_scan,
|
||||
outer_relids,
|
||||
loop_count,
|
||||
true);
|
||||
|
||||
/*
|
||||
* if, after costing the path, we find that it's not worth
|
||||
* using parallel workers, just free it.
|
||||
*/
|
||||
if (ipath->path.parallel_workers > 0)
|
||||
add_partial_path(rel, (Path *) ipath);
|
||||
else
|
||||
pfree(ipath);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1066,8 +1099,36 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
||||
BackwardScanDirection,
|
||||
index_only_scan,
|
||||
outer_relids,
|
||||
loop_count);
|
||||
loop_count,
|
||||
false);
|
||||
result = lappend(result, ipath);
|
||||
|
||||
/* If appropriate, consider parallel index scan */
|
||||
if (index->amcanparallel && !index_only_scan &&
|
||||
rel->consider_parallel && outer_relids == NULL &&
|
||||
scantype != ST_BITMAPSCAN)
|
||||
{
|
||||
ipath = create_index_path(root, index,
|
||||
index_clauses,
|
||||
clause_columns,
|
||||
NIL,
|
||||
NIL,
|
||||
useful_pathkeys,
|
||||
BackwardScanDirection,
|
||||
index_only_scan,
|
||||
outer_relids,
|
||||
loop_count,
|
||||
true);
|
||||
|
||||
/*
|
||||
* if, after costing the path, we find that it's not worth
|
||||
* using parallel workers, just free it.
|
||||
*/
|
||||
if (ipath->path.parallel_workers > 0)
|
||||
add_partial_path(rel, (Path *) ipath);
|
||||
else
|
||||
pfree(ipath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user