From 83e876683a6fb96d3239207224a746e61f0e0c45 Mon Sep 17 00:00:00 2001 From: David Geier Date: Thu, 15 Oct 2026 21:32:14 +0200 Subject: [PATCH v4 5/7] Allocate partition cache data lazily Most relations never cache partition keys, descriptors, or partition quals. Collect that state in RelationPartitionInfo and allocate it only when one of the partition cache APIs needs it. sizeof(RelationData): 296 bytes --- src/backend/partitioning/partdesc.c | 41 +++++---- src/backend/utils/cache/partcache.c | 43 ++++++--- src/backend/utils/cache/relcache.c | 132 ++++++++++++++++------------ src/include/utils/rel_internal.h | 70 +++++++++------ 4 files changed, 173 insertions(+), 113 deletions(-) diff --git a/src/backend/partitioning/partdesc.c b/src/backend/partitioning/partdesc.c index 4399fe9a3a0..951e708c5d1 100644 --- a/src/backend/partitioning/partdesc.c +++ b/src/backend/partitioning/partdesc.c @@ -70,6 +70,8 @@ static PartitionDesc RelationBuildPartitionDesc(Relation rel, PartitionDesc RelationGetPartitionDesc(Relation rel, bool omit_detached) { + RelationPartitionInfo *partinfo = rel->rd_partinfo; + Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); /* @@ -80,10 +82,10 @@ RelationGetPartitionDesc(Relation rel, bool omit_detached) * If there is no active snapshot, detached partitions aren't omitted * either, so we can use the cached descriptor too in that case. */ - if (likely(rel->rd_partdesc && - (!rel->rd_partdesc->detached_exist || !omit_detached || + if (likely(partinfo && partinfo->partdesc && + (!partinfo->partdesc->detached_exist || !omit_detached || !ActiveSnapshotSet()))) - return rel->rd_partdesc; + return partinfo->partdesc; /* * If we're asked to omit detached partitions, we may be able to use a @@ -94,16 +96,17 @@ RelationGetPartitionDesc(Relation rel, bool omit_detached) * scratch. */ if (omit_detached && - rel->rd_partdesc_nodetached && + partinfo && + partinfo->partdesc_nodetached && ActiveSnapshotSet()) { Snapshot activesnap; - Assert(TransactionIdIsValid(rel->rd_partdesc_nodetached_xmin)); + Assert(TransactionIdIsValid(partinfo->partdesc_nodetached_xmin)); activesnap = GetActiveSnapshot(); - if (!XidInMVCCSnapshot(rel->rd_partdesc_nodetached_xmin, activesnap)) - return rel->rd_partdesc_nodetached; + if (!XidInMVCCSnapshot(partinfo->partdesc_nodetached_xmin, activesnap)) + return partinfo->partdesc_nodetached; } return RelationBuildPartitionDesc(rel, omit_detached); @@ -147,6 +150,7 @@ RelationBuildPartitionDesc(Relation rel, bool omit_detached) nparts; bool retried = false; PartitionKey partkey = RelationGetPartitionKey(rel); + RelationPartitionInfo *partinfo; MemoryContext new_pdcxt; MemoryContext oldcxt; int *mapping; @@ -385,12 +389,17 @@ retry: * the regular partdesc in rd_pdcxt, and the partdesc-excluding- * detached-partitions in rd_pddcxt.) */ + if (rel->rd_partinfo == NULL) + rel->rd_partinfo = MemoryContextAllocZero(CacheMemoryContext, + sizeof(RelationPartitionInfo)); + partinfo = rel->rd_partinfo; + if (is_omit) { - if (rel->rd_pddcxt != NULL) - MemoryContextSetParent(rel->rd_pddcxt, new_pdcxt); - rel->rd_pddcxt = new_pdcxt; - rel->rd_partdesc_nodetached = partdesc; + if (partinfo->pddcxt != NULL) + MemoryContextSetParent(partinfo->pddcxt, new_pdcxt); + partinfo->pddcxt = new_pdcxt; + partinfo->partdesc_nodetached = partdesc; /* * For partdescs built excluding detached partitions, which we save @@ -401,14 +410,14 @@ retry: * with. */ Assert(TransactionIdIsValid(detached_xmin)); - rel->rd_partdesc_nodetached_xmin = detached_xmin; + partinfo->partdesc_nodetached_xmin = detached_xmin; } else { - if (rel->rd_pdcxt != NULL) - MemoryContextSetParent(rel->rd_pdcxt, new_pdcxt); - rel->rd_pdcxt = new_pdcxt; - rel->rd_partdesc = partdesc; + if (partinfo->pdcxt != NULL) + MemoryContextSetParent(partinfo->pdcxt, new_pdcxt); + partinfo->pdcxt = new_pdcxt; + partinfo->partdesc = partdesc; } return partdesc; diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c index a0982674884..e7c563eaca3 100644 --- a/src/backend/utils/cache/partcache.c +++ b/src/backend/utils/cache/partcache.c @@ -53,10 +53,10 @@ RelationGetPartitionKey(Relation rel) if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) return NULL; - if (unlikely(rel->rd_partkey == NULL)) + if (unlikely(rel->rd_partinfo == NULL || rel->rd_partinfo->partkey == NULL)) RelationBuildPartitionKey(rel); - return rel->rd_partkey; + return rel->rd_partinfo->partkey; } /* @@ -90,6 +90,7 @@ RelationBuildPartitionKey(Relation relation) MemoryContext partkeycxt, oldcxt; int16 procnum; + RelationPartitionInfo *partinfo; tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(RelationGetRelid(relation))); @@ -255,17 +256,23 @@ RelationBuildPartitionKey(Relation relation) ReleaseSysCache(tuple); + partinfo = relation->rd_partinfo; + /* Assert that we're not leaking any old data during assignments below */ - Assert(relation->rd_partkeycxt == NULL); - Assert(relation->rd_partkey == NULL); + Assert(partinfo == NULL || partinfo->partkeycxt == NULL); + Assert(partinfo == NULL || partinfo->partkey == NULL); /* * Success --- reparent our context and make the relcache point to the * newly constructed key */ + if (partinfo == NULL) + relation->rd_partinfo = partinfo = + MemoryContextAllocZero(CacheMemoryContext, + sizeof(RelationPartitionInfo)); MemoryContextSetParent(partkeycxt, CacheMemoryContext); - relation->rd_partkeycxt = partkeycxt; - relation->rd_partkey = key; + partinfo->partkeycxt = partkeycxt; + partinfo->partkey = key; } /* @@ -344,13 +351,14 @@ generate_partition_qual(Relation rel) *result = NIL; Oid parentrelid; Relation parent; + RelationPartitionInfo *partinfo; /* Guard against stack overflow due to overly deep partition tree */ check_stack_depth(); /* If we already cached the result, just return a copy */ - if (rel->rd_partcheckvalid) - return copyObject(rel->rd_partcheck); + if (rel->rd_partinfo && rel->rd_partinfo->partcheckvalid) + return copyObject(rel->rd_partinfo->partcheck); /* * Grab at least an AccessShareLock on the parent table. Must do this @@ -397,9 +405,16 @@ generate_partition_qual(Relation rel) */ result = map_partition_varattnos(result, 1, rel, parent); + partinfo = rel->rd_partinfo; + /* Assert that we're not leaking any old data during assignments below */ - Assert(rel->rd_partcheckcxt == NULL); - Assert(rel->rd_partcheck == NIL); + Assert(partinfo == NULL || partinfo->partcheckcxt == NULL); + Assert(partinfo == NULL || partinfo->partcheck == NIL); + + if (partinfo == NULL) + rel->rd_partinfo = partinfo = + MemoryContextAllocZero(CacheMemoryContext, + sizeof(RelationPartitionInfo)); /* * Save a copy in the relcache. The order of these operations is fairly @@ -434,12 +449,12 @@ generate_partition_qual(Relation rel) /* finally, link the allocations and memctx into the right places */ MemoryContextSetParent(partctx, CacheMemoryContext); - rel->rd_partcheckcxt = partctx; - rel->rd_partcheck = partcheck; + partinfo->partcheckcxt = partctx; + partinfo->partcheck = partcheck; } else - rel->rd_partcheck = NIL; - rel->rd_partcheckvalid = true; + partinfo->partcheck = NIL; + partinfo->partcheckvalid = true; /* Keep the parent locked until commit */ relation_close(parent, NoLock); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index fb03140df99..8af0b218d1d 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -93,7 +93,7 @@ #include "utils/snapmgr.h" #include "utils/syscache.h" -#define RELCACHE_INIT_FILEMAGIC 0x573267 /* version ID value */ +#define RELCACHE_INIT_FILEMAGIC 0x573268 /* version ID value */ /* * Whether to bother checking if relation cache memory needs to be freed @@ -1222,16 +1222,7 @@ retry: relation->rd_toastchunkidtype = InvalidOid; /* partitioning data is not loaded till asked for */ - relation->rd_partkey = NULL; - relation->rd_partkeycxt = NULL; - relation->rd_partdesc = NULL; - relation->rd_partdesc_nodetached = NULL; - relation->rd_partdesc_nodetached_xmin = InvalidTransactionId; - relation->rd_pdcxt = NULL; - relation->rd_pddcxt = NULL; - relation->rd_partcheck = NIL; - relation->rd_partcheckvalid = false; - relation->rd_partcheckcxt = NULL; + relation->rd_partinfo = NULL; /* * initialize access method information @@ -2243,15 +2234,16 @@ RelationCloseCleanup(Relation relation) * stale partition descriptors it has. This is unlikely, so check to see * if there are child contexts before expending a call to mcxt.c. */ - if (RelationHasReferenceCountZero(relation) && !RelationIsIndex(relation)) + if (RelationHasReferenceCountZero(relation) && + !RelationIsIndex(relation) && relation->rd_partinfo != NULL) { - if (relation->rd_pdcxt != NULL && - relation->rd_pdcxt->firstchild != NULL) - MemoryContextDeleteChildren(relation->rd_pdcxt); + RelationPartitionInfo *partinfo = relation->rd_partinfo; + + if (partinfo->pdcxt != NULL && partinfo->pdcxt->firstchild != NULL) + MemoryContextDeleteChildren(partinfo->pdcxt); - if (relation->rd_pddcxt != NULL && - relation->rd_pddcxt->firstchild != NULL) - MemoryContextDeleteChildren(relation->rd_pddcxt); + if (partinfo->pddcxt != NULL && partinfo->pddcxt->firstchild != NULL) + MemoryContextDeleteChildren(partinfo->pddcxt); } #ifdef RELCACHE_FORCE_RELEASE @@ -2489,8 +2481,20 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) pfree(relation->rd_options); if (relation->rd_amcache) pfree(relation->rd_amcache); - if (relation->rd_partcheckcxt) - MemoryContextDelete(relation->rd_partcheckcxt); + if (relation->rd_partinfo) + { + RelationPartitionInfo *partinfo = relation->rd_partinfo; + + if (partinfo->partkeycxt) + MemoryContextDelete(partinfo->partkeycxt); + if (partinfo->pdcxt) + MemoryContextDelete(partinfo->pdcxt); + if (partinfo->pddcxt) + MemoryContextDelete(partinfo->pddcxt); + if (partinfo->partcheckcxt) + MemoryContextDelete(partinfo->partcheckcxt); + pfree(partinfo); + } if (!is_index) { list_free(relation->rd_indexlist); @@ -2510,12 +2514,6 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) MemoryContextDelete(relation->rd_rulescxt); if (relation->rd_rsdesc) MemoryContextDelete(relation->rd_rsdesc->rscxt); - if (relation->rd_partkeycxt) - MemoryContextDelete(relation->rd_partkeycxt); - if (relation->rd_pdcxt) - MemoryContextDelete(relation->rd_pdcxt); - if (relation->rd_pddcxt) - MemoryContextDelete(relation->rd_pddcxt); } else { @@ -2721,9 +2719,23 @@ RelationRebuildRelation(Relation relation) keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules); keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc); /* partkey is immutable once set up, so we can always keep it */ - keep_partkey = (relation->rd_partkey != NULL); + keep_partkey = (relation->rd_partinfo != NULL && + relation->rd_partinfo->partkey != NULL); } + /* + * Ensure that the destination has storage for partition metadata + * before starting the descriptor swap. Nothing in the swap region + * may fail, because relation is transiently inconsistent there. + */ + if (!RelationIsIndex(relation) && newrel->rd_partinfo == NULL && + (keep_partkey || + (relation->rd_partinfo != NULL && + (relation->rd_partinfo->pdcxt != NULL || + relation->rd_partinfo->pddcxt != NULL)))) + newrel->rd_partinfo = MemoryContextAllocZero(CacheMemoryContext, + sizeof(RelationPartitionInfo)); + /* * Perform swapping of the relcache entry contents. Within this * process the old entry is momentarily invalid, so there *must* be no @@ -2781,27 +2793,39 @@ RelationRebuildRelation(Relation relation) /* pgstat_info / enabled must be preserved */ SWAPFIELD(struct PgStat_RelationStatus *, pgstat_info); SWAPFIELD(bool, pgstat_enabled); - /* preserve old partition key if we have one */ + /* + * preserve old partition key if we have one; rd_partinfo as a whole + * has already been swapped above along with the rest of the struct, + * so at this point "relation" holds the freshly-built (usually NULL) + * rd_partinfo and "newrel" holds the old one. + */ if (keep_partkey) { - SWAPFIELD(PartitionKey, rd_partkey); - SWAPFIELD(MemoryContext, rd_partkeycxt); + Assert(newrel->rd_partinfo != NULL); + Assert(relation->rd_partinfo != NULL); + + SWAPFIELD(PartitionKey, rd_partinfo->partkey); + SWAPFIELD(MemoryContext, rd_partinfo->partkeycxt); } - if (newrel->rd_pdcxt != NULL || newrel->rd_pddcxt != NULL) + if (newrel->rd_partinfo != NULL && + (newrel->rd_partinfo->pdcxt != NULL || newrel->rd_partinfo->pddcxt != NULL)) { + RelationPartitionInfo *old_partinfo = newrel->rd_partinfo; + RelationPartitionInfo *partinfo = relation->rd_partinfo; + /* * We are rebuilding a partitioned relation with a non-zero * reference count, so we must keep the old partition descriptor * around, in case there's a PartitionDirectory with a pointer to - * it. This means we can't free the old rd_pdcxt yet. (This is + * it. This means we can't free the old pdcxt yet. (This is * necessary because RelationGetPartitionDesc hands out direct * pointers to the relcache's data structure, unlike our usual * practice which is to hand out copies. We'd have the same - * problem with rd_partkey, except that we always preserve that + * problem with partkey, except that we always preserve that * once created.) * * To ensure that it's not leaked completely, re-attach it to the - * new reldesc, or make it a child of the new reldesc's rd_pdcxt + * new reldesc, or make it a child of the new reldesc's pdcxt * in the unlikely event that there is one already. (Compare hack * in RelationBuildPartitionDesc.) RelationClose will clean up * any such contexts once the reference count reaches zero. @@ -2814,23 +2838,24 @@ RelationRebuildRelation(Relation relation) * "old" partition descriptor is actually the one hanging off of * newrel. */ - relation->rd_partdesc = NULL; /* ensure rd_partdesc is invalid */ - relation->rd_partdesc_nodetached = NULL; - relation->rd_partdesc_nodetached_xmin = InvalidTransactionId; - if (relation->rd_pdcxt != NULL) /* probably never happens */ - MemoryContextSetParent(newrel->rd_pdcxt, relation->rd_pdcxt); + Assert(partinfo != NULL); + partinfo->partdesc = NULL; /* ensure partdesc is invalid */ + partinfo->partdesc_nodetached = NULL; + partinfo->partdesc_nodetached_xmin = InvalidTransactionId; + if (partinfo->pdcxt != NULL) /* probably never happens */ + MemoryContextSetParent(old_partinfo->pdcxt, partinfo->pdcxt); else - relation->rd_pdcxt = newrel->rd_pdcxt; - if (relation->rd_pddcxt != NULL) - MemoryContextSetParent(newrel->rd_pddcxt, relation->rd_pddcxt); + partinfo->pdcxt = old_partinfo->pdcxt; + if (partinfo->pddcxt != NULL) + MemoryContextSetParent(old_partinfo->pddcxt, partinfo->pddcxt); else - relation->rd_pddcxt = newrel->rd_pddcxt; + partinfo->pddcxt = old_partinfo->pddcxt; /* drop newrel's pointers so we don't destroy it below */ - newrel->rd_partdesc = NULL; - newrel->rd_partdesc_nodetached = NULL; - newrel->rd_partdesc_nodetached_xmin = InvalidTransactionId; - newrel->rd_pdcxt = NULL; - newrel->rd_pddcxt = NULL; + old_partinfo->partdesc = NULL; + old_partinfo->partdesc_nodetached = NULL; + old_partinfo->partdesc_nodetached_xmin = InvalidTransactionId; + old_partinfo->pdcxt = NULL; + old_partinfo->pddcxt = NULL; } #undef SWAPFIELD @@ -6559,13 +6584,6 @@ load_relcache_init_file(bool shared) rel->rd_rulescxt = NULL; rel->trigdesc = NULL; rel->rd_rsdesc = NULL; - rel->rd_partkey = NULL; - rel->rd_partkeycxt = NULL; - rel->rd_partdesc = NULL; - rel->rd_partdesc_nodetached = NULL; - rel->rd_partdesc_nodetached_xmin = InvalidTransactionId; - rel->rd_pdcxt = NULL; - rel->rd_pddcxt = NULL; rel->rd_keyattr = NULL; rel->rd_pkattr = NULL; rel->rd_idattr = NULL; @@ -6598,9 +6616,7 @@ load_relcache_init_file(bool shared) * Reset transient-state fields in the relcache entry */ rel->rd_smgr = NULL; - rel->rd_partcheck = NIL; - rel->rd_partcheckvalid = false; - rel->rd_partcheckcxt = NULL; + rel->rd_partinfo = NULL; if (rel->rd_isnailed) rel->rd_refcnt = 1; else diff --git a/src/include/utils/rel_internal.h b/src/include/utils/rel_internal.h index f2ec9196f3c..54b109ec1f7 100644 --- a/src/include/utils/rel_internal.h +++ b/src/include/utils/rel_internal.h @@ -34,6 +34,45 @@ typedef struct LockInfoData typedef LockInfoData *LockInfo; +/* + * RelationPartitionInfo + * Partition-related data cached for a relation, allocated lazily on + * first use. Only ever populated for partitioned tables (partkey and + * the partdesc fields) and/or partitions (partcheck fields); most + * relations need none of this, hence it's a separate lazily-allocated + * struct rather than a set of fields embedded directly in + * RelationData. + */ +typedef struct RelationPartitionInfo +{ + /* data managed by RelationGetPartitionKey: */ + PartitionKey partkey; /* partition key, or NULL */ + MemoryContext partkeycxt; /* private context for partkey, if any */ + + /* data managed by RelationGetPartitionDesc: */ + PartitionDesc partdesc; /* partition descriptor, or NULL */ + MemoryContext pdcxt; /* private context for partdesc, if any */ + + /* Same as above, for partdescs that omit detached partitions */ + PartitionDesc partdesc_nodetached; /* partdesc w/o detached parts */ + MemoryContext pddcxt; /* for partdesc_nodetached, if any */ + + /* data managed by RelationGetPartitionQual: */ + List *partcheck; /* partition CHECK quals */ + MemoryContext partcheckcxt; /* private cxt for partcheck, if any */ + + /* + * pg_inherits.xmin of the partition that was excluded in + * partdesc_nodetached. This informs a future user of that partdesc: if + * this value is not in progress for the active snapshot, then the + * partdesc can be used, otherwise they have to build a new one. (This + * matches what find_inheritance_children_extended would do). + */ + TransactionId partdesc_nodetached_xmin; + + bool partcheckvalid; /* true if partcheck has been computed */ +} RelationPartitionInfo; + /* * Here are the contents of a relation cache entry. */ @@ -55,27 +94,6 @@ typedef struct RelationData List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */ bool rd_fkeyvalid; /* true if rd_fkeylist has been computed */ - /* data managed by RelationGetPartitionKey: */ - PartitionKey rd_partkey; /* partition key, or NULL */ - MemoryContext rd_partkeycxt; /* private context for rd_partkey, if any */ - - /* data managed by RelationGetPartitionDesc: */ - PartitionDesc rd_partdesc; /* partition descriptor, or NULL */ - MemoryContext rd_pdcxt; /* private context for rd_partdesc, if any */ - - /* Same as above, for partdescs that omit detached partitions */ - PartitionDesc rd_partdesc_nodetached; /* partdesc w/o detached parts */ - MemoryContext rd_pddcxt; /* for rd_partdesc_nodetached, if any */ - - /* - * pg_inherits.xmin of the partition that was excluded in - * rd_partdesc_nodetached. This informs a future user of that partdesc: - * if this value is not in progress for the active snapshot, then the - * partdesc can be used, otherwise they have to build a new one. (This - * matches what find_inheritance_children_extended would do). - */ - TransactionId rd_partdesc_nodetached_xmin; - /* data managed by RelationGetIndexList: */ List *rd_indexlist; /* list of OIDs of indexes on relation */ Oid rd_pkindex; /* OID of (deferrable?) primary key, if any */ @@ -224,10 +242,12 @@ typedef struct RelationData Oid rd_id; /* relation's object id */ LockInfoData rd_lockInfo; /* lock mgr's info for locking relation */ - /* data managed by RelationGetPartitionQual: */ - List *rd_partcheck; /* partition CHECK quals */ - bool rd_partcheckvalid; /* true if list has been computed */ - MemoryContext rd_partcheckcxt; /* private cxt for rd_partcheck, if any */ + /* + * Data managed by RelationGetPartitionKey(), RelationGetPartitionDesc(), + * and RelationGetPartitionQual(); NULL if none of these has been + * called for this relation. See RelationPartitionInfo above. + */ + RelationPartitionInfo *rd_partinfo; /* * rd_options is set whenever rd_rel is loaded into the relcache entry. -- 2.55.0