diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 13e076c..aca28cc 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1737,6 +1737,26 @@ include_dir 'conf.d' + + relation_cache_max_size (integer) + + relation_cache_max_size configuration + parameter + + + + + Specifies the maximum total amount of memory allowed for all system + relation caches in kilobytes. The value defaults to 0, indicating that + pruning by this parameter is disabled at all. After the amount of + memory used by all relation caches exceed this size, a new cache entry + creation will remove one or more not-recently-used cache entries. This + means frequent creation of new cache entry may lead to a slight + slowdown of queries. + + + + max_stack_depth (integer) diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c index 6a85e14..98df265 100644 --- a/src/backend/commands/policy.c +++ b/src/backend/commands/policy.c @@ -200,12 +200,12 @@ RelationBuildRowSecurity(Relation relation) * relation's row security policy. This makes it easy to clean up during * a relcache flush. */ - rscxt = AllocSetContextCreate(CacheMemoryContext, + rscxt = AllocSetContextCreate(RelCacheMemoryContext, "row security descriptor", ALLOCSET_SMALL_SIZES); /* - * Since rscxt lives under CacheMemoryContext, it is long-lived. Use a + * Since rscxt lives under RelCacheMemoryContext, it is long-lived. Use a * PG_TRY block to ensure it'll get freed if we fail partway through. */ PG_TRY(); diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 3ae2640..57ac2ef 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -1921,7 +1921,7 @@ EnableDisableTrigger(Relation rel, const char *tgname, * Build trigger data to attach to the given relcache entry. * * Note that trigger data attached to a relcache entry must be stored in - * CacheMemoryContext to ensure it survives as long as the relcache entry. + * RelCacheMemoryContext to ensure it survives as long as the relcache entry. * But we should be running in a less long-lived working context. To avoid * leaking cache memory if this routine fails partway through, we build a * temporary TriggerDesc in working memory and then copy the completed @@ -2067,7 +2067,7 @@ RelationBuildTriggers(Relation relation) SetTriggerFlags(trigdesc, &(triggers[i])); /* Copy completed trigdesc into cache storage */ - oldContext = MemoryContextSwitchTo(CacheMemoryContext); + oldContext = MemoryContextSwitchTo(RelCacheMemoryContext); relation->trigdesc = CopyTriggerDesc(trigdesc); MemoryContextSwitchTo(oldContext); diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index c917ec4..eba5a25 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -434,8 +434,8 @@ GetFdwRoutineForRelation(Relation relation, bool makecopy) /* Get the info by consulting the catalogs and the FDW code */ fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(relation)); - /* Save the data for later reuse in CacheMemoryContext */ - cfdwroutine = (FdwRoutine *) MemoryContextAlloc(CacheMemoryContext, + /* Save the data for later reuse in RelCacheMemoryContext */ + cfdwroutine = (FdwRoutine *) MemoryContextAlloc(RelCacheMemoryContext, sizeof(FdwRoutine)); memcpy(cfdwroutine, fdwroutine, sizeof(FdwRoutine)); relation->rd_fdwroutine = cfdwroutine; diff --git a/src/backend/partitioning/partdesc.c b/src/backend/partitioning/partdesc.c index e436d1e..9238be0 100644 --- a/src/backend/partitioning/partdesc.c +++ b/src/backend/partitioning/partdesc.c @@ -174,7 +174,7 @@ RelationBuildPartitionDesc(Relation rel) } /* Now build the actual relcache partition descriptor */ - rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext, + rel->rd_pdcxt = AllocSetContextCreate(RelCacheMemoryContext, "partition descriptor", ALLOCSET_SMALL_SIZES); MemoryContextCopyAndSetIdentifier(rel->rd_pdcxt, diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c index 8f43d68..c6f7f8f 100644 --- a/src/backend/utils/cache/partcache.c +++ b/src/backend/utils/cache/partcache.c @@ -45,10 +45,10 @@ static List *generate_partition_qual(Relation rel); * * Partitioning key data is a complex structure; to avoid complicated logic to * free individual elements whenever the relcache entry is flushed, we give it - * its own memory context, child of CacheMemoryContext, which can easily be + * its own memory context, child of RelCacheMemoryContext, which can easily be * deleted on its own. To avoid leaking memory in that context in case of an * error partway through this function, the context is initially created as a - * child of CurTransactionContext and only re-parented to CacheMemoryContext + * child of CurTransactionContext and only re-parented to RelCacheMemoryContext * at the end, when no further errors are possible. Also, we don't make this * context the current context except in very brief code sections, out of fear * that some of our callees allocate memory on their own which would be leaked @@ -239,7 +239,7 @@ RelationBuildPartitionKey(Relation relation) * Success --- reparent our context and make the relcache point to the * newly constructed key */ - MemoryContextSetParent(partkeycxt, CacheMemoryContext); + MemoryContextSetParent(partkeycxt, RelCacheMemoryContext); relation->rd_partkeycxt = partkeycxt; relation->rd_partkey = key; } @@ -374,7 +374,7 @@ generate_partition_qual(Relation rel) elog(ERROR, "unexpected whole-row reference found in partition key"); /* Save a copy in the relcache */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); rel->rd_partcheck = copyObject(result); MemoryContextSwitchTo(oldcxt); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 64f3c2e..9ee2f4f 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -135,6 +135,15 @@ typedef struct relidcacheent static HTAB *RelationIdCache; /* + * GUC for limit by the number of entries. Entries are managed by LRU list + * and removed when the number of them goes above relation_cache_max_size + * in kilobytes + */ +int relation_cache_max_size = 0; + +static dlist_head rel_lruhead = DLIST_STATIC_INIT(rel_lruhead); + +/* * This flag is false until we have prepared the critical relcache entries * that are needed to do indexscans on the tables read by relcache building. */ @@ -201,6 +210,12 @@ do { \ Relation _old_rel = hentry->reldesc; \ Assert(replace_allowed); \ hentry->reldesc = (RELATION); \ + /* nailed entries are excluded from LRU list not to be blown away */ \ + if (!((RELATION)->rd_isnailed)) \ + { \ + dlist_push_tail(&rel_lruhead, &((RELATION)->rd_lrunode)); \ + dlist_delete(&_old_rel->rd_lrunode); \ + } \ if (RelationHasReferenceCountZero(_old_rel)) \ RelationDestroyRelation(_old_rel, false); \ else if (!IsBootstrapProcessingMode()) \ @@ -208,7 +223,11 @@ do { \ RelationGetRelationName(_old_rel)); \ } \ else \ + { \ hentry->reldesc = (RELATION); \ + if (!((RELATION)->rd_isnailed)) \ + dlist_push_tail(&rel_lruhead, &((RELATION)->rd_lrunode)); \ + } \ } while(0) #define RelationIdCacheLookup(ID, RELATION) \ @@ -218,7 +237,11 @@ do { \ (void *) &(ID), \ HASH_FIND, NULL); \ if (hentry) \ + { \ RELATION = hentry->reldesc; \ + if (!(RELATION)->rd_isnailed) \ + dlist_move_tail(&rel_lruhead, &((RELATION)->rd_lrunode)); \ + } \ else \ RELATION = NULL; \ } while(0) @@ -229,6 +252,8 @@ do { \ hentry = (RelIdCacheEnt *) hash_search(RelationIdCache, \ (void *) &((RELATION)->rd_id), \ HASH_REMOVE, NULL); \ + if (hentry && !(RELATION)->rd_isnailed) \ + dlist_delete(&(RELATION)->rd_lrunode); \ if (hentry == NULL) \ elog(WARNING, "failed to delete relcache entry for OID %u", \ (RELATION)->rd_id); \ @@ -297,6 +322,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid, StrategyNumber numSupport); static void RelationCacheInitFileRemoveInDir(const char *tblspcpath); static void unlink_initfile(const char *initfilename, int elevel); +static void CreateRelCacheMemoryContext(void); +static void CleanupOldRelationCache(void); /* @@ -390,8 +417,8 @@ AllocateRelationDesc(Form_pg_class relp) MemoryContext oldcxt; Form_pg_class relationForm; - /* Relcache entries must live in CacheMemoryContext */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + /* Relcache entries must live in RelCacheMemoryContext */ + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); /* * allocate and zero space for new relation descriptor @@ -482,7 +509,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) */ if (options) { - relation->rd_options = MemoryContextAlloc(CacheMemoryContext, + relation->rd_options = MemoryContextAlloc(RelCacheMemoryContext, VARSIZE(options)); memcpy(relation->rd_options, options, VARSIZE(options)); pfree(options); @@ -512,7 +539,7 @@ RelationBuildTupleDesc(Relation relation) relation->rd_att->tdtypeid = relation->rd_rel->reltype; relation->rd_att->tdtypmod = -1; /* unnecessary, but... */ - constr = (TupleConstr *) MemoryContextAlloc(CacheMemoryContext, + constr = (TupleConstr *) MemoryContextAlloc(RelCacheMemoryContext, sizeof(TupleConstr)); constr->has_not_null = false; constr->has_generated_stored = false; @@ -576,7 +603,7 @@ RelationBuildTupleDesc(Relation relation) { if (attrdef == NULL) attrdef = (AttrDefault *) - MemoryContextAllocZero(CacheMemoryContext, + MemoryContextAllocZero(RelCacheMemoryContext, RelationGetNumberOfAttributes(relation) * sizeof(AttrDefault)); attrdef[ndef].adnum = attnum; @@ -606,7 +633,7 @@ RelationBuildTupleDesc(Relation relation) if (attrmiss == NULL) attrmiss = (AttrMissing *) - MemoryContextAllocZero(CacheMemoryContext, + MemoryContextAllocZero(RelCacheMemoryContext, relation->rd_rel->relnatts * sizeof(AttrMissing)); @@ -627,7 +654,7 @@ RelationBuildTupleDesc(Relation relation) else { /* otherwise copy in the correct context */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); attrmiss[attnum - 1].am_value = datumCopy(missval, attp->attbyval, attp->attlen); @@ -700,7 +727,7 @@ RelationBuildTupleDesc(Relation relation) { constr->num_check = relation->rd_rel->relchecks; constr->check = (ConstrCheck *) - MemoryContextAllocZero(CacheMemoryContext, + MemoryContextAllocZero(RelCacheMemoryContext, constr->num_check * sizeof(ConstrCheck)); CheckConstraintFetch(relation); } @@ -747,7 +774,7 @@ RelationBuildRuleLock(Relation relation) /* * Make the private context. Assume it'll not contain much data. */ - rulescxt = AllocSetContextCreate(CacheMemoryContext, + rulescxt = AllocSetContextCreate(RelCacheMemoryContext, "relation rules", ALLOCSET_SMALL_SIZES); relation->rd_rulescxt = rulescxt; @@ -1011,6 +1038,48 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2) } /* + * CleaunupOldRelationCache + * Clean up old entries if the amount of memory exceeds relation_cache_max_size + * to prevent relcache from bloating. + */ +static void +CleanupOldRelationCache(void) +{ + Relation relation; + dlist_mutable_iter iter; + + if (relation_cache_max_size == 0 || + MemoryContextGetUsedspace(RelCacheMemoryContext) <= + (Size) relation_cache_max_size * 1024) + return; + + /* Scan over LRU to find entries to remove */ + dlist_foreach_modify(iter, &rel_lruhead) + { + relation = dlist_container(RelationData, rd_lrunode, iter.cur); + + /* check against global size */ + if (MemoryContextGetUsedspace(RelCacheMemoryContext) <= + (Size) relation_cache_max_size * 1024) + break; + + /* + * We don't remove referenced entries. Nailed entries are not removed + * as well but we don't care because nailed ones are not added to LRU + * list in the first place. + */ + if (!RelationHasReferenceCountZero(relation)) + continue; + + elog(DEBUG1, "pruning relation cache (id = %d, %s)", + relation->rd_id, RelationGetRelationName(relation)); + RelationClearRelation(relation, false); + } + elog(DEBUG1, "current RelCacheMemoryContext size (kB): %d", + (int) MemoryContextGetUsedspace(RelCacheMemoryContext) / 1024); +} + +/* * RelationBuildDesc * * Build a relation descriptor. The caller must hold at least @@ -1243,7 +1312,14 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) * we'll elog a WARNING and leak the already-present entry. */ if (insertIt) + { + /* + * Before trying to expand hash table, cleanup old entries to supress + * memory usage under relation_cache_max_size + */ + CleanupOldRelationCache(); RelationCacheInsert(relation, true); + } /* It's fully valid */ relation->rd_isvalid = true; @@ -1383,7 +1459,7 @@ RelationInitIndexAccessInfo(Relation relation) if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for index %u", RelationGetRelid(relation)); - oldcontext = MemoryContextSwitchTo(CacheMemoryContext); + oldcontext = MemoryContextSwitchTo(RelCacheMemoryContext); relation->rd_indextuple = heap_copytuple(tuple); relation->rd_index = (Form_pg_index) GETSTRUCT(relation->rd_indextuple); MemoryContextSwitchTo(oldcontext); @@ -1411,7 +1487,7 @@ RelationInitIndexAccessInfo(Relation relation) * a context, and not just a couple of pallocs, is so that we won't leak * any subsidiary info attached to fmgr lookup records. */ - indexcxt = AllocSetContextCreate(CacheMemoryContext, + indexcxt = AllocSetContextCreate(RelCacheMemoryContext, "index info", ALLOCSET_SMALL_SIZES); relation->rd_indexcxt = indexcxt; @@ -1795,7 +1871,7 @@ RelationInitTableAccessMethod(Relation relation) * during bootstrap or before RelationCacheInitializePhase3 runs, and none of * these properties matter then...) * - * NOTE: we assume we are already switched into CacheMemoryContext. + * NOTE: we assume we are already switched into RelCacheMemoryContext. */ static void formrdesc(const char *relationName, Oid relationReltype, @@ -2341,6 +2417,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) else FreeTupleDesc(relation->rd_att); } + FreeTriggerDesc(relation->trigdesc); list_free_deep(relation->rd_fkeylist); list_free(relation->rd_indexlist); @@ -2580,7 +2657,7 @@ RelationClearRelation(Relation relation, bool rebuild) memcpy(newrel, relation, sizeof(RelationData)); memcpy(relation, &tmpstruct, sizeof(RelationData)); } - + /* rd_smgr must not be swapped, due to back-links from smgr level */ SWAPFIELD(SMgrRelation, rd_smgr); /* rd_refcnt must be preserved */ @@ -2590,6 +2667,8 @@ RelationClearRelation(Relation relation, bool rebuild) /* creation sub-XIDs must be preserved */ SWAPFIELD(SubTransactionId, rd_createSubid); SWAPFIELD(SubTransactionId, rd_newRelfilenodeSubid); + /* LRU node should be preserved because it's still in dlinked list */ + SWAPFIELD(dlist_node, rd_lrunode); /* un-swap rd_rel pointers, swap contents instead */ SWAPFIELD(Form_pg_class, rd_rel); /* ... but actually, we don't have to update newrel->rd_rel */ @@ -2888,7 +2967,7 @@ RememberToFreeTupleDescAtEOX(TupleDesc td) { MemoryContext oldcxt; - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc)); EOXactTupleDescArrayLen = 16; @@ -3246,10 +3325,10 @@ RelationBuildLocalRelation(const char *relname, /* * switch to the cache context to create the relcache entry. */ - if (!CacheMemoryContext) - CreateCacheMemoryContext(); + if (!RelCacheMemoryContext) + CreateRelCacheMemoryContext(); - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); /* * allocate a new relation descriptor and fill in basic state fields. @@ -3381,6 +3460,12 @@ RelationBuildLocalRelation(const char *relname, RelationInitTableAccessMethod(rel); /* + * Before trying to expand hash table, cleanup old entries to supress + * memory usage under relation_cache_max_size + */ + CleanupOldRelationCache(); + + /* * Okay to insert into the relcache hash table. * * Ordinarily, there should certainly not be an existing hash entry for @@ -3568,10 +3653,10 @@ RelationCacheInitialize(void) HASHCTL ctl; /* - * make sure cache memory context exists + * make sure relation cache memory context exists */ - if (!CacheMemoryContext) - CreateCacheMemoryContext(); + if (!RelCacheMemoryContext) + CreateRelCacheMemoryContext(); /* * create hashtable that indexes the relcache @@ -3617,9 +3702,9 @@ RelationCacheInitializePhase2(void) return; /* - * switch to cache memory context + * switch to relation cache memory context */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); /* * Try to load the shared relcache cache file. If unsuccessful, bootstrap @@ -3672,9 +3757,9 @@ RelationCacheInitializePhase3(void) RelationMapInitializePhase3(); /* - * switch to cache memory context + * switch to relation cache memory context */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); /* * Try to load the local relcache cache file. If unsuccessful, bootstrap @@ -3959,6 +4044,28 @@ RelationCacheInitializePhase3(void) } /* + * Routine for creating relation cache context + * if it doesn't exist yet. + * + * RelCacheMemoryContext is set under CacheMemoryContext + * to calculate memory usage easily and make MemoryContextStats() output + * organized. + */ +static void +CreateRelCacheMemoryContext(void) +{ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + if (!RelCacheMemoryContext) + { + RelCacheMemoryContext = AllocSetContextCreate(CacheMemoryContext, + "RelCacheMemoryContext", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSetCollectGroupSize(RelCacheMemoryContext); + } +} + +/* * Load one critical system index into the relcache * * indexoid is the OID of the target index, heapoid is the OID of the catalog @@ -4005,7 +4112,7 @@ BuildHardcodedDescriptor(int natts, const FormData_pg_attribute *attrs) MemoryContext oldcxt; int i; - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); result = CreateTemplateTupleDesc(natts); result->tdtypeid = RECORDOID; /* not right, but we don't care */ @@ -4109,7 +4216,7 @@ AttrDefaultFetch(Relation relation) /* detoast and convert to cstring in caller's context */ char *s = TextDatumGetCString(val); - attrdef[i].adbin = MemoryContextStrdup(CacheMemoryContext, s); + attrdef[i].adbin = MemoryContextStrdup(RelCacheMemoryContext, s); pfree(s); } break; @@ -4164,7 +4271,7 @@ CheckConstraintFetch(Relation relation) check[found].ccvalid = conform->convalidated; check[found].ccnoinherit = conform->connoinherit; - check[found].ccname = MemoryContextStrdup(CacheMemoryContext, + check[found].ccname = MemoryContextStrdup(RelCacheMemoryContext, NameStr(conform->conname)); /* Grab and test conbin is actually set */ @@ -4177,7 +4284,7 @@ CheckConstraintFetch(Relation relation) /* detoast and convert to cstring in caller's context */ s = TextDatumGetCString(val); - check[found].ccbin = MemoryContextStrdup(CacheMemoryContext, s); + check[found].ccbin = MemoryContextStrdup(RelCacheMemoryContext, s); pfree(s); found++; @@ -4287,7 +4394,7 @@ RelationGetFKeyList(Relation relation) table_close(conrel, AccessShareLock); /* Now save a copy of the completed list in the relcache entry. */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); oldlist = relation->rd_fkeylist; relation->rd_fkeylist = copyObject(result); relation->rd_fkeyvalid = true; @@ -4406,7 +4513,7 @@ RelationGetIndexList(Relation relation) table_close(indrel, AccessShareLock); /* Now save a copy of the completed list in the relcache entry. */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); oldlist = relation->rd_indexlist; relation->rd_indexlist = list_copy(result); relation->rd_pkindex = pkeyIndex; @@ -4494,7 +4601,7 @@ RelationGetStatExtList(Relation relation) table_close(indrel, AccessShareLock); /* Now save a copy of the completed list in the relcache entry. */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); oldlist = relation->rd_statlist; relation->rd_statlist = list_copy(result); @@ -4568,7 +4675,7 @@ RelationSetIndexList(Relation relation, List *indexIds) Assert(relation->rd_isnailed); /* Copy the list into the cache context (could fail for lack of mem) */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); indexIds = list_copy(indexIds); MemoryContextSwitchTo(oldcxt); /* Okay to replace old list */ @@ -4987,7 +5094,7 @@ restart: * leave the relcache entry looking like the other ones are valid but * empty. */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); relation->rd_keyattr = bms_copy(uindexattrs); relation->rd_pkattr = bms_copy(pkindexattrs); relation->rd_idattr = bms_copy(idindexattrs); @@ -5196,7 +5303,7 @@ GetRelationPublicationActions(Relation relation) } /* Now save copy of the actions in the relcache entry. */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); relation->rd_pubactions = palloc(sizeof(PublicationActions)); memcpy(relation->rd_pubactions, pubactions, sizeof(PublicationActions)); MemoryContextSwitchTo(oldcxt); @@ -5332,7 +5439,7 @@ errtableconstraint(Relation rel, const char *conname) * criticalSharedRelcachesBuilt to true. * If not successful, return false. * - * NOTE: we assume we are already switched into CacheMemoryContext. + * NOTE: we assume we are already switched into RelCacheMemoryContext. */ static bool load_relcache_init_file(bool shared) @@ -5501,7 +5608,7 @@ load_relcache_init_file(bool shared) * prepare index info context --- parameters should match * RelationInitIndexAccessInfo */ - indexcxt = AllocSetContextCreate(CacheMemoryContext, + indexcxt = AllocSetContextCreate(RelCacheMemoryContext, "index info", ALLOCSET_SMALL_SIZES); rel->rd_indexcxt = indexcxt; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 42d427e..eeecba7 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -91,6 +91,7 @@ #include "utils/plancache.h" #include "utils/portal.h" #include "utils/ps_status.h" +#include "utils/relcache.h" #include "utils/rls.h" #include "utils/snapmgr.h" #include "utils/tzparser.h" @@ -2256,6 +2257,17 @@ static struct config_int ConfigureNamesInt[] = NULL, NULL, NULL }, + { + {"relation_cache_max_size", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the maximum size of relation cache in kilobytes."), + NULL, + GUC_UNIT_KB + }, + &relation_cache_max_size, + 0, 0, MAX_KILOBYTES, + NULL, NULL, NULL + }, + /* * We use the hopefully-safely-small value of 100kB as the compiled-in * default for max_stack_depth. InitializeGUCOptions will increase it if diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index a3b2e92..d26a269 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -131,6 +131,7 @@ #catalog_cache_memory_target = 0kB # in kB #catalog_cache_prune_min_age = 300s # -1 disables pruning #catalog_cache_max_size = 0kB # in kB +#relation_cache_max_size = 0kB # in kB #max_stack_depth = 2MB # min 100kB #shared_memory_type = mmap # the default is the first option # supported by the operating system: diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 19f3756..a871b7d 100644 --- a/src/backend/utils/mmgr/aset.c +++ b/src/backend/utils/mmgr/aset.c @@ -262,6 +262,17 @@ static AllocSetFreeList context_freelists[2] = } }; +/* support macros to account size of descendant */ +#define UpdateParentSize(context, size) \ +do { \ + Assert((context)->collectGroupSize); \ + for (MemoryContext cur = (context->parent); \ + cur && cur->collectGroupSize; \ + cur = cur->parent) \ + cur->usedspace += size; \ +} while (0) + + /* * These functions implement the MemoryContext API for AllocSet contexts. */ @@ -779,6 +790,8 @@ AllocSetAlloc(MemoryContext context, Size size) VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN); context->usedspace += chunk_size; + if (context->collectGroupSize) + UpdateParentSize(context, chunk_size); return AllocChunkGetPointer(chunk); } @@ -820,6 +833,8 @@ AllocSetAlloc(MemoryContext context, Size size) VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN); context->usedspace += chunk->size; + if (context->collectGroupSize) + UpdateParentSize(context, chunk->size); return AllocChunkGetPointer(chunk); } @@ -981,6 +996,8 @@ AllocSetAlloc(MemoryContext context, Size size) VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN); context->usedspace += chunk_size; + if (context->collectGroupSize) + UpdateParentSize(context, chunk_size); return AllocChunkGetPointer(chunk); } @@ -1029,6 +1046,9 @@ AllocSetFree(MemoryContext context, void *pointer) /* OK, remove block from aset's list and free it */ context->usedspace -= chunk->size; + if (context->collectGroupSize) + UpdateParentSize(context, -chunk->size); + if (block->prev) block->prev->next = block->next; else @@ -1047,6 +1067,8 @@ AllocSetFree(MemoryContext context, void *pointer) chunk->aset = (void *) set->freelist[fidx]; context->usedspace -= chunk->size; + if (context->collectGroupSize) + UpdateParentSize(context, -chunk->size); #ifdef CLOBBER_FREED_MEMORY wipe_mem(pointer, chunk->size); @@ -1168,6 +1190,9 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) chksize = MAXALIGN(size); blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; context->usedspace -= oldsize; + if (context->collectGroupSize) + UpdateParentSize(context, -oldsize); + block = (AllocBlock) realloc(block, blksize); if (block == NULL) { @@ -1188,6 +1213,9 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) block->next->prev = block; chunk->size = chksize; context->usedspace += chksize; + if (context->collectGroupSize) + UpdateParentSize(context, chksize); + #ifdef MEMORY_CONTEXT_CHECKING #ifdef RANDOMIZE_ALLOCATED_MEMORY diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 2f02786..fce7a87 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -52,6 +52,9 @@ MemoryContext CurTransactionContext = NULL; /* This is a transient link to the active portal's memory context: */ MemoryContext PortalContext = NULL; +/* MemoryContext for relcache, whose parent is CacheMemoryContext */ +MemoryContext RelCacheMemoryContext = NULL; + static void MemoryContextCallResetCallbacks(MemoryContext context); static void MemoryContextStatsInternal(MemoryContext context, int level, bool print, int max_children, @@ -174,6 +177,7 @@ MemoryContextResetOnly(MemoryContext context) context->methods->reset(context); context->usedspace = 0; context->isReset = true; + context->collectGroupSize = false; VALGRIND_DESTROY_MEMPOOL(context); VALGRIND_CREATE_MEMPOOL(context, 0, false); } @@ -758,6 +762,15 @@ MemoryContextCreate(MemoryContext node, node->nextchild = NULL; node->allowInCritSection = false; } + + /* + * if this node is descendant of parent which want to + * collect descendant sizes, mark the flag true + */ + if (parent && (parent->collectGroupSize)) + node->collectGroupSize = true; + else + node->collectGroupSize = false; VALGRIND_CREATE_MEMPOOL(node, 0, false); } diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h index 1203780..9f6f809 100644 --- a/src/include/nodes/memnodes.h +++ b/src/include/nodes/memnodes.h @@ -85,6 +85,7 @@ typedef struct MemoryContextData MemoryContext prevchild; /* previous child of same parent */ MemoryContext nextchild; /* next child of same parent */ Size usedspace; /* accumulates consumed memory size */ + bool collectGroupSize; /* account size from its descendant */ const char *name; /* context name (just for debugging) */ const char *ident; /* context ID if any (just for debugging) */ MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */ @@ -109,3 +110,5 @@ typedef struct MemoryContextData /* Interface routines for memory usedspace-based accounting */ #define MemoryContextGetUsedspace(c) ((c)->usedspace) + +#define MemoryContextSetCollectGroupSize(c) ((c)->collectGroupSize = true) diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index 30d7998..83e6569 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -63,6 +63,13 @@ extern PGDLLIMPORT MemoryContext CurTransactionContext; /* This is a transient link to the active portal's memory context: */ extern PGDLLIMPORT MemoryContext PortalContext; +/* + * MemoryContext for relcache, which is a child of + * CacheMemoryContext, used to keep track of memory usage and + * prune old relcaches exceeded + */ +extern PGDLLIMPORT MemoryContext RelCacheMemoryContext; + /* Backwards compatibility macro */ #define MemoryContextResetAndDeleteChildren(ctx) MemoryContextReset(ctx) diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 5402851..beb626a 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -20,6 +20,7 @@ #include "catalog/pg_index.h" #include "catalog/pg_publication.h" #include "fmgr.h" +#include "lib/ilist.h" #include "nodes/bitmapset.h" #include "rewrite/prs2lock.h" #include "storage/block.h" @@ -63,6 +64,7 @@ typedef struct RelationData char rd_indexvalid; /* state of rd_indexlist: 0 = not valid, 1 = * valid, 2 = temporarily forced */ bool rd_statvalid; /* is rd_statlist valid? */ + dlist_node rd_lrunode; /* LRU node */ /* * rd_createSubid is the ID of the highest subtransaction the rel has diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index 809d6aa..e724189 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -33,6 +33,8 @@ typedef struct RelationData *Relation; */ typedef Relation *RelationPtr; + + /* * Routines to open (lookup) and close a relcache entry */ @@ -141,4 +143,7 @@ extern bool criticalRelcachesBuilt; /* should be used only by relcache.c and postinit.c */ extern bool criticalSharedRelcachesBuilt; +/* for guc.c, not PGDLLIPMPORT'ed */ +extern int relation_cache_max_size; + #endif /* RELCACHE_H */