The catcache code is located at src/backend/utils/cache/catcache.c, which contains links and operations to initialize SysCache structures and pointer relationships between data structures.
Finding tuples in CatCache
There are two ways to find tuples in a CatCache: exactly matching the SearchCatCache and partially matching the SearchCatcacheList. The former is used for all the key values required by a given CatCache and returns tuples in the CatCache that exactly match this key value. The latter only needs to give some key values and return partially matched tuples as a CatCList.
Exact Match Find
Implemented by the function SearchCatCache, whose functions are as follows:
SearchCatCache(CatCache *cache,//cacheID to find Datum v1, //V1v2v3v4 are used to find the key values of tuples, corresponding to the tuple search keys recorded in the Cache, respectively. Datum v2, Datum v3, Datum v4) { return SearchCatCacheInternal(cache, cache->cc_nkeys, v1, v2, v3, v4);//Call the SearchCatCache function to find }
When the number of parameters is specific, the compiler selects the SearchCatCacheN() inline body and expands the loop to make them faster than SearchCatCache().
//First, a partial search of one key HeapTuple SearchCatCache1(CatCache *cache,Datum v1) {return SearchCatCacheInternal(cache, 1, v1, 0, 0, 0);} //Second, partial search with two keys HeapTuple SearchCatCache2(CatCache *cache,Datum v1, Datum v2) {return SearchCatCacheInternal(cache, 2, v1, v2, 0, 0);} //Third, three-key partial search HeapTuple SearchCatCache3(CatCache *cache,Datum v1, Datum v2, Datum v3) {return SearchCatCacheInternal(cache, 3, v1, v2, v3, 0);} //Fourth, four-key partial search (no difference from exact search) HeapTuple SearchCatCache4(CatCache *cache,Datum v1, Datum v2, Datum v3, Datum v4) {return SearchCatCacheInternal(cache, 4, v1, v2, v3, v4);}
When calling the SearchCatCacheInternal function, replace the key s that are not present with 0. The rest of the steps are the same as exact searches.
Main Code for Search:
static inline HeapTuple SearchCatCacheInternal(CatCache *cache,int nkeys,Datum v1,Datum v2,Datum v3,Datum v4) { Datum arguments[CATCACHE_MAXKEYS];//Array of parameters to hold key values uint32 hashValue; Index hashIndex; dlist_iter iter; dlist_head *bucket; CatCTup *ct; /* Make sure we're in an accurate action, even if it ends up being a cache hit */ Assert(IsTransactionState()); Assert(cache->cc_nkeys == nkeys); /* One-time startup overhead per cache */ if (unlikely(cache->cc_tupdesc == NULL)) CatalogCacheInitializeCache(cache); #ifdef CATCACHE_STATS cache->cc_searches++; #endif /* Initialize local parameter array */ arguments[0] = v1; arguments[1] = v2; arguments[2] = v3; arguments[3] = v4; /* Calculate hash value * Find the corresponding hash bucket sequence number based on the hash value */ hashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4); hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); /* * Get the hash bucket from the hash bucket number you just got * Note: You can use dlist_here Foreach, because even if we modify the DLIST in the loop, the loop will not continue after that. */ bucket = &cache->cc_bucket[hashIndex]; dlist_foreach(iter, bucket)//Iterate over a hash bucket { ct = dlist_container(CatCTup, cache_elem, iter.cur); if (ct->dead) continue; /* Ignore dead tuples */ if (ct->hash_value != hashValue) continue; /* Hash errors are skipped quickly */ if (!CatalogCacheCompareTuple(cache, nkeys, ct->keys, arguments))//Skip if key values do not match continue; /* * Being able to do so indicates that a matching tuple has been found in the cache and moved to the chain header of the hash bucket. This * Is to speed up subsequent searches. * This practice of putting the most recently visited element at the front of the list of chains allows the most frequently visited element to be quickly accessed * Visit. */ dlist_move_head(bucket, &ct->cache_elem); /* Judges on this tuple */ if (!ct->negative)//If the tuple is not negative { ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); ct->refcount++;//Add its citation technique to one ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); CACHE_elog(DEBUG2, "SearchCatCache(%s): found in bucket %d", cache->cc_relname, hashIndex); #ifdef CATCACHE_STATS cache->cc_hits++;//Cache hits plus one #endif return &ct->tuple;//Return this tuple } else//If it is a negative tuple { CACHE_elog(DEBUG2, "SearchCatCache(%s): found neg entry in bucket %d", cache->cc_relname, hashIndex); #ifdef CATCACHE_STATS cache->cc_neg_hits++;//Cached negative tuple hits plus one #endif return NULL;//Return not found } } //If executed here, no matching tuples were found at the end of the iteration return SearchCatCacheMiss(cache, nkeys, hashValue, hashIndex, v1, v2, v3, v4);//Cache lookup failed }
When you first enter the SearchCatCache function, you need to call CatalogCacheInitializeCache(cache) once because the system tables have not yet been loaded into the related structure of SysCache.
/* * Initialize directory cache * This function ultimately initializes catcache by getting the tuple descriptor and setting the hash and equation function links. */ #ifdef CACHEDEBUG #define CatalogCacheInitializeCache_DEBUG1 \ elog(DEBUG2, "CatalogCacheInitializeCache: cache @%p rel=%u", cache, \ cache->cc_reloid) #define CatalogCacheInitializeCache_DEBUG2 \ do { \ if (cache->cc_keyno[i] > 0) { \ elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d, %u", \ i+1, cache->cc_nkeys, cache->cc_keyno[i], \ TupleDescAttr(tupdesc, cache->cc_keyno[i] - 1)->atttypid); \ } else { \ elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d", \ i+1, cache->cc_nkeys, cache->cc_keyno[i]); \ } \ } while(0) #else #define CatalogCacheInitializeCache_DEBUG1 #define CatalogCacheInitializeCache_DEBUG2 #endif /* * Initialize cache information by calling CatalogCacheInitializeCache * (You won't look up the data at this time, but you'll set the field type, hash function, or whatever * So heap_is called in this function Open read field type) */ static void CatalogCacheInitializeCache(CatCache *cache) { Relation relation;//Storage table MemoryContext oldcxt;//Storage Context TupleDesc tupdesc; //Permanent Cache Storage int i; //Used for circular calls to initialize key information for the cache CatalogCacheInitializeCache_DEBUG1; //Open the corresponding system table relation = table_open(cache->cc_reloid, AccessShareLock); /* Switch to the cache context so that our allocation does not disappear at the end of the transaction */ Assert(CacheMemoryContext != NULL); oldcxt = MemoryContextSwitchTo(CacheMemoryContext); /* Copy Relcache's tuple descriptor to the permanent cache store */ tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(relation)); /* Also save the name of the relationship and the relisshared flag (cc_relname for debugging purposes only) */ cache->cc_relname = pstrdup(RelationGetRelationName(relation)); cache->cc_relisshared = RelationGetForm(relation)->relisshared; /* Returns the caller's memory context and closes rel */ MemoryContextSwitchTo(oldcxt); table_close(relation, AccessShareLock); CACHE_elog(DEBUG2, "CatalogCacheInitializeCache: %s, %d keys", cache->cc_relname, cache->cc_nkeys); /* Key information to initialize the cache */ for (i = 0; i < cache->cc_nkeys; ++i) { Oid keytype; RegProcedure eqfunc; CatalogCacheInitializeCache_DEBUG2; if (cache->cc_keyno[i] > 0) { Form_pg_attribute attr = TupleDescAttr(tupdesc, cache->cc_keyno[i] - 1); keytype = attr->atttypid; /* Cache key columns should always be NOT NULL */ Assert(attr->attnotnull); } else {if (cache->cc_keyno[i] < 0)//Sys attribute is not supported in cache elog(FATAL, "sys attributes are not supported in caches"); keytype = OIDOID; } GetCCHashEqFuncs(keytype, &cache->cc_hashfunc[i], &eqfunc, &cache->cc_fastequal[i]); /* Find the equality function (we assume this will not require any directory lookups of supported types) */ fmgr_info_cxt(eqfunc, &cache->cc_skey[i].sk_func, CacheMemoryContext); /* Initialize sk_properly for HeapKeyTest() and heap scan Attno */ cache->cc_skey[i].sk_attno = cache->cc_keyno[i]; /* Also fill in sk_strategy - Always the Standard Equation */ cache->cc_skey[i].sk_strategy = BTEqualStrategyNumber; cache->cc_skey[i].sk_subtype = InvalidOid; /* If a catcache key requires a collation, it must be a C collation */ cache->cc_skey[i].sk_collation = C_COLLATION_OID; //Initialize directory cache CACHE_elog(DEBUG2, "CatalogCacheInitializeCache %s %d %p", cache->cc_relname, i, cache); } /* Mark that this cache has been fully initialized */ cache->cc_tupdesc = tupdesc; }
Call Find Support Function
/* Find support function for type. */ static void GetCCHashEqFuncs(Oid keytype, CCHashFN *hashfunc, RegProcedure *eqfunc, CCFastEqualFN *fasteqfunc) { switch (keytype) { case BOOLOID: *hashfunc = charhashfast; *fasteqfunc = chareqfast; *eqfunc = F_BOOLEQ; break; case CHAROID: *hashfunc = charhashfast; *fasteqfunc = chareqfast; *eqfunc = F_CHAREQ; break; case NAMEOID: *hashfunc = namehashfast; *fasteqfunc = nameeqfast; *eqfunc = F_NAMEEQ; break; case INT2OID: *hashfunc = int2hashfast; *fasteqfunc = int2eqfast; *eqfunc = F_INT2EQ; break; case INT4OID: *hashfunc = int4hashfast; *fasteqfunc = int4eqfast; *eqfunc = F_INT4EQ; break; case TEXTOID: *hashfunc = texthashfast; *fasteqfunc = texteqfast; *eqfunc = F_TEXTEQ; break; case OIDOID: case REGPROCOID: case REGPROCEDUREOID: case REGOPEROID: case REGOPERATOROID: case REGCLASSOID: case REGTYPEOID: case REGCONFIGOID: case REGDICTIONARYOID: case REGROLEOID: case REGNAMESPACEOID: *hashfunc = int4hashfast; *fasteqfunc = int4eqfast; *eqfunc = F_OIDEQ; break; case OIDVECTOROID: *hashfunc = oidvectorhashfast; *fasteqfunc = oidvectoreqfast; *eqfunc = F_OIDVECTOREQ; break; default: //The type of keyword is not supported as a cache key elog(FATAL, "type %u not supported as catcache key", keytype); *hashfunc = NULL; /* Keep the compiler quiet */ *eqfunc = InvalidOid; break; } }
Calculate the hash value to get the hash bucket
Set v1\v2v3v4 to cur_ In the corresponding element of the sky array, call CatalogCacheComputeHashValue (cache, cache->cc_nkeys, cur_skey) to calculate the hash value
/* * CatalogCacheComputeHashValue * Compute the hash value using the hash function that was previously set * Calculates the hash value associated with a given set of lookup keys */ static uint32 CatalogCacheComputeHashValue(CatCache *cache, int nkeys, Datum v1, Datum v2, Datum v3, Datum v4) { uint32 hashValue = 0; uint32 oneHash; CCHashFN *cc_hashfunc = cache->cc_hashfunc; CACHE_elog(DEBUG2, "CatalogCacheComputeHashValue %s %d %p",cache->cc_relname, nkeys, cache); switch (nkeys) { case 4: oneHash = (cc_hashfunc[3]) (v4); hashValue ^= oneHash << 24; hashValue ^= oneHash >> 8; /* fail */ case 3: oneHash = (cc_hashfunc[2]) (v3); hashValue ^= oneHash << 16; hashValue ^= oneHash >> 16; /* fail */ case 2: oneHash = (cc_hashfunc[1]) (v2); hashValue ^= oneHash << 8; hashValue ^= oneHash >> 24; /* fail */ case 1: oneHash = (cc_hashfunc[0]) (v1); hashValue ^= oneHash; break; default: //Hash key number error: elog(FATAL, "wrong number of hash keys: %d", nkeys); break; } return hashValue; }
Using HASH_INDEX macro calculates the index of the hash bucket and gets the CatCache at cc_ The subscript of the corresponding Hash bucket in the bucket array.
Traverse the Hash bucket chain to find a Dlelem that meets the query requirements and put dle_in its structure The value property is cast to the CatCTup type, and HeapKeyTest is used to test whether the cached tuple matches the input key. If found, use DLMoveToFront to place the tuple first in the Hash bucket. If it is a positive tuple, refcount and cc_hits add 1 to return a tuple. If it is a negative tuple, cc_neg_hits plus 1 returns NULL.
If not found, indicating that no tuples are cached in SysCache, further scanning of the physical system tables is required to confirm whether the tuples to be found do not really exist or are not cached in the CatCache. If the scanned physical system table can find the qualified remaining guilty hostess, then the tuple needs to be wrapped as Dlelem and added to its corresponding Hash bucket inner chain table header and returned to the tuple. If the tuple to be found is not found in the physical system table, then the tuple does not exist, and a negative tuple with only key values but no actual tuple is constructed. It is packed and added to the head of the chain inside the Hash bucket.
/* * CatalogCacheComputeTupleHashValue * Compute tuple hash * Calculates the hash value associated with a given tuple to cache */ static uint32 CatalogCacheComputeTupleHashValue(CatCache *cache, int nkeys, HeapTuple tuple) { Datum v1 = 0,//V1v2v3v4 is used to calculate the hash value of tuples, corresponding to the tuple search keys recorded in the Cache, respectively. v2 = 0, v3 = 0, v4 = 0; bool isNull = false; int *cc_keyno = cache->cc_keyno; TupleDesc cc_tupdesc = cache->cc_tupdesc; /* Extract a key field from a tuple and insert it into scankey */ switch (nkeys) { case 4: v4 = fastgetattr(tuple, cc_keyno[3], cc_tupdesc, &isNull); Assert(!isNull); /* fail */ case 3: v3 = fastgetattr(tuple, cc_keyno[2], cc_tupdesc, &isNull); Assert(!isNull); /* fail */ case 2: v2 = fastgetattr(tuple, cc_keyno[1], cc_tupdesc, &isNull); Assert(!isNull); /* fail */ case 1: v1 = fastgetattr(tuple, cc_keyno[0], cc_tupdesc, &isNull); Assert(!isNull); break; default: elog(FATAL, "wrong number of hash keys: %d", nkeys); break; } return CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4); }
The SearchCatCache function has a call to the function CatalogCacheCompareTuple on its way
/* * CatalogCacheCompareTuple * Directory Cache Compare Tuple * Compare a tuple with the passed parameters. */ static inline bool CatalogCacheCompareTuple(const CatCache *cache, int nkeys, const Datum *cachekeys, const Datum *searchkeys) { const CCFastEqualFN *cc_fastequal = cache->cc_fastequal; int i; for (i = 0; i < nkeys; i++) { if (!(cc_fastequal[i]) (cachekeys[i], searchkeys[i])) return false; } return true; }
If no matching tuples are found at the end of the iteration, the SearchCatCacheMiss function is reached, not just returning a cache lookup failure.
static pg_noinline HeapTuple SearchCatCacheMiss(CatCache *cache,int nkeys,uint32 hashValue,Index hashIndex, Datum v1,Datum v2,Datum v3,Datum v4) { ScanKeyData cur_skey[CATCACHE_MAXKEYS];//Store keywords for scanning Relation relation;//Storage table SysScanDesc scandesc;//System table scan descriptor HeapTuple ntp;//Storage tuple CatCTup *ct;//That is, tuple pointer in hash bucket Datum arguments[CATCACHE_MAXKEYS];//Store keyword parameters //Initialization parameter array arguments[0] = v1; arguments[1] = v2; arguments[2] = v3; arguments[3] = v4; //Make physical system tables memcpy(cur_skey, cache->cc_skey, sizeof(ScanKeyData) * nkeys); cur_skey[0].sk_argument = v1; cur_skey[1].sk_argument = v2; cur_skey[2].sk_argument = v3; cur_skey[3].sk_argument = v4; //Open the corresponding system table relation = table_open(cache->cc_reloid, AccessShareLock); //Scan the physical system tables to confirm that the tuple you are looking for does not really exist or is not cached in the CatCache scandesc = systable_beginscan( relation,cache->cc_indexoid, IndexScanOK(cache,cur_skey),NULL,nkeys,cur_skey); ct = NULL; while (HeapTupleIsValid(ntp = systable_getnext(scandesc)))//Get tuples from open system tables { ct = CatalogCacheCreateEntry(cache, ntp, arguments, hashValue, hashIndex, false);//Put tuples in the hash bucket ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); ct->refcount++;//Set the number of references to 1 immediately ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); break;//This is break because only one matches is found by default } systable_endscan(scandesc);//Close Descriptor table_close(relation, AccessShareLock);//Close Open Table if (ct == NULL)//If the corresponding tuple was not found in the system table just now { ct = CatalogCacheCreateEntry(cache, NULL, arguments, hashValue, hashIndex, true);//Construct a negative tuple CACHE_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples", cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup); CACHE_elog(DEBUG2, "SearchCatCache(%s): put neg entry in bucket %d", cache->cc_relname, hashIndex); //Since the return was not found, there is no need to add one to the reference count return NULL;//Return not found } CACHE_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples", cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup); CACHE_elog(DEBUG2, "SearchCatCache(%s): put in bucket %d", cache->cc_relname, hashIndex); #ifdef CATCACHE_STATS cache->cc_newloads++;//Cache Load Count Plus One #endif return &ct->tuple;//Returns tuples loaded into ct data structures }
Function IndexScanOK needs to be called when scanning physical tables
/* * IndexScanOK * Scan Index * * This function checks tuples that IndexSupportInitialize() obtains during relcache initialization to support some system indexes of critical syscache. * We can't use indexscan to get these, or we'll go into infinite recursion. However, normal heap scanning can work. Once we have finished relcache initialization (signaled by critical RelcachesBuilt), we don't have to worry anymore. * * Similarly, during backend startup, we must be able to use pg_authid and pg_auth_members syscaches authenticate even though we do not have relcache entries for these directory indexes. */ static bool IndexScanOK(CatCache *cache, ScanKey cur_skey) { switch (cache->id) { case INDEXRELID: /* * Before we use indexscans (which are always changing), we don't need to track exactly which indexes must be loaded, just force all pg_s Index searches are heap scans until we have built a critical relcache. */ if (!criticalRelcachesBuilt) return false; break; case AMOID: case AMNAME: /* * Always at pg_ Do heap scan in am because it is so small that indexscan doesn't mean much anyway * Meaning. When we first built the key relcache entries, we "had to" do this, but we could always do this * Do so. */ return false; case AUTHNAME: case AUTHOID: case AUTHMEMMEMROLE: /* * Protects authentication lookups that occur before relcache collects entries for a shared index. */ if (!criticalSharedRelcachesBuilt) return false; break; default: break; } /* Normally, allow index scan */ return true; }
When you put tuples in a hash bucket or create negative tuples, you need to call the function CatalogCacheCreateEntry
/* * CatalogCacheCreateEntry * CatalogCache Create Entry * Create a new catcup entry to copy the given HeapTuple and other supplied data into it. * The initial refcount for the new entry is 0. */ static CatCTup * CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments, uint32 hashValue, Index hashIndex, bool negative) { CatCTup *ct; HeapTuple dtp; MemoryContext oldcxt; /* Negative item has no associated tuple */ if (ntp) { int i; Assert(!negative); /* * If there are any out-of-line toasted fields in the tuple, expand them inline. This uses catcache later * Items save cycles and prevent toast tuples from being released before attempting to get them, in case some * Things use slightly outdated catcache items. */ if (HeapTupleHasExternal(ntp)) dtp = toast_flatten_tuple(ntp, cache->cc_tupdesc); else dtp = ntp; /* Allocate memory for catcup and cache components at once */ oldcxt = MemoryContextSwitchTo(CacheMemoryContext); ct = (CatCTup *) palloc(sizeof(CatCTup) + MAXIMUM_ALIGNOF + dtp->t_len); 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)); /* Content copy of tuples */ memcpy((char *) ct->tuple.t_data, (const char *) dtp->t_data, dtp->t_len); MemoryContextSwitchTo(oldcxt); if (dtp != ntp) heap_freetuple(dtp); /* Extract keys - if not by value, they point to tuples */ for (i = 0; i < cache->cc_nkeys; i++) { Datum atp; bool isnull; atp = heap_getattr(&ct->tuple, cache->cc_keyno[i], cache->cc_tupdesc, &isnull); Assert(!isnull); ct->keys[i] = atp; } } else { Assert(negative); oldcxt = MemoryContextSwitchTo(CacheMemoryContext); ct = (CatCTup *) palloc(sizeof(CatCTup)); /* Storage keys - they point to separately allocated memory, if not by value. */ CatCacheCopyKeys(cache->cc_tupdesc, cache->cc_nkeys, cache->cc_keyno, arguments, ct->keys); MemoryContextSwitchTo(oldcxt); } /* Complete the initialization of the catcup header and add it to the cached chain table and count. */ ct->ct_magic = CT_MAGIC; ct->my_cache = cache; ct->c_list = NULL; ct->refcount = 0; /* at present */ ct->dead = false; ct->negative = negative; ct->hash_value = hashValue; dlist_push_head(&cache->cc_bucket[hashIndex], &ct->cache_elem); cache->cc_ntup++; CacheHdr->ch_ntup++; /* If the hash table is too full, expand the bucket array. When Fill Factor > 2, we zoom in arbitrarily */ if (cache->cc_ntup > cache->cc_nbuckets * 2) RehashCatCache(cache); return ct; }
Call RehashCatCache
/* Expand the cache to double the number of buckets. */ static void RehashCatCache(CatCache *cp) { dlist_head *newbucket; int newnbuckets; int i; //Directory cache id for re-hashing buckets elog(DEBUG1, "rehashing catalog cache id %d for %s; %d tups, %d buckets", cp->id, cp->cc_relname, cp->cc_ntup, cp->cc_nbuckets); /* Assign a new, larger hash table. */ newnbuckets = cp->cc_nbuckets * 2; newbucket = (dlist_head *) MemoryContextAllocZero(CacheMemoryContext, newnbuckets * sizeof(dlist_head)); /* Move all entries in the old hash table to the new one. */ 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); int hashIndex = HASH_INDEX(ct->hash_value, newnbuckets);//Compute the hash value and find the corresponding hash bucket number based on the hash value dlist_delete(iter.cur); dlist_push_head(&newbucket[hashIndex], &ct->cache_elem); } } /* Switch to a new array. */ pfree(cp->cc_bucket); cp->cc_nbuckets = newnbuckets; cp->cc_bucket = newbucket; }
The function CatCacheCopyKeys needs to be called
/* Helper Routine that copies keys from the srckeys array to the dstkeys array to ensure that the data is adequately allocated in the current memory context. */ static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos, Datum *srckeys, Datum *dstkeys) { int i; /* Memory and lookup performance can be improved by storing all keys in one allocation. */ for (i = 0; i < nkeys; i++) { int attnum = attnos[i]; Form_pg_attribute att = TupleDescAttr(tupdesc, attnum - 1); Datum src = srckeys[i]; NameData srcname; /* Care must be taken when the caller passes a C string requiring NAME: the given parameter is converted to a properly populated NAME. Otherwise, memcpy() completed by datumCopy() may fall off the end of memory. */ if (att->atttypid == NAMEOID) { namestrcpy(&srcname, DatumGetCString(src)); src = NameGetDatum(&srcname); } dstkeys[i] = datumCopy(src, att->attbyval, att->attlen); } }
Partial Lookup
Partial matching uses the function SearchCatcacheList, which produces a CatCList structure in which tuples found in the Cache are stored in a chain table.
The tuple field record in a CatCList is a negative tuple that is used only to store the key values used by the CatCList and has no other users. The tuple contained in the CatCList is actually recorded with variable-length data represented by the members field whose actual length is recorded by n_members field record.
The SearchCatCacheList function also first calculates the Hash value of the lookup key, but it first calculates the cc_value of the CatCache Find whether the results of the lookup key were previously cached in the CatCList chain table recorded in the lists field, which uses the tuple field in the CatCList to compare Hash values with the lookup key.
If a CatCList matching the Hash value can be found, cc_lhits plus 1 and move the CatCList to cc_lists point to the header of the list and return the CatCList found. If CatCList is not found in CatCache, scan the physical system tables and build the corresponding CatCList and add it to cc_ The lists point to the head of the chain table.
Main Code
/* * SearchCatCacheList * * Generates a list of all tuples that match a partial key (that is, a key specifies only the first K of the N key columns cached). * It makes no sense to specify all the key columns for the cache here: since the keys are unique and there can be at most one match, you should use SearchCatCache() instead. * Therefore, this function accepts one less Datum parameter than SearchCatCache(). */ CatCList * SearchCatCacheList(CatCache *cache, int nkeys, Datum v1, Datum v2, Datum v3) { Datum v4 = 0; /* Last column value */ Datum arguments[CATCACHE_MAXKEYS]; uint32 lHashValue; dlist_iter iter; CatCList *cl; CatCTup *ct; List *volatile ctlist; ListCell *ctlist_item; int nmembers; bool ordered; HeapTuple ntp; MemoryContext oldcxt; int i; /* One-time startup overhead per cache */ if (cache->cc_tupdesc == NULL) CatalogCacheInitializeCache(cache); Assert(nkeys > 0 && nkeys < cache->cc_nkeys); #ifdef CATCACHE_STATS cache->cc_lsearches++; #endif /* Initialize local parameter array */ arguments[0] = v1; arguments[1] = v2; arguments[2] = v3; arguments[3] = v4; /* Calculates the hash value of a given key for faster searching. We haven't broken CatCList items into buckets yet, but this still allows us to skip unmatched items quickly most of the time. */ lHashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4); /* *Scan these items until we find a match or exhaust our list *Note: dlist_can be used here Foreach, because even if we modify the DLIST in the loop, the loop will not continue after that. */ dlist_foreach(iter, &cache->cc_lists) { cl = dlist_container(CatCList, cache_elem, iter.cur); if (cl->dead) continue; /* Ignore dead tuples */ if (cl->hash_value != lHashValue) continue; /* Hash errors are skipped quickly */ /* See if the cache list matches our keys. */ if (cl->nkeys != nkeys) continue; if (!CatalogCacheCompareTuple(cache, nkeys, cl->keys, arguments))//Skip if key values do not match continue; /* * Being able to do so indicates that a matching tuple has been found in the cache and moved to the chain header of the hash bucket. This * Is to speed up subsequent searches. * This practice of putting the most recently visited element at the front of the list of chains allows the most frequently visited element to be quickly accessed * Visit. */ dlist_move_head(&cache->cc_lists, &cl->cache_elem); /* Increase the reference count of the list and return it */ ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); cl->refcount++;//Add its citation technique to one ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl); CACHE_elog(DEBUG2, "SearchCatCacheList(%s): found list", cache->cc_relname); #ifdef CATCACHE_STATS cache->cc_lhits++;//Cache hits plus one #endif return cl;//Return this tuple } /* List Not found in the cache, so we must build it by reading the relationship. For each matching element found in the relationship * Group, use existing cache entries if possible, otherwise build a new one. * We must temporarily add refcounts for members to ensure they are not removed from the cache when other members are loaded. We make * With a PG_ The TRY block ensures that if there are errors before the CatCList is built, we can undo these refcount s. */ ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); ctlist = NIL; PG_TRY(); { ScanKeyData cur_skey[CATCACHE_MAXKEYS]; Relation relation; SysScanDesc scandesc; /* Look in the relationship, copy scankey, and fill in each calling field.*/ memcpy(cur_skey, cache->cc_skey, sizeof(ScanKeyData) * cache->cc_nkeys); cur_skey[0].sk_argument = v1; cur_skey[1].sk_argument = v2; cur_skey[2].sk_argument = v3; cur_skey[3].sk_argument = v4; relation = table_open(cache->cc_reloid, AccessShareLock); scandesc = systable_beginscan(relation, cache->cc_indexoid, IndexScanOK(cache, cur_skey), NULL, nkeys, cur_skey); /* If we're doing an index scan, the list will be sorted */ ordered = (scandesc->irel != NULL); while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) { uint32 hashValue; Index hashIndex; bool found = false; dlist_head *bucket; /* * See if this tuple already has entries. */ ct = NULL; hashValue = CatalogCacheComputeTupleHashValue(cache, cache->cc_nkeys, ntp); hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); bucket = &cache->cc_bucket[hashIndex]; dlist_foreach(iter, bucket) { ct = dlist_container(CatCTup, cache_elem, iter.cur); if (ct->dead || ct->negative) continue; /* Ignore dead and negative tuples */ if (ct->hash_value != hashValue) continue; /* Hash errors are skipped quickly */ if (!ItemPointerEquals(&(ct->tuple.t_self), &(ntp->t_self))) continue; //Skip if key values do not match /* Find a match, but cannot use it if it already belongs to another list */ if (ct->c_list) continue; found = true; break; /* A-OK */ } if (!found) { /* We couldn't find an available entry, so we did it again */ ct = CatalogCacheCreateEntry(cache, ntp, arguments, hashValue, hashIndex, false); } /* Note: Add an entry to the ctlist and increase its reference count */ /* This method will keep the state correct if lappend runs out of memory */ ctlist = lappend(ctlist, ct); ct->refcount++; } systable_endscan(scandesc); table_close(relation, AccessShareLock); /* Now we can build the CatCList entry. */ oldcxt = MemoryContextSwitchTo(CacheMemoryContext); nmembers = list_length(ctlist); cl = (CatCList *) palloc(offsetof(CatCList, members) + nmembers * sizeof(CatCTup *)); /* Get Key Value */ CatCacheCopyKeys(cache->cc_tupdesc, nkeys, cache->cc_keyno, arguments, cl->keys); MemoryContextSwitchTo(oldcxt); /* * Before we finish building CatCList and remembering it as the resource owner, we've now experienced possible touches * The last thing to do with the elog log management system. Therefore, exit PG_TRY is okay, in fact, we'd better turn it on * Do this before you start marking members as belonging to the list. */ } PG_CATCH(); { foreach(ctlist_item, ctlist) { ct = (CatCTup *) lfirst(ctlist_item); Assert(ct->c_list == NULL); Assert(ct->refcount > 0); ct->refcount--; if ( #ifndef CATCACHE_FORCE_RELEASE ct->dead && #endif ct->refcount == 0 && (ct->c_list == NULL || ct->c_list->refcount == 0)) CatCacheRemoveCTup(cache, ct); } PG_RE_THROW(); } PG_END_TRY(); cl->cl_magic = CL_MAGIC; cl->my_cache = cache; cl->refcount = 0; /* at present */ cl->dead = false; cl->ordered = ordered; cl->nkeys = nkeys; cl->hash_value = lHashValue; cl->n_members = nmembers; i = 0; foreach(ctlist_item, ctlist) { cl->members[i++] = ct = (CatCTup *) lfirst(ctlist_item); Assert(ct->c_list == NULL); ct->c_list = cl; /* Release Temporary References on Members */ Assert(ct->refcount > 0); ct->refcount--; /* If a member has died, mark the death list */ if (ct->dead) cl->dead = true; } Assert(i == nmembers); dlist_push_head(&cache->cc_lists, &cl->cache_elem); /* Finally, increase the number of references to the list and return it */ cl->refcount++; ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl); CACHE_elog(DEBUG2, "SearchCatCacheList(%s): made list of %d members", cache->cc_relname, nmembers); return cl; }
On the way to SeachCatCacheList, the CatalogCacheInitializeCache was also called to load the system tables.
Call CatalogCacheComputeHashValue to calculate the hash value for the given key.
Call CatalogCacheCompareTuple to compare tuples and passed parameters.
Call IndexScanOK and scan the physical system to confirm that the tuple you are looking for does not really exist or is not replaced by a CatCache.
Call CatalogCacheCreateEntry to regenerate an entry when no one is available.
Call RehashCatCache to expand the capacity of the bucket,
Call CatCacheCopyKeys to get the key value,
Call CatalogCacheComputeTupleHashValue to calculate the tuple hash value and the hash value associated with the given tuple to be cached.
Then call CatCacheRemoveCTup
/* * CatCacheRemoveCTup * * Unlink and delete the given cache entry * * * NB: If it is a member of a CatCList, the CatCList is also deleted. * Cached entries and lists are best left without reference counts. */ static void CatCacheRemoveCTup(CatCache *cache, CatCTup *ct) { Assert(ct->refcount == 0); Assert(ct->my_cache == cache); if (ct->c_list) { /* * The cleanest way to handle this problem is to call CatCacheRemoveCList, which will recursively call me back * The work will be completed. Set the Death flag to make sure it recurs. */ ct->dead = true; CatCacheRemoveCList(cache, ct->c_list); return; /* Nothing to do */ } /* Break Link from Chain List */ dlist_delete(&ct->cache_elem); /* * When we deal with a negative entry, the free key, the normal entry just points to the tuple and is allocated with the catcup. */ if (ct->negative) CatCacheFreeKeys(cache->cc_tupdesc, cache->cc_nkeys, cache->cc_keyno, ct->keys); pfree(ct); --cache->cc_ntup; --CacheHdr->ch_ntup; }
CatCacheFreeKeys is called when ct is negative
/* * Helper that releases keys stored in a key array. */ static void CatCacheFreeKeys(TupleDesc tupdesc, int nkeys, int *attnos, Datum *keys) { int i; for (i = 0; i < nkeys; i++) { int attnum = attnos[i]; Form_pg_attribute att; /* Do not support system properties in caching */ Assert(attnum > 0); att = TupleDescAttr(tupdesc, attnum - 1); if (!att->attbyval) pfree(DatumGetPointer(keys[i])); } }
The caller cannot modify the list object or tuple being pointed to, and must call ReleaseCatCacheList() while processing the list.
/* * ReleaseCatCacheList * * Reduce the reference count of the catcache list. */ void ReleaseCatCacheList(CatCList *list) { /* Security check to make sure we get a cache entry */ Assert(list->cl_magic == CL_MAGIC); Assert(list->refcount > 0); list->refcount--; ResourceOwnerForgetCatCacheListRef(CurrentResourceOwner, list); if ( #ifndef CATCACHE_FORCE_RELEASE list->dead && #endif list->refcount == 0) CatCacheRemoveCList(list->my_cache, list); }
The CatCacheRemoveCList function is last called
/* * CatCacheRemoveCList * Unlink and delete the given cache list entry * Note: Any invalid member entries will also be deleted. */ static void CatCacheRemoveCList(CatCache *cache, CatCList *cl) { int i; Assert(cl->refcount == 0); Assert(cl->my_cache == cache); /* Decouple from member tuples */ for (i = cl->n_members; --i >= 0;) { CatCTup *ct = cl->members[i]; Assert(ct->c_list == cl); ct->c_list = NULL; /* If the member is dead and not referenced now, delete it */ if ( #ifndef CATCACHE_FORCE_RELEASE ct->dead && #endif ct->refcount == 0) CatCacheRemoveCTup(cache, ct); } /* Break Link from Chain List */ dlist_delete(&cl->cache_elem); /* Freely Associated Column Data */ CatCacheFreeKeys(cache->cc_tupdesc, cl->nkeys, cache->cc_keyno, cl->keys); pfree(cl); }
Some content comes from:
"PostgreSQL Database Kernel Analysis" by Peng Zhiyong and Peng Yuwei
PG cache 03
Memory management (3)