diff options
author | Simon Riggs <simon@2ndQuadrant.com> | 2018-03-27 19:57:02 +0100 |
---|---|---|
committer | Simon Riggs <simon@2ndQuadrant.com> | 2018-03-27 19:57:02 +0100 |
commit | c203d6cf81b4d7e43edb2b75ec1b741ba48e04e0 (patch) | |
tree | cf9e4a14290ef99232a5f5f477d5b2672df57629 /src/backend/utils/cache/relcache.c | |
parent | 1944cdc98273dbb8439ad9b387ca2858531afcf0 (diff) | |
download | postgresql-c203d6cf81b4d7e43edb2b75ec1b741ba48e04e0.tar.gz postgresql-c203d6cf81b4d7e43edb2b75ec1b741ba48e04e0.zip |
Allow HOT updates for some expression indexes
If the value of an index expression is unchanged after UPDATE,
allow HOT updates where previously we disallowed them, giving
a significant performance boost in those cases.
Particularly useful for indexes such as JSON->>field where the
JSON value changes but the indexed value does not.
Submitted as "surjective indexes" patch, now enabled by use
of new "recheck_on_update" parameter.
Author: Konstantin Knizhnik
Reviewer: Simon Riggs, with much wordsmithing and some cleanup
Diffstat (limited to 'src/backend/utils/cache/relcache.c')
-rw-r--r-- | src/backend/utils/cache/relcache.c | 112 |
1 files changed, 106 insertions, 6 deletions
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 6ab4db26bdd..46513024405 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -69,8 +69,10 @@ #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/prep.h" #include "optimizer/var.h" +#include "pgstat.h" #include "rewrite/rewriteDefine.h" #include "rewrite/rowsecurity.h" #include "storage/lmgr.h" @@ -2314,9 +2316,11 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) list_free_deep(relation->rd_fkeylist); list_free(relation->rd_indexlist); bms_free(relation->rd_indexattr); + bms_free(relation->rd_projindexattr); bms_free(relation->rd_keyattr); bms_free(relation->rd_pkattr); bms_free(relation->rd_idattr); + bms_free(relation->rd_projidx); if (relation->rd_pubactions) pfree(relation->rd_pubactions); if (relation->rd_options) @@ -4799,6 +4803,73 @@ RelationGetIndexPredicate(Relation relation) return result; } +#define HEURISTIC_MAX_HOT_RECHECK_EXPR_COST 1000 + +/* + * Check if functional index is projection: index expression returns some subset + * of its argument values. During HOT update check we handle projection indexes + * differently: instead of checking if any of attributes used in indexed + * expression were updated, we calculate and compare values of index expression + * for old and new tuple values. + * + * Decision made by this function is based on two sources: + * 1. Calculated cost of index expression: if greater than some heuristic limit + then extra comparison of index expression values is expected to be too + expensive, so we don't attempt it by default. + * 2. "recheck_on_update" index option explicitly set by user, which overrides 1) + */ +static bool IsProjectionFunctionalIndex(Relation index, IndexInfo* ii) +{ + bool is_projection = false; + + if (ii->ii_Expressions) + { + HeapTuple tuple; + Datum reloptions; + bool isnull; + QualCost index_expr_cost; + + /* by default functional index is considered as non-injective */ + is_projection = true; + + cost_qual_eval(&index_expr_cost, ii->ii_Expressions, NULL); + + /* + * If index expression is too expensive, then disable projection + * optimization, because extra evaluation of index expression is + * expected to be more expensive than index update. Currently the + * projection optimization has to calculate index expression twice + * when the value of index expression has not changed and three times + * when values differ because the expression is recalculated when + * inserting a new index entry for the changed value. + */ + if ((index_expr_cost.startup + index_expr_cost.per_tuple) > + HEURISTIC_MAX_HOT_RECHECK_EXPR_COST) + is_projection = false; + + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(RelationGetRelid(index))); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", RelationGetRelid(index)); + + reloptions = SysCacheGetAttr(RELOID, tuple, + Anum_pg_class_reloptions, &isnull); + if (!isnull) + { + GenericIndexOpts *idxopts; + + idxopts = (GenericIndexOpts *) index_generic_reloptions(reloptions, false); + + if (idxopts != NULL) + { + is_projection = idxopts->recheck_on_update; + pfree(idxopts); + } + } + ReleaseSysCache(tuple); + } + return is_projection; +} + /* * RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers * @@ -4826,24 +4897,29 @@ RelationGetIndexPredicate(Relation relation) Bitmapset * RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) { - Bitmapset *indexattrs; /* indexed columns */ + Bitmapset *indexattrs; /* columns used in non-projection indexes */ + Bitmapset *projindexattrs; /* columns used in projection indexes */ Bitmapset *uindexattrs; /* columns in unique indexes */ Bitmapset *pkindexattrs; /* columns in the primary index */ Bitmapset *idindexattrs; /* columns in the replica identity */ + Bitmapset *projindexes; /* projection indexes */ List *indexoidlist; List *newindexoidlist; Oid relpkindex; Oid relreplindex; ListCell *l; MemoryContext oldcxt; + int indexno; /* Quick exit if we already computed the result. */ if (relation->rd_indexattr != NULL) { switch (attrKind) { - case INDEX_ATTR_BITMAP_ALL: + case INDEX_ATTR_BITMAP_HOT: return bms_copy(relation->rd_indexattr); + case INDEX_ATTR_BITMAP_PROJ: + return bms_copy(relation->rd_projindexattr); case INDEX_ATTR_BITMAP_KEY: return bms_copy(relation->rd_keyattr); case INDEX_ATTR_BITMAP_PRIMARY_KEY: @@ -4890,9 +4966,12 @@ restart: * won't be returned at all by RelationGetIndexList. */ indexattrs = NULL; + projindexattrs = NULL; uindexattrs = NULL; pkindexattrs = NULL; idindexattrs = NULL; + projindexes = NULL; + indexno = 0; foreach(l, indexoidlist) { Oid indexOid = lfirst_oid(l); @@ -4943,13 +5022,22 @@ restart: } } - /* Collect all attributes used in expressions, too */ - pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs); - + /* Collect attributes used in expressions, too */ + if (IsProjectionFunctionalIndex(indexDesc, indexInfo)) + { + projindexes = bms_add_member(projindexes, indexno); + pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &projindexattrs); + } + else + { + /* Collect all attributes used in expressions, too */ + pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs); + } /* Collect all attributes in the index predicate, too */ pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs); index_close(indexDesc, AccessShareLock); + indexno += 1; } /* @@ -4976,6 +5064,8 @@ restart: bms_free(pkindexattrs); bms_free(idindexattrs); bms_free(indexattrs); + bms_free(projindexattrs); + bms_free(projindexes); goto restart; } @@ -4983,12 +5073,16 @@ restart: /* Don't leak the old values of these bitmaps, if any */ bms_free(relation->rd_indexattr); relation->rd_indexattr = NULL; + bms_free(relation->rd_projindexattr); + relation->rd_projindexattr = NULL; bms_free(relation->rd_keyattr); relation->rd_keyattr = NULL; bms_free(relation->rd_pkattr); relation->rd_pkattr = NULL; bms_free(relation->rd_idattr); relation->rd_idattr = NULL; + bms_free(relation->rd_projidx); + relation->rd_projidx = NULL; /* * Now save copies of the bitmaps in the relcache entry. We intentionally @@ -5002,13 +5096,17 @@ restart: relation->rd_pkattr = bms_copy(pkindexattrs); relation->rd_idattr = bms_copy(idindexattrs); relation->rd_indexattr = bms_copy(indexattrs); + relation->rd_projindexattr = bms_copy(projindexattrs); + relation->rd_projidx = bms_copy(projindexes); MemoryContextSwitchTo(oldcxt); /* We return our original working copy for caller to play with */ switch (attrKind) { - case INDEX_ATTR_BITMAP_ALL: + case INDEX_ATTR_BITMAP_HOT: return indexattrs; + case INDEX_ATTR_BITMAP_PROJ: + return projindexattrs; case INDEX_ATTR_BITMAP_KEY: return uindexattrs; case INDEX_ATTR_BITMAP_PRIMARY_KEY: @@ -5632,9 +5730,11 @@ load_relcache_init_file(bool shared) rel->rd_pkindex = InvalidOid; rel->rd_replidindex = InvalidOid; rel->rd_indexattr = NULL; + rel->rd_projindexattr = NULL; rel->rd_keyattr = NULL; rel->rd_pkattr = NULL; rel->rd_idattr = NULL; + rel->rd_projidx = NULL; rel->rd_pubactions = NULL; rel->rd_statvalid = false; rel->rd_statlist = NIL; |