]> granicus.if.org Git - postgresql/commitdiff
tableam: Move heap specific logic from estimate_rel_size below tableam.
authorAndres Freund <andres@anarazel.de>
Sat, 30 Mar 2019 23:40:33 +0000 (16:40 -0700)
committerAndres Freund <andres@anarazel.de>
Sun, 31 Mar 2019 02:26:36 +0000 (19:26 -0700)
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

src/backend/access/heap/heapam_handler.c
src/backend/optimizer/util/plancat.c
src/include/access/tableam.h
src/include/optimizer/plancat.h

index 33dac14edc2341b65a5cbea994b088e60213ebda..cf6e35670b0e24e81d63a0fa1c763dad36448e1c 100644 (file)
@@ -19,6 +19,8 @@
  */
 #include "postgres.h"
 
+#include <math.h>
+
 #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,
 };
 
 
index 5bda0e0ea289aa63227cac00912d5a075abbf9f8..89f909bceb36df9fb235119eeb37908f7d06913e 100644 (file)
@@ -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;
index 1f0eb2fdb7cd80f0ff56b1e65769db18d7c73e98..873ad1531341d8df2e4496eea91f0dc63865da5a 100644 (file)
@@ -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.
  * ----------------------------------------------------------------------------
index c556e0f2589486dd169ec77a3c0a7f1f425dfea3..3254613e6e4ca75d88108f6a2cd0d748c72b3ee5 100644 (file)
@@ -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,