Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit c14d4ac

Browse files
committed
Avoid looping over all type cache entries in TypeCacheRelCallback()
Currently when a single relcache entry gets invalidated, TypeCacheRelCallback() has to loop over all type cache entries to find appropriate typentry to invalidate. Unfortunately, using the syscache here is impossible, because this callback could be called outside a transaction and this makes impossible catalog lookups. This is why present commit introduces RelIdToTypeIdCacheHash to map relation OID to its composite type OID. We are keeping RelIdToTypeIdCacheHash entry while corresponding type cache entry have something to clean. Therefore, RelIdToTypeIdCacheHash shouldn't get bloat in the case of temporary tables flood. Discussion: https://postgr.es/m/5812a6e5-68ae-4d84-9d85-b443176966a1%40sigaev.ru Author: Teodor Sigaev Reviewed-by: Aleksander Alekseev, Tom Lane, Michael Paquier, Roman Zharkov Reviewed-by: Andrei Lepikhov, Pavel Borisov
1 parent 3890d90 commit c14d4ac

File tree

2 files changed

+232
-44
lines changed

2 files changed

+232
-44
lines changed

src/backend/utils/cache/typcache.c

Lines changed: 231 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,20 @@
7777
/* The main type cache hashtable searched by lookup_type_cache */
7878
static HTAB *TypeCacheHash = NULL;
7979

80+
/*
81+
* The mapping of relation's OID to the corresponding composite type OID.
82+
* We're keeping the map entry when the corresponding typentry has something
83+
* to clear i.e it has either TCFLAGS_HAVE_PG_TYPE_DATA, or
84+
* TCFLAGS_OPERATOR_FLAGS, or tupdesc.
85+
*/
86+
static HTAB *RelIdToTypeIdCacheHash = NULL;
87+
88+
typedef struct RelIdToTypeIdCacheEntry
89+
{
90+
Oid relid; /* OID of the relation */
91+
Oid composite_typid; /* OID of the relation's composite type */
92+
} RelIdToTypeIdCacheEntry;
93+
8094
/* List of type cache entries for domain types */
8195
static TypeCacheEntry *firstDomainTypeEntry = NULL;
8296

@@ -329,6 +343,8 @@ static void shared_record_typmod_registry_detach(dsm_segment *segment,
329343
static TupleDesc find_or_make_matching_shared_tupledesc(TupleDesc tupdesc);
330344
static dsa_pointer share_tupledesc(dsa_area *area, TupleDesc tupdesc,
331345
uint32 typmod);
346+
static void insert_rel_type_cache_if_needed(TypeCacheEntry *typentry);
347+
static void delete_rel_type_cache_if_needed(TypeCacheEntry *typentry);
332348

333349

334350
/*
@@ -376,6 +392,13 @@ lookup_type_cache(Oid type_id, int flags)
376392
TypeCacheHash = hash_create("Type information cache", 64,
377393
&ctl, HASH_ELEM | HASH_FUNCTION);
378394

395+
Assert(RelIdToTypeIdCacheHash == NULL);
396+
397+
ctl.keysize = sizeof(Oid);
398+
ctl.entrysize = sizeof(RelIdToTypeIdCacheEntry);
399+
RelIdToTypeIdCacheHash = hash_create("Map from relid to OID of cached composite type", 64,
400+
&ctl, HASH_ELEM | HASH_BLOBS);
401+
379402
/* Also set up callbacks for SI invalidations */
380403
CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0);
381404
CacheRegisterSyscacheCallback(TYPEOID, TypeCacheTypCallback, (Datum) 0);
@@ -387,6 +410,8 @@ lookup_type_cache(Oid type_id, int flags)
387410
CreateCacheMemoryContext();
388411
}
389412

413+
Assert(TypeCacheHash != NULL && RelIdToTypeIdCacheHash != NULL);
414+
390415
/* Try to look up an existing entry */
391416
typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
392417
&type_id,
@@ -438,6 +463,7 @@ lookup_type_cache(Oid type_id, int flags)
438463
typentry->typelem = typtup->typelem;
439464
typentry->typcollation = typtup->typcollation;
440465
typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA;
466+
insert_rel_type_cache_if_needed(typentry);
441467

442468
/* If it's a domain, immediately thread it into the domain cache list */
443469
if (typentry->typtype == TYPTYPE_DOMAIN)
@@ -483,6 +509,7 @@ lookup_type_cache(Oid type_id, int flags)
483509
typentry->typelem = typtup->typelem;
484510
typentry->typcollation = typtup->typcollation;
485511
typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA;
512+
insert_rel_type_cache_if_needed(typentry);
486513

487514
ReleaseSysCache(tp);
488515
}
@@ -2281,6 +2308,53 @@ SharedRecordTypmodRegistryAttach(SharedRecordTypmodRegistry *registry)
22812308
CurrentSession->shared_typmod_table = typmod_table;
22822309
}
22832310

2311+
/*
2312+
* InvalidateCompositeTypeCacheEntry
2313+
* Invalidate particular TypeCacheEntry on Relcache inval callback
2314+
*
2315+
* Delete the cached tuple descriptor (if any) for the given composite
2316+
* type, and reset whatever info we have cached about the composite type's
2317+
* comparability.
2318+
*/
2319+
static void
2320+
InvalidateCompositeTypeCacheEntry(TypeCacheEntry *typentry)
2321+
{
2322+
bool hadTupDescOrOpclass;
2323+
2324+
Assert(typentry->typtype == TYPTYPE_COMPOSITE &&
2325+
OidIsValid(typentry->typrelid));
2326+
2327+
hadTupDescOrOpclass = (typentry->tupDesc != NULL) ||
2328+
(typentry->flags & TCFLAGS_OPERATOR_FLAGS);
2329+
2330+
/* Delete tupdesc if we have it */
2331+
if (typentry->tupDesc != NULL)
2332+
{
2333+
/*
2334+
* Release our refcount and free the tupdesc if none remain. We can't
2335+
* use DecrTupleDescRefCount here because this reference is not logged
2336+
* by the current resource owner.
2337+
*/
2338+
Assert(typentry->tupDesc->tdrefcount > 0);
2339+
if (--typentry->tupDesc->tdrefcount == 0)
2340+
FreeTupleDesc(typentry->tupDesc);
2341+
typentry->tupDesc = NULL;
2342+
2343+
/*
2344+
* Also clear tupDesc_identifier, so that anyone watching it will
2345+
* realize that the tupdesc has changed.
2346+
*/
2347+
typentry->tupDesc_identifier = 0;
2348+
}
2349+
2350+
/* Reset equality/comparison/hashing validity information */
2351+
typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
2352+
2353+
/* Call check_delete_rel_type_cache() if we actually cleared something */
2354+
if (hadTupDescOrOpclass)
2355+
delete_rel_type_cache_if_needed(typentry);
2356+
}
2357+
22842358
/*
22852359
* TypeCacheRelCallback
22862360
* Relcache inval callback function
@@ -2290,63 +2364,55 @@ SharedRecordTypmodRegistryAttach(SharedRecordTypmodRegistry *registry)
22902364
* whatever info we have cached about the composite type's comparability.
22912365
*
22922366
* This is called when a relcache invalidation event occurs for the given
2293-
* relid. We must scan the whole typcache hash since we don't know the
2294-
* type OID corresponding to the relid. We could do a direct search if this
2295-
* were a syscache-flush callback on pg_type, but then we would need all
2296-
* ALTER-TABLE-like commands that could modify a rowtype to issue syscache
2297-
* invals against the rel's pg_type OID. The extra SI signaling could very
2298-
* well cost more than we'd save, since in most usages there are not very
2299-
* many entries in a backend's typcache. The risk of bugs-of-omission seems
2300-
* high, too.
2301-
*
2302-
* Another possibility, with only localized impact, is to maintain a second
2303-
* hashtable that indexes composite-type typcache entries by their typrelid.
2304-
* But it's still not clear it's worth the trouble.
2367+
* relid. We can't use syscache to find a type corresponding to the given
2368+
* relation because the code can be called outside of transaction. Thus we use
2369+
* the RelIdToTypeIdCacheHash map to locate appropriate typcache entry.
23052370
*/
23062371
static void
23072372
TypeCacheRelCallback(Datum arg, Oid relid)
23082373
{
2309-
HASH_SEQ_STATUS status;
23102374
TypeCacheEntry *typentry;
23112375

2312-
/* TypeCacheHash must exist, else this callback wouldn't be registered */
2313-
hash_seq_init(&status, TypeCacheHash);
2314-
while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
2376+
/*
2377+
* RelIdToTypeIdCacheHash and TypeCacheHash should exist, otherwise this
2378+
* callback wouldn't be registered
2379+
*/
2380+
if (OidIsValid(relid))
23152381
{
2316-
if (typentry->typtype == TYPTYPE_COMPOSITE)
2382+
RelIdToTypeIdCacheEntry *relentry;
2383+
2384+
/*
2385+
* Find an RelIdToTypeIdCacheHash entry, which should exist as soon as
2386+
* corresponding typcache entry has something to clean.
2387+
*/
2388+
relentry = (RelIdToTypeIdCacheEntry *) hash_search(RelIdToTypeIdCacheHash,
2389+
&relid,
2390+
HASH_FIND, NULL);
2391+
2392+
if (relentry != NULL)
23172393
{
2318-
/* Skip if no match, unless we're zapping all composite types */
2319-
if (relid != typentry->typrelid && relid != InvalidOid)
2320-
continue;
2394+
typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
2395+
&relentry->composite_typid,
2396+
HASH_FIND, NULL);
23212397

2322-
/* Delete tupdesc if we have it */
2323-
if (typentry->tupDesc != NULL)
2398+
if (typentry != NULL)
23242399
{
2325-
/*
2326-
* Release our refcount, and free the tupdesc if none remain.
2327-
* (Can't use DecrTupleDescRefCount because this reference is
2328-
* not logged in current resource owner.)
2329-
*/
2330-
Assert(typentry->tupDesc->tdrefcount > 0);
2331-
if (--typentry->tupDesc->tdrefcount == 0)
2332-
FreeTupleDesc(typentry->tupDesc);
2333-
typentry->tupDesc = NULL;
2400+
Assert(typentry->typtype == TYPTYPE_COMPOSITE);
2401+
Assert(relid == typentry->typrelid);
23342402

2335-
/*
2336-
* Also clear tupDesc_identifier, so that anything watching
2337-
* that will realize that the tupdesc has possibly changed.
2338-
* (Alternatively, we could specify that to detect possible
2339-
* tupdesc change, one must check for tupDesc != NULL as well
2340-
* as tupDesc_identifier being the same as what was previously
2341-
* seen. That seems error-prone.)
2342-
*/
2343-
typentry->tupDesc_identifier = 0;
2403+
InvalidateCompositeTypeCacheEntry(typentry);
23442404
}
2345-
2346-
/* Reset equality/comparison/hashing validity information */
2347-
typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
23482405
}
2349-
else if (typentry->typtype == TYPTYPE_DOMAIN)
2406+
2407+
/*
2408+
* Visit all the domain types sequentially. Typically this shouldn't
2409+
* affect performance since domain types are less tended to bloat.
2410+
* Domain types are created manually, unlike composite types which are
2411+
* automatically created for every temporary table.
2412+
*/
2413+
for (typentry = firstDomainTypeEntry;
2414+
typentry != NULL;
2415+
typentry = typentry->nextDomain)
23502416
{
23512417
/*
23522418
* If it's domain over composite, reset flags. (We don't bother
@@ -2358,6 +2424,36 @@ TypeCacheRelCallback(Datum arg, Oid relid)
23582424
typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
23592425
}
23602426
}
2427+
else
2428+
{
2429+
HASH_SEQ_STATUS status;
2430+
2431+
/*
2432+
* Relid is invalid. By convention we need to reset all composite
2433+
* types in cache. Also, we should reset flags for domain types, and
2434+
* we loop over all entries in hash, so, do it in a single scan.
2435+
*/
2436+
hash_seq_init(&status, TypeCacheHash);
2437+
while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
2438+
{
2439+
if (typentry->typtype == TYPTYPE_COMPOSITE)
2440+
{
2441+
InvalidateCompositeTypeCacheEntry(typentry);
2442+
}
2443+
else if (typentry->typtype == TYPTYPE_DOMAIN)
2444+
{
2445+
/*
2446+
* If it's domain over composite, reset flags. (We don't
2447+
* bother trying to determine whether the specific base type
2448+
* needs a reset.) Note that if we haven't determined whether
2449+
* the base type is composite, we don't need to reset
2450+
* anything.
2451+
*/
2452+
if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE)
2453+
typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
2454+
}
2455+
}
2456+
}
23612457
}
23622458

23632459
/*
@@ -2388,6 +2484,8 @@ TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue)
23882484

23892485
while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
23902486
{
2487+
bool hadPgTypeData = (typentry->flags & TCFLAGS_HAVE_PG_TYPE_DATA);
2488+
23912489
Assert(hashvalue == 0 || typentry->type_id_hash == hashvalue);
23922490

23932491
/*
@@ -2397,6 +2495,13 @@ TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue)
23972495
*/
23982496
typentry->flags &= ~(TCFLAGS_HAVE_PG_TYPE_DATA |
23992497
TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS);
2498+
2499+
/*
2500+
* Call check_delete_rel_type_cache() if we cleaned
2501+
* TCFLAGS_HAVE_PG_TYPE_DATA flag previously.
2502+
*/
2503+
if (hadPgTypeData)
2504+
delete_rel_type_cache_if_needed(typentry);
24002505
}
24012506
}
24022507

@@ -2905,3 +3010,85 @@ shared_record_typmod_registry_detach(dsm_segment *segment, Datum datum)
29053010
}
29063011
CurrentSession->shared_typmod_registry = NULL;
29073012
}
3013+
3014+
/*
3015+
* Insert RelIdToTypeIdCacheHash entry after setting of the
3016+
* TCFLAGS_HAVE_PG_TYPE_DATA flag if needed.
3017+
*/
3018+
static void
3019+
insert_rel_type_cache_if_needed(TypeCacheEntry *typentry)
3020+
{
3021+
Assert(typentry->flags & TCFLAGS_HAVE_PG_TYPE_DATA);
3022+
3023+
/* Immediately quit for non-composite types */
3024+
if (typentry->typtype != TYPTYPE_COMPOSITE)
3025+
return;
3026+
3027+
/* typrelid should be given for composite types */
3028+
Assert(OidIsValid(typentry->typrelid));
3029+
3030+
/*
3031+
* Insert a RelIdToTypeIdCacheHash entry if the typentry doesn't have any
3032+
* information indicating there should be such an entry already.
3033+
*/
3034+
if (!(typentry->flags & TCFLAGS_OPERATOR_FLAGS) &&
3035+
typentry->tupDesc == NULL)
3036+
{
3037+
RelIdToTypeIdCacheEntry *relentry;
3038+
bool found;
3039+
3040+
relentry = (RelIdToTypeIdCacheEntry *) hash_search(RelIdToTypeIdCacheHash,
3041+
&typentry->typrelid,
3042+
HASH_ENTER, &found);
3043+
Assert(!found);
3044+
relentry->relid = typentry->typrelid;
3045+
relentry->composite_typid = typentry->type_id;
3046+
}
3047+
}
3048+
3049+
/*
3050+
* Delete entry RelIdToTypeIdCacheHash if needed after resetting of the
3051+
* TCFLAGS_HAVE_PG_TYPE_DATA flag, or any of TCFLAGS_OPERATOR_FLAGS flags,
3052+
* or tupDesc.
3053+
*/
3054+
static void
3055+
delete_rel_type_cache_if_needed(TypeCacheEntry *typentry)
3056+
{
3057+
/* Immediately quit for non-composite types */
3058+
if (typentry->typtype != TYPTYPE_COMPOSITE)
3059+
return;
3060+
3061+
/* typrelid should be given for composite types */
3062+
Assert(OidIsValid(typentry->typrelid));
3063+
3064+
/*
3065+
* Delete a RelIdToTypeIdCacheHash entry if the typentry doesn't have any
3066+
* information indicating entry should be still there.
3067+
*/
3068+
if (!(typentry->flags & TCFLAGS_HAVE_PG_TYPE_DATA) &&
3069+
!(typentry->flags & TCFLAGS_OPERATOR_FLAGS) &&
3070+
typentry->tupDesc == NULL)
3071+
{
3072+
bool found;
3073+
3074+
(void) hash_search(RelIdToTypeIdCacheHash,
3075+
&typentry->typrelid,
3076+
HASH_REMOVE, &found);
3077+
Assert(found);
3078+
}
3079+
else
3080+
{
3081+
#ifdef USE_ASSERT_CHECKING
3082+
/*
3083+
* In assert-enabled builds otherwise check for RelIdToTypeIdCacheHash
3084+
* entry if it should exist.
3085+
*/
3086+
bool found;
3087+
3088+
(void) hash_search(RelIdToTypeIdCacheHash,
3089+
&typentry->typrelid,
3090+
HASH_FIND, &found);
3091+
Assert(found);
3092+
#endif
3093+
}
3094+
}

src/tools/pgindent/typedefs.list

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,6 +2377,7 @@ RelFileLocator
23772377
RelFileLocatorBackend
23782378
RelFileNumber
23792379
RelIdCacheEnt
2380+
RelIdToTypeIdCacheEntry
23802381
RelInfo
23812382
RelInfoArr
23822383
RelMapFile

0 commit comments

Comments
 (0)