From: Kyotaro Horiguchi Date: Tue, 16 Oct 2018 13:04:30 +0900 Subject: [PATCH 2/4] Remove entries that haven't been used for a certain time Catcache entries can be left alone for several reasons. It is not desirable that they eat up memory. With this patch, This adds consideration of removal of entries that haven't been used for a certain time before enlarging the hash array. This also can put a hard limit on the number of catcache entries. --- doc/src/sgml/config.sgml | 38 +++++ src/backend/access/transam/xact.c | 5 + src/backend/utils/cache/catcache.c | 205 +++++++++++++++++++++++++- src/backend/utils/misc/guc.c | 63 ++++++++ src/backend/utils/misc/postgresql.conf.sample | 2 + src/include/utils/catcache.h | 33 ++++- 6 files changed, 338 insertions(+), 8 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 9b7a7388d5..d0d2374944 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1662,6 +1662,44 @@ include_dir 'conf.d' + + syscache_memory_target (integer) + + syscache_memory_target configuration parameter + + + + + Specifies the maximum amount of memory to which syscache is expanded + without pruning. The value defaults to 0, indicating that pruning is + always considered. After exceeding this size, syscache pruning is + considered according to + . If you need to keep + certain amount of syscache entries with intermittent usage, try + increase this setting. + + + + + + syscache_prune_min_age (integer) + + syscache_prune_min_age configuration parameter + + + + + Specifies the minimum amount of unused time in seconds at which a + syscache entry is considered to be removed. -1 indicates that syscache + pruning is disabled at all. The value defaults to 600 seconds + (10 minutes). The syscache entries that are not + used for the duration can be removed to prevent syscache bloat. This + behavior is suppressed until the size of syscache exceeds + . + + + + max_stack_depth (integer) diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 92bda87804..ddc433c59e 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -734,7 +734,12 @@ void SetCurrentStatementStartTimestamp(void) { if (!IsParallelWorker()) + { stmtStartTimestamp = GetCurrentTimestamp(); + + /* Set this timestamp as aproximated current time */ + SetCatCacheClock(stmtStartTimestamp); + } else Assert(stmtStartTimestamp != 0); } diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index 258a1d64cc..c70ce3b745 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -71,9 +71,38 @@ #define CACHE6_elog(a,b,c,d,e,f,g) #endif +/* + * GUC variable to define the minimum size of hash to cosider entry eviction. + * This variable is shared among various cache mechanisms. + */ +int cache_memory_target = 0; + + +/* + * GUC for entry limit. Entries are removed when the number of them goes above + * cache_entry_limit by the ratio specified by cache_entry_limit_prune_ratio + */ +int cache_entry_limit = 0; +double cache_entry_limit_prune_ratio = 0.8; + +/* GUC variable to define the minimum age of entries that will be cosidered to + * be evicted in seconds. This variable is shared among various cache + * mechanisms. + */ +int cache_prune_min_age = 600; + +/* + * Ignorance interval between two success move of a cache entry in LRU list, + * in microseconds. + */ +#define LRU_IGNORANCE_INTERVAL 100000 /* 100ms */ + /* Cache management header --- pointer is NULL until created */ static CatCacheHeader *CacheHdr = NULL; +/* Timestamp used for any operation on caches. */ +TimestampTz catcacheclock = 0; + static inline HeapTuple SearchCatCacheInternal(CatCache *cache, int nkeys, Datum v1, Datum v2, @@ -481,6 +510,7 @@ CatCacheRemoveCTup(CatCache *cache, CatCTup *ct) /* delink from linked list */ dlist_delete(&ct->cache_elem); + dlist_delete(&ct->lru_node); /* * Free keys when we're dealing with a negative entry, normal entries just @@ -490,6 +520,7 @@ CatCacheRemoveCTup(CatCache *cache, CatCTup *ct) CatCacheFreeKeys(cache->cc_tupdesc, cache->cc_nkeys, cache->cc_keyno, ct->keys); + cache->cc_tupsize -= ct->size; pfree(ct); --cache->cc_ntup; @@ -841,7 +872,9 @@ InitCatCache(int id, cp->cc_nkeys = nkeys; for (i = 0; i < nkeys; ++i) cp->cc_keyno[i] = key[i]; + cp->cc_tupsize = 0; + dlist_init(&cp->cc_lru_list); /* * new cache is initialized as far as we can go for now. print some * debugging information, if appropriate. @@ -858,9 +891,133 @@ InitCatCache(int id, */ MemoryContextSwitchTo(oldcxt); + /* initilize catcache reference clock if haven't done yet */ + if (catcacheclock == 0) + catcacheclock = GetCurrentTimestamp(); + return cp; } +/* + * CatCacheCleanupOldEntries - Remove infrequently-used entries + * + * Catcache entries can be left alone for several reasons. We remove them if + * they are not accessed for a certain time to prevent catcache from + * bloating. The eviction is performed with the similar algorithm with buffer + * eviction using access counter. Entries that are accessed several times can + * live longer than those that have had no access in the same duration. + */ +#define PRUNE_BY_AGE 0x01 +#define PRUNE_BY_NUMBER 0x02 + +static bool +CatCacheCleanupOldEntries(CatCache *cp) +{ + int nremoved = 0; + size_t hash_size; + int nelems_before = cp->cc_ntup; + int ndelelems = 0; + int action = 0; + dlist_mutable_iter iter; + + if (cache_prune_min_age >= 0) + { + /* prune only if the size of the hash is above the target */ + + hash_size = cp->cc_nbuckets * sizeof(dlist_head); + if (hash_size + cp->cc_tupsize > (Size) cache_memory_target * 1024L) + action |= PRUNE_BY_AGE; + } + + if (cache_entry_limit > 0 && nelems_before >= cache_entry_limit) + { + ndelelems = nelems_before - + (int) (cache_entry_limit * cache_entry_limit_prune_ratio); + + if (ndelelems < 256) + ndelelems = 256; + if (ndelelems > nelems_before) + ndelelems = nelems_before; + + action |= PRUNE_BY_NUMBER; + } + + /* Return immediately if no pruning is wanted */ + if (action == 0) + return false; + + /* Scan over LRU to find entries to remove */ + dlist_foreach_modify(iter, &cp->cc_lru_list) + { + CatCTup *ct = dlist_container(CatCTup, lru_node, iter.cur); + bool remove_this = false; + + /* We don't remove referenced entry */ + if (ct->refcount != 0 || + (ct->c_list && ct->c_list->refcount != 0)) + continue; + + /* check against age */ + if (action & PRUNE_BY_AGE) + { + long entry_age; + int us; + + /* + * Calculate the duration from the time of the last access to the + * "current" time. Since catcacheclock is not advanced within a + * transaction, the entries that are accessed within the current + * transaction won't be pruned. + */ + TimestampDifference(ct->lastaccess, catcacheclock, &entry_age, &us); + + if (entry_age < cache_prune_min_age) + { + /* no longer have a business with further entries, exit */ + action &= ~PRUNE_BY_AGE; + break; + } + + /* + * Entries that are not accessed after last pruning are removed in + * that seconds, and that has been accessed several times are + * removed after leaving alone for up to three times of the + * duration. We don't try shrink buckets since pruning effectively + * caps catcache expansion in the long term. + */ + if (ct->naccess > 0) + ct->naccess--; + else + remove_this = true; + } + + /* check against entry number */ + if (action & PRUNE_BY_NUMBER) + { + if (nremoved < ndelelems) + remove_this = true; + else + action &= ~PRUNE_BY_NUMBER; /* satisfied */ + } + + /* exit if finished */ + if (action == 0) + break; + + /* do the work */ + if (remove_this) + { + CatCacheRemoveCTup(cp, ct); + nremoved++; + } + } + + elog(DEBUG1, "pruning catalog cache id=%d for %s: removed %d / %d", + cp->id, cp->cc_relname, nremoved, nelems_before); + + return nremoved > 0; +} + /* * Enlarge a catcache, doubling the number of buckets. */ @@ -1274,6 +1431,21 @@ SearchCatCacheInternal(CatCache *cache, */ dlist_move_head(bucket, &ct->cache_elem); + /* Update access information for pruning */ + if (ct->naccess < 2) + ct->naccess++; + + /* + * We don't want too frequent update of LRU. cache_prune_min_age can + * be changed on-session so we need to maintan the LRU regardless of + * cache_prune_min_age. + */ + if (catcacheclock - ct->lastaccess > LRU_IGNORANCE_INTERVAL) + { + ct->lastaccess = catcacheclock; + dlist_move_tail(&cache->cc_lru_list, &ct->lru_node); + } + /* * If it's a positive entry, bump its refcount and return it. If it's * negative, we can report failure to the caller. @@ -1819,11 +1991,13 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments, CatCTup *ct; HeapTuple dtp; MemoryContext oldcxt; + int tupsize = 0; /* negative entries have no tuple associated */ if (ntp) { int i; + int tupsize; Assert(!negative); @@ -1842,13 +2016,14 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments, /* Allocate memory for CatCTup and the cached tuple in one go */ oldcxt = MemoryContextSwitchTo(CacheMemoryContext); - ct = (CatCTup *) palloc(sizeof(CatCTup) + - MAXIMUM_ALIGNOF + dtp->t_len); + tupsize = sizeof(CatCTup) + MAXIMUM_ALIGNOF + dtp->t_len; + ct = (CatCTup *) palloc(tupsize); ct->tuple.t_len = dtp->t_len; ct->tuple.t_self = dtp->t_self; ct->tuple.t_tableOid = dtp->t_tableOid; ct->tuple.t_data = (HeapTupleHeader) MAXALIGN(((char *) ct) + sizeof(CatCTup)); + ct->size = tupsize; /* copy tuple contents */ memcpy((char *) ct->tuple.t_data, (const char *) dtp->t_data, @@ -1876,8 +2051,8 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments, { Assert(negative); oldcxt = MemoryContextSwitchTo(CacheMemoryContext); - ct = (CatCTup *) palloc(sizeof(CatCTup)); - + tupsize = sizeof(CatCTup); + ct = (CatCTup *) palloc(tupsize); /* * Store keys - they'll point into separately allocated memory if not * by-value. @@ -1898,18 +2073,34 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments, ct->dead = false; ct->negative = negative; ct->hash_value = hashValue; + ct->naccess = 0; + ct->lastaccess = catcacheclock; + dlist_push_tail(&cache->cc_lru_list, &ct->lru_node); + ct->size = tupsize; dlist_push_head(&cache->cc_bucket[hashIndex], &ct->cache_elem); cache->cc_ntup++; CacheHdr->ch_ntup++; + cache->cc_tupsize += tupsize; + + /* increase refcount so that this survives pruning */ + ct->refcount++; /* - * If the hash table has become too full, enlarge the buckets array. Quite - * arbitrarily, we enlarge when fill factor > 2. + * If the hash table has become too full, try cleanup by removing + * infrequently used entries to make a room for the new entry. If it + * failed, enlarge the bucket array instead. Quite arbitrarily, we try + * this when fill factor > 2. */ - if (cache->cc_ntup > cache->cc_nbuckets * 2) + if (cache->cc_ntup > cache->cc_nbuckets * 2 && + !CatCacheCleanupOldEntries(cache)) RehashCatCache(cache); + /* we may still want to prune by entry number, check it */ + else if (cache_entry_limit > 0 && cache->cc_ntup > cache_entry_limit) + CatCacheCleanupOldEntries(cache); + + ct->refcount--; return ct; } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 8681ada33a..d4df841982 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -81,6 +81,7 @@ #include "tsearch/ts_cache.h" #include "utils/builtins.h" #include "utils/bytea.h" +#include "utils/catcache.h" #include "utils/guc_tables.h" #include "utils/float.h" #include "utils/memutils.h" @@ -2204,6 +2205,58 @@ static struct config_int ConfigureNamesInt[] = NULL, NULL, NULL }, + { + {"cache_memory_target", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the minimum syscache size to keep."), + gettext_noop("Cache is not pruned before exceeding this size."), + GUC_UNIT_KB + }, + &cache_memory_target, + 0, 0, MAX_KILOBYTES, + NULL, NULL, NULL + }, + + { + {"cache_prune_min_age", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the minimum unused duration of cache entries before removal."), + gettext_noop("Cache entries that live unused for longer than this seconds are considered to be removed."), + GUC_UNIT_S + }, + &cache_prune_min_age, + 600, -1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"cache_entry_limit", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the maximum entries of catcache."), + NULL + }, + &cache_entry_limit, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"cache_entry_limit", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the maximum entries of catcache."), + NULL + }, + &cache_entry_limit, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + + { + {"cache_entry_limit", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the maximum entries of catcache."), + NULL + }, + &cache_entry_limit, + 0, 0, INT_MAX, + 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 @@ -3368,6 +3421,16 @@ static struct config_real ConfigureNamesReal[] = NULL, NULL, NULL }, + { + {"cache_entry_limit_prune_ratio", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the maximum entries of catcache."), + NULL + }, + &cache_entry_limit_prune_ratio, + 0.8, 0.0, 1.0, + NULL, NULL, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, 0.0, 0.0, 0.0, NULL, NULL, NULL diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index c7f53470df..108d332f2c 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -128,6 +128,8 @@ #work_mem = 4MB # min 64kB #maintenance_work_mem = 64MB # min 1MB #autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem +#cache_memory_target = 0kB # in kB +#cache_prune_min_age = 600s # -1 disables pruning #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/include/utils/catcache.h b/src/include/utils/catcache.h index 65d816a583..3c6842e272 100644 --- a/src/include/utils/catcache.h +++ b/src/include/utils/catcache.h @@ -22,6 +22,7 @@ #include "access/htup.h" #include "access/skey.h" +#include "datatype/timestamp.h" #include "lib/ilist.h" #include "utils/relcache.h" @@ -61,6 +62,9 @@ typedef struct catcache slist_node cc_next; /* list link */ ScanKeyData cc_skey[CATCACHE_MAXKEYS]; /* precomputed key info for heap * scans */ + dlist_head cc_lru_list; + int cc_tupsize; /* total amount of catcache tuples */ + int cc_nfreeent; /* # of entries currently not referenced */ /* * Keep these at the end, so that compiling catcache.c with CATCACHE_STATS @@ -119,7 +123,10 @@ typedef struct catctup bool dead; /* dead but not yet removed? */ bool negative; /* negative cache entry? */ HeapTupleData tuple; /* tuple management header */ - + int naccess; /* # of access to this entry, up to 2 */ + TimestampTz lastaccess; /* approx. timestamp of the last usage */ + dlist_node lru_node; /* LRU node */ + int size; /* palloc'ed size off this tuple */ /* * The tuple may also be a member of at most one CatCList. (If a single * catcache is list-searched with varying numbers of keys, we may have to @@ -189,6 +196,30 @@ typedef struct catcacheheader /* this extern duplicates utils/memutils.h... */ extern PGDLLIMPORT MemoryContext CacheMemoryContext; +/* for guc.c, not PGDLLPMPORT'ed */ +extern int cache_prune_min_age; +extern int cache_memory_target; +extern int cache_entry_limit; +extern double cache_entry_limit_prune_ratio; + +/* to use as access timestamp of catcache entries */ +extern TimestampTz catcacheclock; + +/* + * SetCatCacheClock - set timestamp for catcache access record + */ +static inline void +SetCatCacheClock(TimestampTz ts) +{ + catcacheclock = ts; +} + +static inline TimestampTz +GetCatCacheClock(void) +{ + return catcacheclock; +} + extern void CreateCacheMemoryContext(void); extern CatCache *InitCatCache(int id, Oid reloid, Oid indexoid, -- 2.16.3 ----Next_Part(Thu_Feb_07_21_18_45_2019_504)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v13-0003-Syscache-usage-tracking-feature.patch"