At Wed, 27 Jan 2021 13:11:55 +0200, Heikki Linnakangas <hlinn...@iki.fi> wrote in > On 27/01/2021 03:13, Kyotaro Horiguchi wrote: > > At Thu, 14 Jan 2021 17:32:27 +0900 (JST), Kyotaro Horiguchi > > <horikyota....@gmail.com> wrote in > >> The commit 4656e3d668 (debug_invalidate_system_caches_always) > >> conflicted with this patch. Rebased. > > At Wed, 27 Jan 2021 10:07:47 +0900 (JST), Kyotaro Horiguchi > > <horikyota....@gmail.com> wrote in > >> (I found a bug in a benchmark-aid function > >> (CatalogCacheFlushCatalog2), I repost an updated version soon.) > > I noticed that a catcachebench-aid function > > CatalogCacheFlushCatalog2() allocates bucked array wrongly in the > > current memory context, which leads to a crash. > > This is a fixed it then rebased version. > > Thanks, with the scripts you provided, I was able to run the > performance tests on my laptop, and got very similar results as you > did. > > The impact of v7-0002-Remove-dead-flag-from-catcache-tuple.patch is > very small. I think I could see it in the tests, but only barely. And > the tests did nothing else than do syscache lookups; in any real world > scenario, it would be lost in noise. I think we can put that aside for > now, and focus on v6-0001-CatCache-expiration-feature.patch:
I agree to that opinion. But a bit dissapointing that the long struggle ended up in vain:p > The pruning is still pretty lethargic: > > - Entries created in the same transactions are never pruned away > > - The size of the hash table is never shrunk. So even though the patch > - puts a backstop to the hash table growing indefinitely, if you run one > - transaction that bloats the cache, it's bloated for the rest of the > - session. Right. But more frequent check impacts on performance. We can do more aggressive pruning in idle-time. > I think that's OK. We might want to be more aggressive in the future, > but for now it seems reasonable to lean towards the current behavior > where nothing is pruned. Although I wonder if we should try to set > 'catcacheclock' more aggressively. I think we could set it whenever > GetCurrentTimestamp() is called, for example. Ah. I didn't thought that direction. global_last_acquired_timestamp or such? > Given how unaggressive this mechanism is, I think it should be safe to > enable it by default. What would be a suitable default for > catalog_cache_prune_min_age? 30 seconds? Without a detailed thought, it seems a bit too short. The ever-suggested value for the variable is 300-600s. That is, intermittent queries with about 5-10 minutes intervals don't lose cache entries. In a bad case, two queries alternately remove each other's cache entries. Q1: adds 100 entries <1 minute passed> Q2: adds 100 entries but rehash is going to happen at 150 entries and the existing 100 entreis added by Q1 are removed. <1 minute passed> Q1: adds 100 entries but rehash is going to happen at 150 entries and the existing 100 entreis added by Q2 are removed. <repeats> Or a transaction sequence persists longer than that seconds may lose some of the catcache entries. > Documentation needs to be updated for the new GUC. > > Attached is a version with a few little cleanups: > - use TimestampTz instead of uint64 for the timestamps > - remove assign_catalog_cache_prune_min_age(). All it did was convert > - the GUC's value from seconds to microseconds, and stored it in a > - separate variable. Multiplication is cheap, so we can just do it when > - we use the GUC's value instead. Yeah, the laater is a trace of the struggle for cutting down cpu cycles in the normal paths. I don't object to do so. I found that some comments are apparently stale. cp->cc_oldest_ts is not used anywhere, but it is added for the decision of whether to scan or not. I fixed the following points in the attached. - Removed some comments that is obvious. ("Timestamp in us") - Added cp->cc_oldest_ts check in CatCacheCleanupOldEntries. - Set the default value for catalog_cache_prune_min_age to 600s. - Added a doc entry for the new GUC in the resoruce/memory section. - Fix some code comments. - Adjust pruning criteria from (ct->lastaccess < prune_threshold) to <=. I was going to write in the doc something like "you can inspect memory consumption by catalog caches using pg_backend_memory_contexts", but all the memory used by catalog cache is in CacheMemoryContext. Is it sensible for each catalog cache to have their own contexts? regards. -- Kyotaro Horiguchi NTT Open Source Software Center
>From 01d32ffa499ee9185e222fe6fa3d39ad6ac5ff37 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas <heikki.linnakan...@iki.fi> Date: Wed, 27 Jan 2021 13:08:08 +0200 Subject: [PATCH v9] CatCache expiration feature Author: Kyotaro Horiguchi --- doc/src/sgml/config.sgml | 20 +++++++ src/backend/access/transam/xact.c | 3 ++ src/backend/utils/cache/catcache.c | 85 +++++++++++++++++++++++++++++- src/backend/utils/misc/guc.c | 12 +++++ src/include/utils/catcache.h | 17 ++++++ 5 files changed, 136 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index f1037df5a9..14be8061ce 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1960,6 +1960,26 @@ include_dir 'conf.d' </listitem> </varlistentry> + <varlistentry id="guc-catalog-cache-prune-min-age" xreflabel="catalog_cache_prune_min_age"> + <term><varname>catalog_cache_prune_min_age</varname> (<type>integer</type>) + <indexterm> + <primary><varname>catalog_cache_prune_min_age</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Setting <varname>catalog_cache_prune_min_age</varname> allows catalog + cache entries older than this seconds removed. A value + of <literal>-1</literal> disables this feature, effectively setting + the value to infinity. The default is 600 seconds. You can reduce + this value to reduce the amount of memory used by the catalog cache in + exchange of possible performance degradation or increase it to gain in + performance of intermittently executed queries in exchange of the + possible increase of memory usage. + </para> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index a2068e3fd4..86888d2409 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -1086,6 +1086,9 @@ static void AtStart_Cache(void) { AcceptInvalidationMessages(); + + if (xactStartTimestamp != 0) + SetCatCacheClock(xactStartTimestamp); } /* diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index fa2b49c676..3e24a81992 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 */ @@ -60,9 +61,18 @@ #define CACHE_elog(...) #endif +/* + * GUC variable to define the minimum age of entries that are the candidates + * for evictetion in seconds. -1 to disable the feature. + */ +int catalog_cache_prune_min_age = -1; + /* Cache management header --- pointer is NULL until created */ static CatCacheHeader *CacheHdr = NULL; +/* Clock for the last accessed time of catcache entry. */ +TimestampTz catcacheclock = 0; + static inline HeapTuple SearchCatCacheInternal(CatCache *cache, int nkeys, Datum v1, Datum v2, @@ -74,6 +84,7 @@ static pg_noinline HeapTuple SearchCatCacheMiss(CatCache *cache, Index hashIndex, 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); @@ -833,6 +844,7 @@ InitCatCache(int id, cp->cc_nkeys = nkeys; for (i = 0; i < nkeys; ++i) cp->cc_keyno[i] = key[i]; + cp->cc_oldest_ts = catcacheclock; /* * new cache is initialized as far as we can go for now. print some @@ -1264,6 +1276,9 @@ SearchCatCacheInternal(CatCache *cache, */ dlist_move_head(bucket, &ct->cache_elem); + /* Record the last access timestamp */ + 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. @@ -1425,6 +1440,69 @@ SearchCatCacheMiss(CatCache *cache, return &ct->tuple; } +/* + * CatCacheCleanupOldEntries - Remove infrequently-used entries + * + * Removes entries that has been left alone for a certain period to prevent + * catcache bloat. + */ +static bool +CatCacheCleanupOldEntries(CatCache *cp) +{ + int nremoved = 0; + int i; + TimestampTz oldest_ts = catcacheclock; + TimestampTz prune_threshold; + + if (catalog_cache_prune_min_age < 0) + return false; + + /* entries older than this time would be removed */ + prune_threshold = catcacheclock - + ((int64) catalog_cache_prune_min_age) * USECS_PER_SEC; + + /* return if we know we have no entry to remove */ + if (cp->cc_oldest_ts > prune_threshold) + 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)) + { + if (ct->lastaccess <= prune_threshold) + { + CatCacheRemoveCTup(cp, ct); + nremoved++; + + /* don't let the removed entry update oldest_ts */ + continue; + } + } + + /* update the 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 * @@ -1888,6 +1966,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments, ct->dead = false; ct->negative = negative; ct->hash_value = hashValue; + ct->lastaccess = catcacheclock; dlist_push_head(&cache->cc_bucket[hashIndex], &ct->cache_elem); @@ -1899,7 +1978,11 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments, * arbitrarily, we enlarge when fill factor > 2. */ if (cache->cc_ntup > cache->cc_nbuckets * 2) - RehashCatCache(cache); + { + /* try removing old entries before expanding hash */ + if (!CatCacheCleanupOldEntries(cache)) + RehashCatCache(cache); + } return ct; } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index eafdb1118e..6a1e52911a 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -88,6 +88,7 @@ #include "utils/acl.h" #include "utils/builtins.h" #include "utils/bytea.h" +#include "utils/catcache.h" #include "utils/float.h" #include "utils/guc_tables.h" #include "utils/memutils.h" @@ -3445,6 +3446,17 @@ static struct config_int ConfigureNamesInt[] = NULL, NULL, NULL }, + { + {"catalog_cache_prune_min_age", PGC_USERSET, RESOURCES_MEM, + gettext_noop("System catalog cache entries that are unused more than this seconds are to be removed."), + gettext_noop("The value of -1 turns off pruning."), + GUC_UNIT_S + }, + &catalog_cache_prune_min_age, + 600, -1, INT_MAX, + NULL, NULL, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h index ddc2762eb3..786df7aeda 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,7 @@ typedef struct catcache slist_node cc_next; /* list link */ ScanKeyData cc_skey[CATCACHE_MAXKEYS]; /* precomputed key info for heap * scans */ + TimestampTz cc_oldest_ts; /* timestamp of the oldest tuple */ /* * Keep these at the end, so that compiling catcache.c with CATCACHE_STATS @@ -119,6 +121,7 @@ typedef struct catctup bool dead; /* dead but not yet removed? */ bool negative; /* negative cache entry? */ HeapTupleData tuple; /* tuple management header */ + TimestampTz lastaccess; /* timestamp of the last use */ /* * The tuple may also be a member of at most one CatCList. (If a single @@ -189,6 +192,20 @@ typedef struct catcacheheader /* this extern duplicates utils/memutils.h... */ extern PGDLLIMPORT MemoryContext CacheMemoryContext; + +/* for guc.c, not PGDLLPMPORT'ed */ +extern int catalog_cache_prune_min_age; + +/* source clock for access timestamp of catcache entries */ +extern TimestampTz catcacheclock; + +/* SetCatCacheClock - set catcache timestamp source clock */ +static inline void +SetCatCacheClock(TimestampTz ts) +{ + catcacheclock = ts; +} + extern void CreateCacheMemoryContext(void); extern CatCache *InitCatCache(int id, Oid reloid, Oid indexoid, -- 2.27.0