From: Kyotaro Horiguchi Date: Fri, 10 Jan 2020 15:08:54 +0900 Subject: [PATCH v3 3/3] CatCache expiration feature. This adds the catcache expiration feature to the catcache mechanism. Current catcache doesn't remove an entry and there's a case where many hash entries occupy large amont of memory , being not accessed ever after. This can be a quite serious issue on the cases of long-running sessions. The expiration feature keeps process memory usage below certain amount, in exchange of some extent of degradation if it is turned on. --- src/backend/utils/cache/catcache.c | 219 +++++++++++++++++++++++++++-- 1 file changed, 211 insertions(+), 8 deletions(-) diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index 7ac59bcdd6..a07b434fb6 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -38,6 +38,7 @@ #include "utils/rel.h" #include "utils/resowner_private.h" #include "utils/syscache.h" +#include "utils/timestamp.h" /* #define CACHEDEBUG */ /* turns DEBUG elogs on */ @@ -72,7 +73,8 @@ static CatCacheHeader *CacheHdr = NULL; /* Clock for the last accessed time of a catcache entry. */ TimestampTz catcacheclock = 0; -static inline HeapTuple SearchCatCacheInternal(CatCache *cache, +static inline HeapTuple SearchCatCacheInternal(bool do_expire, + CatCache *cache, int nkeys, Datum v1, Datum v2, Datum v3, Datum v4); @@ -93,6 +95,17 @@ static HeapTuple SearchCatCache3b(CatCache *cache, static HeapTuple SearchCatCache4b(CatCache *cache, Datum v1, Datum v2, Datum v3, Datum v4); +static HeapTuple SearchCatCachee(CatCache *cache, + Datum v1, Datum v2, Datum v3, Datum v4); +static HeapTuple SearchCatCache1e(CatCache *cache, Datum v1); +static HeapTuple SearchCatCache2e(CatCache *cache, Datum v1, Datum v2); +static HeapTuple SearchCatCache3e(CatCache *cache, + Datum v1, Datum v2, Datum v3); +static HeapTuple SearchCatCache4e(CatCache *cache, + Datum v1, Datum v2, Datum v3, Datum v4); + +static bool CatCacheCleanupOldEntries(CatCache *cp); + static uint32 CatalogCacheComputeHashValue(CatCache *cache, int nkeys, Datum v1, Datum v2, Datum v3, Datum v4); static uint32 CatalogCacheComputeTupleHashValue(CatCache *cache, int nkeys, @@ -125,13 +138,35 @@ static SearchCatCacheFuncsType catcache_base = { SearchCatCache4b }; +static SearchCatCacheFuncsType catcache_expire = { + SearchCatCachee, + SearchCatCache1e, + SearchCatCache2e, + SearchCatCache3e, + SearchCatCache4e +}; + SearchCatCacheFuncsType *SearchCatCacheFuncs = NULL; +/* set catcache function set according to guc variables */ +static void +set_catcache_functions(void) +{ + if (catalog_cache_prune_min_age < 0) + SearchCatCacheFuncs = &catcache_base; + else + SearchCatCacheFuncs = &catcache_expire; +} + + /* GUC assign function */ void assign_catalog_cache_prune_min_age(int newval, void *extra) { catalog_cache_prune_min_age = newval; + + /* choose corresponding function set */ + set_catcache_functions(); } /* @@ -837,7 +872,7 @@ InitCatCache(int id, slist_init(&CacheHdr->ch_caches); CacheHdr->ch_ntup = 0; - SearchCatCacheFuncs = &catcache_base; + set_catcache_functions(); #ifdef CATCACHE_STATS /* set up to dump stats at backend exit */ @@ -900,6 +935,10 @@ RehashCatCache(CatCache *cp) int newnbuckets; int i; + /* try removing old entries before expanding hash */ + if (CatCacheCleanupOldEntries(cp)) + return; + elog(DEBUG1, "rehashing catalog cache id %d for %s; %d tups, %d buckets", cp->id, cp->cc_relname, cp->cc_ntup, cp->cc_nbuckets); @@ -1187,7 +1226,7 @@ SearchCatCacheb(CatCache *cache, Datum v3, Datum v4) { - return SearchCatCacheInternal(cache, cache->cc_nkeys, v1, v2, v3, v4); + return SearchCatCacheInternal(false, cache, cache->cc_nkeys, v1, v2, v3, v4); } @@ -1201,7 +1240,7 @@ static HeapTuple SearchCatCache1b(CatCache *cache, Datum v1) { - return SearchCatCacheInternal(cache, 1, v1, 0, 0, 0); + return SearchCatCacheInternal(false, cache, 1, v1, 0, 0, 0); } @@ -1209,7 +1248,7 @@ static HeapTuple SearchCatCache2b(CatCache *cache, Datum v1, Datum v2) { - return SearchCatCacheInternal(cache, 2, v1, v2, 0, 0); + return SearchCatCacheInternal(false, cache, 2, v1, v2, 0, 0); } @@ -1217,7 +1256,7 @@ static HeapTuple SearchCatCache3b(CatCache *cache, Datum v1, Datum v2, Datum v3) { - return SearchCatCacheInternal(cache, 3, v1, v2, v3, 0); + return SearchCatCacheInternal(false, cache, 3, v1, v2, v3, 0); } @@ -1225,14 +1264,18 @@ static HeapTuple SearchCatCache4b(CatCache *cache, Datum v1, Datum v2, Datum v3, Datum v4) { - return SearchCatCacheInternal(cache, 4, v1, v2, v3, v4); + return SearchCatCacheInternal(false, cache, 4, v1, v2, v3, v4); } /* * Work-horse for SearchCatCache/SearchCatCacheN. + * + * This function expects smarter compiler reduces useless paths by calling with + * fixed parameters. */ static inline HeapTuple -SearchCatCacheInternal(CatCache *cache, +SearchCatCacheInternal(bool do_expire, + CatCache *cache, int nkeys, Datum v1, Datum v2, @@ -1301,6 +1344,20 @@ SearchCatCacheInternal(CatCache *cache, */ dlist_move_head(bucket, &ct->cache_elem); + /* + * Prolong life of this entry. Since we want run as less instructions + * as possible and want the branch be stable for performance reasons, + * we don't give a strict cap on the counter. All numbers above 1 will + * be regarded as 2 in CatCacheCleanupOldEntries(). + */ + if (do_expire) + { + ct->naccess++; + if (unlikely(ct->naccess == 0)) + ct->naccess = 2; + ct->lastaccess = catcacheclock; + } + /* * If it's a positive entry, bump its refcount and return it. If it's * negative, we can report failure to the caller. @@ -1462,6 +1519,150 @@ SearchCatCacheMiss(CatCache *cache, return &ct->tuple; } +/* + * SearchCatCache with entry pruning + * + * These functions works the same way with SearchCatCacheNb() functions except + * that less-used entries are removed following catalog_cache_prune_min_age + * setting. + */ +static HeapTuple +SearchCatCachee(CatCache *cache, + Datum v1, + Datum v2, + Datum v3, + Datum v4) +{ + return SearchCatCacheInternal(true, cache, cache->cc_nkeys, v1, v2, v3, v4); +} + + +/* + * SearchCatCacheN() are SearchCatCache() versions for a specific number of + * arguments. The compiler can inline the body and unroll loops, making them a + * bit faster than SearchCatCache(). + */ + +static HeapTuple +SearchCatCache1e(CatCache *cache, + Datum v1) +{ + return SearchCatCacheInternal(true, cache, 1, v1, 0, 0, 0); +} + + +static HeapTuple +SearchCatCache2e(CatCache *cache, + Datum v1, Datum v2) +{ + return SearchCatCacheInternal(true, cache, 2, v1, v2, 0, 0); +} + + +static HeapTuple +SearchCatCache3e(CatCache *cache, + Datum v1, Datum v2, Datum v3) +{ + return SearchCatCacheInternal(true, cache, 3, v1, v2, v3, 0); +} + + +static HeapTuple +SearchCatCache4e(CatCache *cache, + Datum v1, Datum v2, Datum v3, Datum v4) +{ + return SearchCatCacheInternal(true, cache, 4, v1, v2, v3, v4); +} + +/* + * CatCacheCleanupOldEntries - Remove infrequently-used entries + * + * Catcache entries happen to be left unused for a long time for several + * reasons. Remove such entries to prevent catcache from bloating. It is based + * on the similar algorithm with buffer eviction. Entries that are accessed + * several times in a certain period live longer than those that have had less + * access in the same duration. + */ +static bool +CatCacheCleanupOldEntries(CatCache *cp) +{ + int nremoved = 0; + int i; + long oldest_ts = catcacheclock; + long age; + int us; + + /* Return immediately if disabled */ + if (catalog_cache_prune_min_age < 0) + return false; + + /* Don't scan the hash when we know we don't have prunable entries */ + TimestampDifference(cp->cc_oldest_ts, catcacheclock, &age, &us); + if (age < catalog_cache_prune_min_age) + return false; + + /* Scan over the whole hash to find entries to remove */ + for (i = 0 ; i < cp->cc_nbuckets ; i++) + { + dlist_mutable_iter iter; + + dlist_foreach_modify(iter, &cp->cc_bucket[i]) + { + CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur); + + /* Don't remove referenced entries */ + if (ct->refcount == 0 && + (ct->c_list == NULL || ct->c_list->refcount == 0)) + { + /* + * Calculate the duration from the time from the last access + * to the "current" time. catcacheclock is updated + * per-statement basis and additionaly udpated periodically + * during a long running query. + */ + TimestampDifference(ct->lastaccess, catcacheclock, &age, &us); + + if (age > catalog_cache_prune_min_age) + { + /* + * Entries that are not accessed after the last pruning + * are removed in that seconds, and their lives are + * prolonged according to how many times they are accessed + * 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 > 2) + ct->naccess = 1; + else if (ct->naccess > 0) + ct->naccess--; + else + { + CatCacheRemoveCTup(cp, ct); + nremoved++; + + /* don't update oldest_ts by removed entry */ + continue; + } + } + } + + /* update oldest timestamp if the entry remains alive */ + if (ct->lastaccess < oldest_ts) + oldest_ts = ct->lastaccess; + } + } + + cp->cc_oldest_ts = oldest_ts; + + if (nremoved > 0) + elog(DEBUG1, "pruning catalog cache id=%d for %s: removed %d / %d", + cp->id, cp->cc_relname, nremoved, cp->cc_ntup + nremoved); + + return nremoved > 0; +} + + /* * ReleaseCatCache * @@ -1925,6 +2126,8 @@ 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_head(&cache->cc_bucket[hashIndex], &ct->cache_elem); -- 2.18.4 ----Next_Part(Thu_Nov__5_16_26_55_2020_457)----