Skip to content

Commit 473182c

Browse files
committed
Use a hash table for catcache.c's CatCList objects.
Up to now, all of the "catcache list" objects within a catalog cache were just chained together on a single dlist, requiring O(N) time to search. Remarkably, we've not had serious performance problems with that so far; but we got a complaint of a bad performance regression from v15 in a case with a large number of roles in the system, which traced down to O(N^2) total time when we probed N catcache lists. Replace that data structure with a hashtable having an enlargeable number of dlists, in an exactly parallel way to the data structure we've used for years for the plain CatCTup cache members. The extra cost of maintaining a hash table seems negligible, since we were already computing a hash value for list searches. Normally this'd be HEAD-only material, but in view of the performance regression it seems advisable to back-patch into v16. In the v16 version of the patch, leave the dead cc_lists field where it is and add the new fields at the end of struct catcache, to avoid possible ABI breakage in case any external code is looking at these structs. (We assume no external code is actually allocating new catcache structs.) Per report from alex work. Discussion: https://postgr.es/m/CAGvXd3OSMbJQwOSc-Tq-Ro1CAz=vggErdSG7pv2s6vmmTOLJSg@mail.gmail.com
1 parent 6acb0a6 commit 473182c

File tree

2 files changed

+124
-25
lines changed

2 files changed

+124
-25
lines changed

src/backend/utils/cache/catcache.c

Lines changed: 120 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ static void CatCachePrintStats(int code, Datum arg);
8888
#endif
8989
static void CatCacheRemoveCTup(CatCache *cache, CatCTup *ct);
9090
static void CatCacheRemoveCList(CatCache *cache, CatCList *cl);
91+
static void RehashCatCache(CatCache *cp);
92+
static void RehashCatCacheLists(CatCache *cp);
9193
static void CatalogCacheInitializeCache(CatCache *cache);
9294
static CatCTup *CatalogCacheCreateEntry(CatCache *cache,
9395
HeapTuple ntp, SysScanDesc scandesc,
@@ -444,6 +446,7 @@ CatCachePrintStats(int code, Datum arg)
444446
long cc_neg_hits = 0;
445447
long cc_newloads = 0;
446448
long cc_invals = 0;
449+
long cc_nlists = 0;
447450
long cc_lsearches = 0;
448451
long cc_lhits = 0;
449452

@@ -453,7 +456,7 @@ CatCachePrintStats(int code, Datum arg)
453456

454457
if (cache->cc_ntup == 0 && cache->cc_searches == 0)
455458
continue; /* don't print unused caches */
456-
elog(DEBUG2, "catcache %s/%u: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld lsrch, %ld lhits",
459+
elog(DEBUG2, "catcache %s/%u: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %d lists, %ld lsrch, %ld lhits",
457460
cache->cc_relname,
458461
cache->cc_indexoid,
459462
cache->cc_ntup,
@@ -465,17 +468,19 @@ CatCachePrintStats(int code, Datum arg)
465468
cache->cc_searches - cache->cc_hits - cache->cc_neg_hits - cache->cc_newloads,
466469
cache->cc_searches - cache->cc_hits - cache->cc_neg_hits,
467470
cache->cc_invals,
471+
cache->cc_nlist,
468472
cache->cc_lsearches,
469473
cache->cc_lhits);
470474
cc_searches += cache->cc_searches;
471475
cc_hits += cache->cc_hits;
472476
cc_neg_hits += cache->cc_neg_hits;
473477
cc_newloads += cache->cc_newloads;
474478
cc_invals += cache->cc_invals;
479+
cc_nlists += cache->cc_nlist;
475480
cc_lsearches += cache->cc_lsearches;
476481
cc_lhits += cache->cc_lhits;
477482
}
478-
elog(DEBUG2, "catcache totals: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld lsrch, %ld lhits",
483+
elog(DEBUG2, "catcache totals: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld lists, %ld lsrch, %ld lhits",
479484
CacheHdr->ch_ntup,
480485
cc_searches,
481486
cc_hits,
@@ -485,6 +490,7 @@ CatCachePrintStats(int code, Datum arg)
485490
cc_searches - cc_hits - cc_neg_hits - cc_newloads,
486491
cc_searches - cc_hits - cc_neg_hits,
487492
cc_invals,
493+
cc_nlists,
488494
cc_lsearches,
489495
cc_lhits);
490496
}
@@ -573,6 +579,8 @@ CatCacheRemoveCList(CatCache *cache, CatCList *cl)
573579
cache->cc_keyno, cl->keys);
574580

575581
pfree(cl);
582+
583+
--cache->cc_nlist;
576584
}
577585

578586

@@ -611,14 +619,19 @@ CatCacheInvalidate(CatCache *cache, uint32 hashValue)
611619
* Invalidate *all* CatCLists in this cache; it's too hard to tell which
612620
* searches might still be correct, so just zap 'em all.
613621
*/
614-
dlist_foreach_modify(iter, &cache->cc_lists)
622+
for (int i = 0; i < cache->cc_nlbuckets; i++)
615623
{
616-
CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur);
624+
dlist_head *bucket = &cache->cc_lbucket[i];
617625

618-
if (cl->refcount > 0)
619-
cl->dead = true;
620-
else
621-
CatCacheRemoveCList(cache, cl);
626+
dlist_foreach_modify(iter, bucket)
627+
{
628+
CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur);
629+
630+
if (cl->refcount > 0)
631+
cl->dead = true;
632+
else
633+
CatCacheRemoveCList(cache, cl);
634+
}
622635
}
623636

624637
/*
@@ -691,14 +704,19 @@ ResetCatalogCache(CatCache *cache)
691704
int i;
692705

693706
/* Remove each list in this cache, or at least mark it dead */
694-
dlist_foreach_modify(iter, &cache->cc_lists)
707+
for (i = 0; i < cache->cc_nlbuckets; i++)
695708
{
696-
CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur);
709+
dlist_head *bucket = &cache->cc_lbucket[i];
697710

698-
if (cl->refcount > 0)
699-
cl->dead = true;
700-
else
701-
CatCacheRemoveCList(cache, cl);
711+
dlist_foreach_modify(iter, bucket)
712+
{
713+
CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur);
714+
715+
if (cl->refcount > 0)
716+
cl->dead = true;
717+
else
718+
CatCacheRemoveCList(cache, cl);
719+
}
702720
}
703721

704722
/* Remove each tuple in this cache, or at least mark it dead */
@@ -862,6 +880,12 @@ InitCatCache(int id,
862880
MCXT_ALLOC_ZERO);
863881
cp->cc_bucket = palloc0(nbuckets * sizeof(dlist_head));
864882

883+
/*
884+
* Many catcaches never receive any list searches. Therefore, we don't
885+
* allocate the cc_lbuckets till we get a list search.
886+
*/
887+
cp->cc_lbucket = NULL;
888+
865889
/*
866890
* initialize the cache's relation information for the relation
867891
* corresponding to this cache, and initialize some of the new cache's
@@ -874,7 +898,9 @@ InitCatCache(int id,
874898
cp->cc_relisshared = false; /* temporary */
875899
cp->cc_tupdesc = (TupleDesc) NULL;
876900
cp->cc_ntup = 0;
901+
cp->cc_nlist = 0;
877902
cp->cc_nbuckets = nbuckets;
903+
cp->cc_nlbuckets = 0;
878904
cp->cc_nkeys = nkeys;
879905
for (i = 0; i < nkeys; ++i)
880906
{
@@ -939,6 +965,44 @@ RehashCatCache(CatCache *cp)
939965
cp->cc_bucket = newbucket;
940966
}
941967

968+
/*
969+
* Enlarge a catcache's list storage, doubling the number of buckets.
970+
*/
971+
static void
972+
RehashCatCacheLists(CatCache *cp)
973+
{
974+
dlist_head *newbucket;
975+
int newnbuckets;
976+
int i;
977+
978+
elog(DEBUG1, "rehashing catalog cache id %d for %s; %d lists, %d buckets",
979+
cp->id, cp->cc_relname, cp->cc_nlist, cp->cc_nlbuckets);
980+
981+
/* Allocate a new, larger, hash table. */
982+
newnbuckets = cp->cc_nlbuckets * 2;
983+
newbucket = (dlist_head *) MemoryContextAllocZero(CacheMemoryContext, newnbuckets * sizeof(dlist_head));
984+
985+
/* Move all entries from old hash table to new. */
986+
for (i = 0; i < cp->cc_nlbuckets; i++)
987+
{
988+
dlist_mutable_iter iter;
989+
990+
dlist_foreach_modify(iter, &cp->cc_lbucket[i])
991+
{
992+
CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur);
993+
int hashIndex = HASH_INDEX(cl->hash_value, newnbuckets);
994+
995+
dlist_delete(iter.cur);
996+
dlist_push_head(&newbucket[hashIndex], &cl->cache_elem);
997+
}
998+
}
999+
1000+
/* Switch to the new array. */
1001+
pfree(cp->cc_lbucket);
1002+
cp->cc_nlbuckets = newnbuckets;
1003+
cp->cc_lbucket = newbucket;
1004+
}
1005+
9421006
/*
9431007
* CatalogCacheInitializeCache
9441008
*
@@ -1588,7 +1652,9 @@ SearchCatCacheList(CatCache *cache,
15881652
Datum v4 = 0; /* dummy last-column value */
15891653
Datum arguments[CATCACHE_MAXKEYS];
15901654
uint32 lHashValue;
1655+
Index lHashIndex;
15911656
dlist_iter iter;
1657+
dlist_head *lbucket;
15921658
CatCList *cl;
15931659
CatCTup *ct;
15941660
List *volatile ctlist;
@@ -1602,7 +1668,7 @@ SearchCatCacheList(CatCache *cache,
16021668
/*
16031669
* one-time startup overhead for each cache
16041670
*/
1605-
if (cache->cc_tupdesc == NULL)
1671+
if (unlikely(cache->cc_tupdesc == NULL))
16061672
CatalogCacheInitializeCache(cache);
16071673

16081674
Assert(nkeys > 0 && nkeys < cache->cc_nkeys);
@@ -1618,19 +1684,45 @@ SearchCatCacheList(CatCache *cache,
16181684
arguments[3] = v4;
16191685

16201686
/*
1621-
* compute a hash value of the given keys for faster search. We don't
1622-
* presently divide the CatCList items into buckets, but this still lets
1623-
* us skip non-matching items quickly most of the time.
1687+
* If we haven't previously done a list search in this cache, create the
1688+
* bucket header array; otherwise, consider whether it's time to enlarge
1689+
* it.
1690+
*/
1691+
if (cache->cc_lbucket == NULL)
1692+
{
1693+
/* Arbitrary initial size --- must be a power of 2 */
1694+
int nbuckets = 16;
1695+
1696+
cache->cc_lbucket = (dlist_head *)
1697+
MemoryContextAllocZero(CacheMemoryContext,
1698+
nbuckets * sizeof(dlist_head));
1699+
/* Don't set cc_nlbuckets if we get OOM allocating cc_lbucket */
1700+
cache->cc_nlbuckets = nbuckets;
1701+
}
1702+
else
1703+
{
1704+
/*
1705+
* If the hash table has become too full, enlarge the buckets array.
1706+
* Quite arbitrarily, we enlarge when fill factor > 2.
1707+
*/
1708+
if (cache->cc_nlist > cache->cc_nlbuckets * 2)
1709+
RehashCatCacheLists(cache);
1710+
}
1711+
1712+
/*
1713+
* Find the hash bucket in which to look for the CatCList.
16241714
*/
16251715
lHashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4);
1716+
lHashIndex = HASH_INDEX(lHashValue, cache->cc_nlbuckets);
16261717

16271718
/*
16281719
* scan the items until we find a match or exhaust our list
16291720
*
16301721
* Note: it's okay to use dlist_foreach here, even though we modify the
16311722
* dlist within the loop, because we don't continue the loop afterwards.
16321723
*/
1633-
dlist_foreach(iter, &cache->cc_lists)
1724+
lbucket = &cache->cc_lbucket[lHashIndex];
1725+
dlist_foreach(iter, lbucket)
16341726
{
16351727
cl = dlist_container(CatCList, cache_elem, iter.cur);
16361728

@@ -1650,13 +1742,13 @@ SearchCatCacheList(CatCache *cache,
16501742
continue;
16511743

16521744
/*
1653-
* We found a matching list. Move the list to the front of the
1654-
* cache's list-of-lists, to speed subsequent searches. (We do not
1745+
* We found a matching list. Move the list to the front of the list
1746+
* for its hashbucket, so as to speed subsequent searches. (We do not
16551747
* move the members to the fronts of their hashbucket lists, however,
16561748
* since there's no point in that unless they are searched for
16571749
* individually.)
16581750
*/
1659-
dlist_move_head(&cache->cc_lists, &cl->cache_elem);
1751+
dlist_move_head(lbucket, &cl->cache_elem);
16601752

16611753
/* Bump the list's refcount and return it */
16621754
ResourceOwnerEnlarge(CurrentResourceOwner);
@@ -1868,7 +1960,12 @@ SearchCatCacheList(CatCache *cache,
18681960
}
18691961
Assert(i == nmembers);
18701962

1871-
dlist_push_head(&cache->cc_lists, &cl->cache_elem);
1963+
/*
1964+
* Add the CatCList to the appropriate bucket, and count it.
1965+
*/
1966+
dlist_push_head(lbucket, &cl->cache_elem);
1967+
1968+
cache->cc_nlist++;
18721969

18731970
/* Finally, bump the list's refcount and return it */
18741971
cl->refcount++;

src/include/utils/catcache.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ typedef struct catcache
5151
CCFastEqualFN cc_fastequal[CATCACHE_MAXKEYS]; /* fast equal function for
5252
* each key */
5353
int cc_keyno[CATCACHE_MAXKEYS]; /* AttrNumber of each key */
54-
dlist_head cc_lists; /* list of CatCList structs */
55-
int cc_ntup; /* # of tuples currently in this cache */
5654
int cc_nkeys; /* # of keys (1..CATCACHE_MAXKEYS) */
55+
int cc_ntup; /* # of tuples currently in this cache */
56+
int cc_nlist; /* # of CatCLists currently in this cache */
57+
int cc_nlbuckets; /* # of CatCList hash buckets in this cache */
58+
dlist_head *cc_lbucket; /* hash buckets for CatCLists */
5759
const char *cc_relname; /* name of relation the tuples come from */
5860
Oid cc_reloid; /* OID of relation the tuples come from */
5961
Oid cc_indexoid; /* OID of index matching cache keys */

0 commit comments

Comments
 (0)