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 */