Skip to content

Commit e384ed6

Browse files
committed
Improve typcache: cache negative lookup results, add invalidation logic.
Previously, if the typcache had for example tried and failed to find a hash opclass for a given data type, it would nonetheless repeat the unsuccessful catalog lookup each time it was asked again. This can lead to a significant amount of useless bufmgr traffic, as in a recent report from Scott Marlowe. Like the catalog caches, typcache should be able to cache negative results. This patch arranges that by making use of separate flag bits to remember whether a particular item has been looked up, rather than treating a zero OID as an indicator that no lookup has been done. Also, install a credible invalidation mechanism, namely watching for inval events in pg_opclass. The sole advantage of the lack of negative caching was that the code would cope if operators or opclasses got added for a type mid-session; to preserve that behavior we have to be able to invalidate stale lookup results. Updates in pg_opclass should be pretty rare in production systems, so it seems sufficient to just invalidate all the dependent data whenever one happens. Adding proper invalidation also means that this code will now react sanely if an opclass is dropped mid-session. Arguably, that's a back-patchable bug fix, but in view of the lack of complaints from the field I'll refrain from back-patching. (Probably, in most cases where an opclass is dropped, the data type itself is dropped soon after, so that this misfeasance has no bad consequences.)
1 parent 202cbdf commit e384ed6

File tree

1 file changed

+127
-63
lines changed

1 file changed

+127
-63
lines changed

src/backend/utils/cache/typcache.c

Lines changed: 127 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,16 @@
1717
* is kept in the type cache.
1818
*
1919
* Once created, a type cache entry lives as long as the backend does, so
20-
* there is no need for a call to release a cache entry. (For present uses,
20+
* there is no need for a call to release a cache entry. If the type is
21+
* dropped, the cache entry simply becomes wasted storage. (For present uses,
2122
* it would be okay to flush type cache entries at the ends of transactions,
2223
* if we needed to reclaim space.)
2324
*
24-
* There is presently no provision for clearing out a cache entry if the
25-
* stored data becomes obsolete. (The code will work if a type acquires
26-
* opclasses it didn't have before while a backend runs --- but not if the
27-
* definition of an existing opclass is altered.) However, the relcache
28-
* doesn't cope with opclasses changing under it, either, so this seems
29-
* a low-priority problem.
30-
*
31-
* We do support clearing the tuple descriptor and operator/function parts
32-
* of a rowtype's cache entry, since those may need to change as a consequence
33-
* of ALTER TABLE.
25+
* We have some provisions for updating cache entries if the stored data
26+
* becomes obsolete. Information dependent on opclasses is cleared if we
27+
* detect updates to pg_opclass. We also support clearing the tuple
28+
* descriptor and operator/function parts of a rowtype's cache entry,
29+
* since those may need to change as a consequence of ALTER TABLE.
3430
*
3531
*
3632
* Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
@@ -70,13 +66,20 @@
7066
static HTAB *TypeCacheHash = NULL;
7167

7268
/* Private flag bits in the TypeCacheEntry.flags field */
73-
#define TCFLAGS_CHECKED_ELEM_PROPERTIES 0x0001
74-
#define TCFLAGS_HAVE_ELEM_EQUALITY 0x0002
75-
#define TCFLAGS_HAVE_ELEM_COMPARE 0x0004
76-
#define TCFLAGS_HAVE_ELEM_HASHING 0x0008
77-
#define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x0010
78-
#define TCFLAGS_HAVE_FIELD_EQUALITY 0x0020
79-
#define TCFLAGS_HAVE_FIELD_COMPARE 0x0040
69+
#define TCFLAGS_CHECKED_BTREE_OPCLASS 0x0001
70+
#define TCFLAGS_CHECKED_HASH_OPCLASS 0x0002
71+
#define TCFLAGS_CHECKED_EQ_OPR 0x0004
72+
#define TCFLAGS_CHECKED_LT_OPR 0x0008
73+
#define TCFLAGS_CHECKED_GT_OPR 0x0010
74+
#define TCFLAGS_CHECKED_CMP_PROC 0x0020
75+
#define TCFLAGS_CHECKED_HASH_PROC 0x0040
76+
#define TCFLAGS_CHECKED_ELEM_PROPERTIES 0x0080
77+
#define TCFLAGS_HAVE_ELEM_EQUALITY 0x0100
78+
#define TCFLAGS_HAVE_ELEM_COMPARE 0x0200
79+
#define TCFLAGS_HAVE_ELEM_HASHING 0x0400
80+
#define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x0800
81+
#define TCFLAGS_HAVE_FIELD_EQUALITY 0x1000
82+
#define TCFLAGS_HAVE_FIELD_COMPARE 0x2000
8083

8184
/* Private information to support comparisons of enum values */
8285
typedef struct
@@ -132,6 +135,7 @@ static bool record_fields_have_equality(TypeCacheEntry *typentry);
132135
static bool record_fields_have_compare(TypeCacheEntry *typentry);
133136
static void cache_record_field_properties(TypeCacheEntry *typentry);
134137
static void TypeCacheRelCallback(Datum arg, Oid relid);
138+
static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
135139
static void load_enum_cache_data(TypeCacheEntry *tcache);
136140
static EnumItem *find_enumitem(TypeCacheEnumData *enumdata, Oid arg);
137141
static int enum_oid_cmp(const void *left, const void *right);
@@ -166,8 +170,9 @@ lookup_type_cache(Oid type_id, int flags)
166170
TypeCacheHash = hash_create("Type information cache", 64,
167171
&ctl, HASH_ELEM | HASH_FUNCTION);
168172

169-
/* Also set up a callback for relcache SI invalidations */
173+
/* Also set up callbacks for SI invalidations */
170174
CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0);
175+
CacheRegisterSyscacheCallback(CLAOID, TypeCacheOpcCallback, (Datum) 0);
171176

172177
/* Also make sure CacheMemoryContext exists */
173178
if (!CacheMemoryContext)
@@ -217,13 +222,14 @@ lookup_type_cache(Oid type_id, int flags)
217222
}
218223

219224
/*
220-
* If we haven't already found the opclasses, try to do so
225+
* Look up opclasses if we haven't already and any dependent info is
226+
* requested.
221227
*/
222228
if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_LT_OPR | TYPECACHE_GT_OPR |
223229
TYPECACHE_CMP_PROC |
224230
TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO |
225231
TYPECACHE_BTREE_OPFAMILY)) &&
226-
typentry->btree_opf == InvalidOid)
232+
!(typentry->flags & TCFLAGS_CHECKED_BTREE_OPCLASS))
227233
{
228234
Oid opclass;
229235

@@ -233,38 +239,36 @@ lookup_type_cache(Oid type_id, int flags)
233239
typentry->btree_opf = get_opclass_family(opclass);
234240
typentry->btree_opintype = get_opclass_input_type(opclass);
235241
}
236-
/* If no btree opclass, we force lookup of the hash opclass */
237-
if (typentry->btree_opf == InvalidOid)
238-
{
239-
if (typentry->hash_opf == InvalidOid)
240-
{
241-
opclass = GetDefaultOpClass(type_id, HASH_AM_OID);
242-
if (OidIsValid(opclass))
243-
{
244-
typentry->hash_opf = get_opclass_family(opclass);
245-
typentry->hash_opintype = get_opclass_input_type(opclass);
246-
}
247-
}
248-
}
249242
else
250243
{
251-
/*
252-
* In case we find a btree opclass where previously we only found
253-
* a hash opclass, reset eq_opr and derived information so that we
254-
* can fetch the btree equality operator instead of the hash
255-
* equality operator. (They're probably the same operator, but we
256-
* don't assume that here.)
257-
*/
258-
typentry->eq_opr = InvalidOid;
259-
typentry->eq_opr_finfo.fn_oid = InvalidOid;
260-
typentry->hash_proc = InvalidOid;
261-
typentry->hash_proc_finfo.fn_oid = InvalidOid;
244+
typentry->btree_opf = typentry->btree_opintype = InvalidOid;
262245
}
246+
247+
/*
248+
* Reset information derived from btree opclass. Note in particular
249+
* that we'll redetermine the eq_opr even if we previously found one;
250+
* this matters in case a btree opclass has been added to a type that
251+
* previously had only a hash opclass.
252+
*/
253+
typentry->flags &= ~(TCFLAGS_CHECKED_EQ_OPR |
254+
TCFLAGS_CHECKED_LT_OPR |
255+
TCFLAGS_CHECKED_GT_OPR |
256+
TCFLAGS_CHECKED_CMP_PROC);
257+
typentry->flags |= TCFLAGS_CHECKED_BTREE_OPCLASS;
263258
}
264259

260+
/*
261+
* If we need to look up equality operator, and there's no btree opclass,
262+
* force lookup of hash opclass.
263+
*/
264+
if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO)) &&
265+
!(typentry->flags & TCFLAGS_CHECKED_EQ_OPR) &&
266+
typentry->btree_opf == InvalidOid)
267+
flags |= TYPECACHE_HASH_OPFAMILY;
268+
265269
if ((flags & (TYPECACHE_HASH_PROC | TYPECACHE_HASH_PROC_FINFO |
266270
TYPECACHE_HASH_OPFAMILY)) &&
267-
typentry->hash_opf == InvalidOid)
271+
!(typentry->flags & TCFLAGS_CHECKED_HASH_OPCLASS))
268272
{
269273
Oid opclass;
270274

@@ -274,11 +278,25 @@ lookup_type_cache(Oid type_id, int flags)
274278
typentry->hash_opf = get_opclass_family(opclass);
275279
typentry->hash_opintype = get_opclass_input_type(opclass);
276280
}
281+
else
282+
{
283+
typentry->hash_opf = typentry->hash_opintype = InvalidOid;
284+
}
285+
286+
/*
287+
* Reset information derived from hash opclass. We do *not* reset the
288+
* eq_opr; if we already found one from the btree opclass, that
289+
* decision is still good.
290+
*/
291+
typentry->flags &= ~(TCFLAGS_CHECKED_HASH_PROC);
292+
typentry->flags |= TCFLAGS_CHECKED_HASH_OPCLASS;
277293
}
278294

279-
/* Look for requested operators and functions */
295+
/*
296+
* Look for requested operators and functions, if we haven't already.
297+
*/
280298
if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO)) &&
281-
typentry->eq_opr == InvalidOid)
299+
!(typentry->flags & TCFLAGS_CHECKED_EQ_OPR))
282300
{
283301
Oid eq_opr = InvalidOid;
284302

@@ -307,17 +325,22 @@ lookup_type_cache(Oid type_id, int flags)
307325
!record_fields_have_equality(typentry))
308326
eq_opr = InvalidOid;
309327

328+
/* Force update of eq_opr_finfo only if we're changing state */
329+
if (typentry->eq_opr != eq_opr)
330+
typentry->eq_opr_finfo.fn_oid = InvalidOid;
331+
310332
typentry->eq_opr = eq_opr;
311333

312334
/*
313335
* Reset info about hash function whenever we pick up new info about
314336
* equality operator. This is so we can ensure that the hash function
315337
* matches the operator.
316338
*/
317-
typentry->hash_proc = InvalidOid;
318-
typentry->hash_proc_finfo.fn_oid = InvalidOid;
339+
typentry->flags &= ~(TCFLAGS_CHECKED_HASH_PROC);
340+
typentry->flags |= TCFLAGS_CHECKED_EQ_OPR;
319341
}
320-
if ((flags & TYPECACHE_LT_OPR) && typentry->lt_opr == InvalidOid)
342+
if ((flags & TYPECACHE_LT_OPR) &&
343+
!(typentry->flags & TCFLAGS_CHECKED_LT_OPR))
321344
{
322345
Oid lt_opr = InvalidOid;
323346

@@ -336,8 +359,10 @@ lookup_type_cache(Oid type_id, int flags)
336359
lt_opr = InvalidOid;
337360

338361
typentry->lt_opr = lt_opr;
362+
typentry->flags |= TCFLAGS_CHECKED_LT_OPR;
339363
}
340-
if ((flags & TYPECACHE_GT_OPR) && typentry->gt_opr == InvalidOid)
364+
if ((flags & TYPECACHE_GT_OPR) &&
365+
!(typentry->flags & TCFLAGS_CHECKED_GT_OPR))
341366
{
342367
Oid gt_opr = InvalidOid;
343368

@@ -356,9 +381,10 @@ lookup_type_cache(Oid type_id, int flags)
356381
gt_opr = InvalidOid;
357382

358383
typentry->gt_opr = gt_opr;
384+
typentry->flags |= TCFLAGS_CHECKED_GT_OPR;
359385
}
360386
if ((flags & (TYPECACHE_CMP_PROC | TYPECACHE_CMP_PROC_FINFO)) &&
361-
typentry->cmp_proc == InvalidOid)
387+
!(typentry->flags & TCFLAGS_CHECKED_CMP_PROC))
362388
{
363389
Oid cmp_proc = InvalidOid;
364390

@@ -376,10 +402,15 @@ lookup_type_cache(Oid type_id, int flags)
376402
!record_fields_have_compare(typentry))
377403
cmp_proc = InvalidOid;
378404

405+
/* Force update of cmp_proc_finfo only if we're changing state */
406+
if (typentry->cmp_proc != cmp_proc)
407+
typentry->cmp_proc_finfo.fn_oid = InvalidOid;
408+
379409
typentry->cmp_proc = cmp_proc;
410+
typentry->flags |= TCFLAGS_CHECKED_CMP_PROC;
380411
}
381412
if ((flags & (TYPECACHE_HASH_PROC | TYPECACHE_HASH_PROC_FINFO)) &&
382-
typentry->hash_proc == InvalidOid)
413+
!(typentry->flags & TCFLAGS_CHECKED_HASH_PROC))
383414
{
384415
Oid hash_proc = InvalidOid;
385416

@@ -407,7 +438,12 @@ lookup_type_cache(Oid type_id, int flags)
407438
!array_element_has_hashing(typentry))
408439
hash_proc = InvalidOid;
409440

441+
/* Force update of hash_proc_finfo only if we're changing state */
442+
if (typentry->hash_proc != hash_proc)
443+
typentry->hash_proc_finfo.fn_oid = InvalidOid;
444+
410445
typentry->hash_proc = hash_proc;
446+
typentry->flags |= TCFLAGS_CHECKED_HASH_PROC;
411447
}
412448

413449
/*
@@ -416,6 +452,11 @@ lookup_type_cache(Oid type_id, int flags)
416452
* Note: we tell fmgr the finfo structures live in CacheMemoryContext,
417453
* which is not quite right (they're really in the hash table's private
418454
* memory context) but this will do for our purposes.
455+
*
456+
* Note: the code above avoids invalidating the finfo structs unless the
457+
* referenced operator/function OID actually changes. This is to prevent
458+
* unnecessary leakage of any subsidiary data attached to an finfo, since
459+
* that would cause session-lifespan memory leaks.
419460
*/
420461
if ((flags & TYPECACHE_EQ_OPR_FINFO) &&
421462
typentry->eq_opr_finfo.fn_oid == InvalidOid &&
@@ -928,15 +969,38 @@ TypeCacheRelCallback(Datum arg, Oid relid)
928969
typentry->tupDesc = NULL;
929970
}
930971

931-
/* Reset equality/comparison/hashing information */
932-
typentry->eq_opr = InvalidOid;
933-
typentry->lt_opr = InvalidOid;
934-
typentry->gt_opr = InvalidOid;
935-
typentry->cmp_proc = InvalidOid;
936-
typentry->hash_proc = InvalidOid;
937-
typentry->eq_opr_finfo.fn_oid = InvalidOid;
938-
typentry->cmp_proc_finfo.fn_oid = InvalidOid;
939-
typentry->hash_proc_finfo.fn_oid = InvalidOid;
972+
/* Reset equality/comparison/hashing validity information */
973+
typentry->flags = 0;
974+
}
975+
}
976+
977+
/*
978+
* TypeCacheOpcCallback
979+
* Syscache inval callback function
980+
*
981+
* This is called when a syscache invalidation event occurs for any pg_opclass
982+
* row. In principle we could probably just invalidate data dependent on the
983+
* particular opclass, but since updates on pg_opclass are rare in production
984+
* it doesn't seem worth a lot of complication: we just mark all cached data
985+
* invalid.
986+
*
987+
* Note that we don't bother watching for updates on pg_amop or pg_amproc.
988+
* This should be safe because ALTER OPERATOR FAMILY ADD/DROP OPERATOR/FUNCTION
989+
* is not allowed to be used to add/drop the primary operators and functions
990+
* of an opclass, only cross-type members of a family; and the latter sorts
991+
* of members are not going to get cached here.
992+
*/
993+
static void
994+
TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue)
995+
{
996+
HASH_SEQ_STATUS status;
997+
TypeCacheEntry *typentry;
998+
999+
/* TypeCacheHash must exist, else this callback wouldn't be registered */
1000+
hash_seq_init(&status, TypeCacheHash);
1001+
while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
1002+
{
1003+
/* Reset equality/comparison/hashing validity information */
9401004
typentry->flags = 0;
9411005
}
9421006
}

0 commit comments

Comments
 (0)