PG Database Source-SysCache Partial Matching Mechanism

Posted by hanhao on Thu, 10 Mar 2022 19:05:13 +0100

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)

Topics: Database PostgreSQL