mirror of
https://github.com/postgres/postgres.git
synced 2025-10-27 00:12:01 +03:00
Add TID Range Scans to support efficient scanning ranges of TIDs
This adds a new executor node named TID Range Scan. The query planner will generate paths for TID Range scans when quals are discovered on base relations which search for ranges on the table's ctid column. These ranges may be open at either end. For example, WHERE ctid >= '(10,0)'; will return all tuples on page 10 and over. To support this, two new optional callback functions have been added to table AM. scan_set_tidrange is used to set the scan range to just the given range of TIDs. scan_getnextslot_tidrange fetches the next tuple in the given range. For AMs were scanning ranges of TIDs would not make sense, these functions can be set to NULL in the TableAmRoutine. The query planner won't generate TID Range Scan Paths in that case. Author: Edmund Horner, David Rowley Reviewed-by: David Rowley, Tomas Vondra, Tom Lane, Andres Freund, Zhihong Yu Discussion: https://postgr.es/m/CAMyN-kB-nFTkF=VA_JPwFNo08S0d-Yk0F741S2B7LDmYAi8eyA@mail.gmail.com
This commit is contained in:
@@ -1283,6 +1283,101 @@ cost_tidscan(Path *path, PlannerInfo *root,
|
||||
path->total_cost = startup_cost + run_cost;
|
||||
}
|
||||
|
||||
/*
|
||||
* cost_tidrangescan
|
||||
* Determines and sets the costs of scanning a relation using a range of
|
||||
* TIDs for 'path'
|
||||
*
|
||||
* 'baserel' is the relation to be scanned
|
||||
* 'tidrangequals' is the list of TID-checkable range quals
|
||||
* 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
|
||||
*/
|
||||
void
|
||||
cost_tidrangescan(Path *path, PlannerInfo *root,
|
||||
RelOptInfo *baserel, List *tidrangequals,
|
||||
ParamPathInfo *param_info)
|
||||
{
|
||||
Selectivity selectivity;
|
||||
double pages;
|
||||
Cost startup_cost = 0;
|
||||
Cost run_cost = 0;
|
||||
QualCost qpqual_cost;
|
||||
Cost cpu_per_tuple;
|
||||
QualCost tid_qual_cost;
|
||||
double ntuples;
|
||||
double nseqpages;
|
||||
double spc_random_page_cost;
|
||||
double spc_seq_page_cost;
|
||||
|
||||
/* Should only be applied to base relations */
|
||||
Assert(baserel->relid > 0);
|
||||
Assert(baserel->rtekind == RTE_RELATION);
|
||||
|
||||
/* Mark the path with the correct row estimate */
|
||||
if (param_info)
|
||||
path->rows = param_info->ppi_rows;
|
||||
else
|
||||
path->rows = baserel->rows;
|
||||
|
||||
/* Count how many tuples and pages we expect to scan */
|
||||
selectivity = clauselist_selectivity(root, tidrangequals, baserel->relid,
|
||||
JOIN_INNER, NULL);
|
||||
pages = ceil(selectivity * baserel->pages);
|
||||
|
||||
if (pages <= 0.0)
|
||||
pages = 1.0;
|
||||
|
||||
/*
|
||||
* The first page in a range requires a random seek, but each subsequent
|
||||
* page is just a normal sequential page read. NOTE: it's desirable for
|
||||
* TID Range Scans to cost more than the equivalent Sequential Scans,
|
||||
* because Seq Scans have some performance advantages such as scan
|
||||
* synchronization and parallelizability, and we'd prefer one of them to
|
||||
* be picked unless a TID Range Scan really is better.
|
||||
*/
|
||||
ntuples = selectivity * baserel->tuples;
|
||||
nseqpages = pages - 1.0;
|
||||
|
||||
if (!enable_tidscan)
|
||||
startup_cost += disable_cost;
|
||||
|
||||
/*
|
||||
* The TID qual expressions will be computed once, any other baserestrict
|
||||
* quals once per retrieved tuple.
|
||||
*/
|
||||
cost_qual_eval(&tid_qual_cost, tidrangequals, root);
|
||||
|
||||
/* fetch estimated page cost for tablespace containing table */
|
||||
get_tablespace_page_costs(baserel->reltablespace,
|
||||
&spc_random_page_cost,
|
||||
&spc_seq_page_cost);
|
||||
|
||||
/* disk costs; 1 random page and the remainder as seq pages */
|
||||
run_cost += spc_random_page_cost + spc_seq_page_cost * nseqpages;
|
||||
|
||||
/* Add scanning CPU costs */
|
||||
get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
|
||||
|
||||
/*
|
||||
* XXX currently we assume TID quals are a subset of qpquals at this
|
||||
* point; they will be removed (if possible) when we create the plan, so
|
||||
* we subtract their cost from the total qpqual cost. (If the TID quals
|
||||
* can't be removed, this is a mistake and we're going to underestimate
|
||||
* the CPU cost a bit.)
|
||||
*/
|
||||
startup_cost += qpqual_cost.startup + tid_qual_cost.per_tuple;
|
||||
cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple -
|
||||
tid_qual_cost.per_tuple;
|
||||
run_cost += cpu_per_tuple * ntuples;
|
||||
|
||||
/* tlist eval costs are paid per output row, not per tuple scanned */
|
||||
startup_cost += path->pathtarget->cost.startup;
|
||||
run_cost += path->pathtarget->cost.per_tuple * path->rows;
|
||||
|
||||
path->startup_cost = startup_cost;
|
||||
path->total_cost = startup_cost + run_cost;
|
||||
}
|
||||
|
||||
/*
|
||||
* cost_subqueryscan
|
||||
* Determines and returns the cost of scanning a subquery RTE.
|
||||
|
||||
Reference in New Issue
Block a user