From: Andres Freund Date: Sat, 30 Mar 2019 23:40:33 +0000 (-0700) Subject: tableam: Move heap specific logic from estimate_rel_size below tableam. X-Git-Tag: REL_12_BETA1~361 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=696d78469f3714a8159f7a145af1fe9179fe3291;p=postgresql tableam: Move heap specific logic from estimate_rel_size below tableam. This just moves the table/matview[/toast] determination of relation size to a callback, and uses a copy of the existing logic to implement that callback for heap. It probably would make sense to also move the index specific logic into a callback, so the metapage handling (and probably more) can be index specific. But that's a separate task. Author: Andres Freund Discussion: https://postgr.es/m/20180703070645.wchpu5muyto5n647@alap3.anarazel.de --- diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 33dac14edc..cf6e35670b 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -19,6 +19,8 @@ */ #include "postgres.h" +#include + #include "miscadmin.h" #include "access/genam.h" @@ -33,6 +35,7 @@ #include "catalog/storage_xlog.h" #include "commands/progress.h" #include "executor/executor.h" +#include "optimizer/plancat.h" #include "pgstat.h" #include "storage/bufmgr.h" #include "storage/bufpage.h" @@ -1870,6 +1873,114 @@ reform_and_rewrite_tuple(HeapTuple tuple, } +/* ------------------------------------------------------------------------ + * Planner related callbacks for the heap AM + * ------------------------------------------------------------------------ + */ + +static void +heapam_estimate_rel_size(Relation rel, int32 *attr_widths, + BlockNumber *pages, double *tuples, + double *allvisfrac) +{ + BlockNumber curpages; + BlockNumber relpages; + double reltuples; + BlockNumber relallvisible; + double density; + + /* it has storage, ok to call the smgr */ + curpages = RelationGetNumberOfBlocks(rel); + + /* coerce values in pg_class to more desirable types */ + relpages = (BlockNumber) rel->rd_rel->relpages; + reltuples = (double) rel->rd_rel->reltuples; + relallvisible = (BlockNumber) rel->rd_rel->relallvisible; + + /* + * HACK: if the relation has never yet been vacuumed, use a minimum size + * estimate of 10 pages. The idea here is to avoid assuming a + * newly-created table is really small, even if it currently is, because + * that may not be true once some data gets loaded into it. Once a vacuum + * or analyze cycle has been done on it, it's more reasonable to believe + * the size is somewhat stable. + * + * (Note that this is only an issue if the plan gets cached and used again + * after the table has been filled. What we're trying to avoid is using a + * nestloop-type plan on a table that has grown substantially since the + * plan was made. Normally, autovacuum/autoanalyze will occur once enough + * inserts have happened and cause cached-plan invalidation; but that + * doesn't happen instantaneously, and it won't happen at all for cases + * such as temporary tables.) + * + * We approximate "never vacuumed" by "has relpages = 0", which means this + * will also fire on genuinely empty relations. Not great, but + * fortunately that's a seldom-seen case in the real world, and it + * shouldn't degrade the quality of the plan too much anyway to err in + * this direction. + * + * If the table has inheritance children, we don't apply this heuristic. + * Totally empty parent tables are quite common, so we should be willing + * to believe that they are empty. + */ + if (curpages < 10 && + relpages == 0 && + !rel->rd_rel->relhassubclass) + curpages = 10; + + /* report estimated # pages */ + *pages = curpages; + /* quick exit if rel is clearly empty */ + if (curpages == 0) + { + *tuples = 0; + *allvisfrac = 0; + return; + } + + /* estimate number of tuples from previous tuple density */ + if (relpages > 0) + density = reltuples / (double) relpages; + else + { + /* + * When we have no data because the relation was truncated, estimate + * tuple width from attribute datatypes. We assume here that the + * pages are completely full, which is OK for tables (since they've + * presumably not been VACUUMed yet) but is probably an overestimate + * for indexes. Fortunately get_relation_info() can clamp the + * overestimate to the parent table's size. + * + * Note: this code intentionally disregards alignment considerations, + * because (a) that would be gilding the lily considering how crude + * the estimate is, and (b) it creates platform dependencies in the + * default plans which are kind of a headache for regression testing. + */ + int32 tuple_width; + + tuple_width = get_rel_data_width(rel, attr_widths); + tuple_width += MAXALIGN(SizeofHeapTupleHeader); + tuple_width += sizeof(ItemIdData); + /* note: integer division is intentional here */ + density = (BLCKSZ - SizeOfPageHeaderData) / tuple_width; + } + *tuples = rint(density * (double) curpages); + + /* + * We use relallvisible as-is, rather than scaling it up like we do for + * the pages and tuples counts, on the theory that any pages added since + * the last VACUUM are most likely not marked all-visible. But costsize.c + * wants it converted to a fraction. + */ + if (relallvisible == 0 || curpages <= 0) + *allvisfrac = 0; + else if ((double) relallvisible >= curpages) + *allvisfrac = 1; + else + *allvisfrac = (double) relallvisible / curpages; +} + + /* ------------------------------------------------------------------------ * Definition of the heap table access method. * ------------------------------------------------------------------------ @@ -1915,6 +2026,8 @@ static const TableAmRoutine heapam_methods = { .scan_analyze_next_tuple = heapam_scan_analyze_next_tuple, .index_build_range_scan = heapam_index_build_range_scan, .index_validate_scan = heapam_index_validate_scan, + + .relation_estimate_size = heapam_estimate_rel_size, }; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 5bda0e0ea2..89f909bceb 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -20,6 +20,7 @@ #include "access/genam.h" #include "access/htup_details.h" #include "access/nbtree.h" +#include "access/tableam.h" #include "access/sysattr.h" #include "access/table.h" #include "access/transam.h" @@ -64,7 +65,6 @@ static void get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel, Relation relation, bool inhparent); static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel, List *idxExprs); -static int32 get_rel_data_width(Relation rel, int32 *attr_widths); static List *get_relation_constraints(PlannerInfo *root, Oid relationObjectId, RelOptInfo *rel, bool include_notnull); @@ -948,47 +948,26 @@ estimate_rel_size(Relation rel, int32 *attr_widths, switch (rel->rd_rel->relkind) { case RELKIND_RELATION: - case RELKIND_INDEX: case RELKIND_MATVIEW: case RELKIND_TOASTVALUE: - /* it has storage, ok to call the smgr */ - curpages = RelationGetNumberOfBlocks(rel); + table_relation_estimate_size(rel, attr_widths, pages, tuples, + allvisfrac); + break; + + case RELKIND_INDEX: /* - * HACK: if the relation has never yet been vacuumed, use a - * minimum size estimate of 10 pages. The idea here is to avoid - * assuming a newly-created table is really small, even if it - * currently is, because that may not be true once some data gets - * loaded into it. Once a vacuum or analyze cycle has been done - * on it, it's more reasonable to believe the size is somewhat - * stable. - * - * (Note that this is only an issue if the plan gets cached and - * used again after the table has been filled. What we're trying - * to avoid is using a nestloop-type plan on a table that has - * grown substantially since the plan was made. Normally, - * autovacuum/autoanalyze will occur once enough inserts have - * happened and cause cached-plan invalidation; but that doesn't - * happen instantaneously, and it won't happen at all for cases - * such as temporary tables.) - * - * We approximate "never vacuumed" by "has relpages = 0", which - * means this will also fire on genuinely empty relations. Not - * great, but fortunately that's a seldom-seen case in the real - * world, and it shouldn't degrade the quality of the plan too - * much anyway to err in this direction. - * - * There are two exceptions wherein we don't apply this heuristic. - * One is if the table has inheritance children. Totally empty - * parent tables are quite common, so we should be willing to - * believe that they are empty. Also, we don't apply the 10-page - * minimum to indexes. + * XXX: It'd probably be good to move this into a callback, + * individual index types e.g. know if they have a metapage. */ - if (curpages < 10 && - rel->rd_rel->relpages == 0 && - !rel->rd_rel->relhassubclass && - rel->rd_rel->relkind != RELKIND_INDEX) - curpages = 10; + + /* it has storage, ok to call the smgr */ + curpages = RelationGetNumberOfBlocks(rel); + + /* coerce values in pg_class to more desirable types */ + relpages = (BlockNumber) rel->rd_rel->relpages; + reltuples = (double) rel->rd_rel->reltuples; + relallvisible = (BlockNumber) rel->rd_rel->relallvisible; /* report estimated # pages */ *pages = curpages; @@ -1005,13 +984,12 @@ estimate_rel_size(Relation rel, int32 *attr_widths, relallvisible = (BlockNumber) rel->rd_rel->relallvisible; /* - * If it's an index, discount the metapage while estimating the - * number of tuples. This is a kluge because it assumes more than - * it ought to about index structure. Currently it's OK for - * btree, hash, and GIN indexes but suspect for GiST indexes. + * Discount the metapage while estimating the number of tuples. + * This is a kluge because it assumes more than it ought to about + * index structure. Currently it's OK for btree, hash, and GIN + * indexes but suspect for GiST indexes. */ - if (rel->rd_rel->relkind == RELKIND_INDEX && - relpages > 0) + if (relpages > 0) { curpages--; relpages--; @@ -1036,6 +1014,8 @@ estimate_rel_size(Relation rel, int32 *attr_widths, * considering how crude the estimate is, and (b) it creates * platform dependencies in the default plans which are kind * of a headache for regression testing. + * + * XXX: Should this logic be more index specific? */ int32 tuple_width; @@ -1060,6 +1040,7 @@ estimate_rel_size(Relation rel, int32 *attr_widths, else *allvisfrac = (double) relallvisible / curpages; break; + case RELKIND_SEQUENCE: /* Sequences always have a known size */ *pages = 1; @@ -1095,7 +1076,7 @@ estimate_rel_size(Relation rel, int32 *attr_widths, * since they might be mostly NULLs, treating them as zero-width is not * necessarily the wrong thing anyway. */ -static int32 +int32 get_rel_data_width(Relation rel, int32 *attr_widths) { int32 tuple_width = 0; diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 1f0eb2fdb7..873ad15313 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -491,6 +491,22 @@ typedef struct TableAmRoutine Snapshot snapshot, struct ValidateIndexState *state); + + /* ------------------------------------------------------------------------ + * Planner related functions. + * ------------------------------------------------------------------------ + */ + + /* + * See table_relation_estimate_size(). + * + * While block oriented, it shouldn't be too hard to for an AM that + * doesn't internally use blocks to convert into a usable representation. + */ + void (*relation_estimate_size) (Relation rel, int32 *attr_widths, + BlockNumber *pages, double *tuples, + double *allvisfrac); + } TableAmRoutine; @@ -1286,6 +1302,25 @@ table_index_validate_scan(Relation heap_rel, } +/* ---------------------------------------------------------------------------- + * Planner related functionality + * ---------------------------------------------------------------------------- + */ + +/* + * Estimate the current size of the relation, as an AM specific workhorse for + * estimate_rel_size(). Look there for an explanation of the parameters. + */ +static inline void +table_relation_estimate_size(Relation rel, int32 *attr_widths, + BlockNumber *pages, double *tuples, + double *allvisfrac) +{ + rel->rd_tableam->relation_estimate_size(rel, attr_widths, pages, tuples, + allvisfrac); +} + + /* ---------------------------------------------------------------------------- * Functions to make modifications a bit simpler. * ---------------------------------------------------------------------------- diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index c556e0f258..3254613e6e 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -33,6 +33,7 @@ extern List *infer_arbiter_indexes(PlannerInfo *root); extern void estimate_rel_size(Relation rel, int32 *attr_widths, BlockNumber *pages, double *tuples, double *allvisfrac); +extern int32 get_rel_data_width(Relation rel, int32 *attr_widths); extern int32 get_relation_data_width(Oid relid, int32 *attr_widths); extern bool relation_excluded_by_constraints(PlannerInfo *root,