From 5d83b0bf51ff9fc85b32998319e23446fc978840 Mon Sep 17 00:00:00 2001 From: David Geier Date: Thu, 3 Sep 2026 13:00:51 +0200 Subject: [PATCH v1 5/8] Move out partition members --- src/backend/partitioning/partdesc.c | 37 +++++---- src/backend/utils/cache/partcache.c | 49 ++++++----- src/backend/utils/cache/relcache.c | 121 ++++++++++++++-------------- src/include/utils/rel_internal.h | 70 ++++++++++------ 4 files changed, 156 insertions(+), 121 deletions(-) diff --git a/src/backend/partitioning/partdesc.c b/src/backend/partitioning/partdesc.c index 4399fe9a3a0..1217bc26d04 100644 --- a/src/backend/partitioning/partdesc.c +++ b/src/backend/partitioning/partdesc.c @@ -80,10 +80,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(rel->rd_partinfo && rel->rd_partinfo->partdesc && + (!rel->rd_partinfo->partdesc->detached_exist || !omit_detached || !ActiveSnapshotSet()))) - return rel->rd_partdesc; + return rel->rd_partinfo->partdesc; /* * If we're asked to omit detached partitions, we may be able to use a @@ -94,16 +94,17 @@ RelationGetPartitionDesc(Relation rel, bool omit_detached) * scratch. */ if (omit_detached && - rel->rd_partdesc_nodetached && + rel->rd_partinfo && + rel->rd_partinfo->partdesc_nodetached && ActiveSnapshotSet()) { Snapshot activesnap; - Assert(TransactionIdIsValid(rel->rd_partdesc_nodetached_xmin)); + Assert(TransactionIdIsValid(rel->rd_partinfo->partdesc_nodetached_xmin)); activesnap = GetActiveSnapshot(); - if (!XidInMVCCSnapshot(rel->rd_partdesc_nodetached_xmin, activesnap)) - return rel->rd_partdesc_nodetached; + if (!XidInMVCCSnapshot(rel->rd_partinfo->partdesc_nodetached_xmin, activesnap)) + return rel->rd_partinfo->partdesc_nodetached; } return RelationBuildPartitionDesc(rel, omit_detached); @@ -385,12 +386,16 @@ 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)); + 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 (rel->rd_partinfo->pddcxt != NULL) + MemoryContextSetParent(rel->rd_partinfo->pddcxt, new_pdcxt); + rel->rd_partinfo->pddcxt = new_pdcxt; + rel->rd_partinfo->partdesc_nodetached = partdesc; /* * For partdescs built excluding detached partitions, which we save @@ -401,14 +406,14 @@ retry: * with. */ Assert(TransactionIdIsValid(detached_xmin)); - rel->rd_partdesc_nodetached_xmin = detached_xmin; + rel->rd_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 (rel->rd_partinfo->pdcxt != NULL) + MemoryContextSetParent(rel->rd_partinfo->pdcxt, new_pdcxt); + rel->rd_partinfo->pdcxt = new_pdcxt; + rel->rd_partinfo->partdesc = partdesc; } return partdesc; diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c index c86efd65dd7..959ac904bf7 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; } /* @@ -256,16 +256,19 @@ RelationBuildPartitionKey(Relation relation) ReleaseSysCache(tuple); /* Assert that we're not leaking any old data during assignments below */ - Assert(relation->rd_partkeycxt == NULL); - Assert(relation->rd_partkey == NULL); + Assert(relation->rd_partinfo == NULL || relation->rd_partinfo->partkeycxt == NULL); + Assert(relation->rd_partinfo == NULL || relation->rd_partinfo->partkey == NULL); /* * Success --- reparent our context and make the relcache point to the * newly constructed key */ MemoryContextSetParent(partkeycxt, CacheMemoryContext); - relation->rd_partkeycxt = partkeycxt; - relation->rd_partkey = key; + if (relation->rd_partinfo == NULL) + relation->rd_partinfo = MemoryContextAllocZero(CacheMemoryContext, + sizeof(RelationPartitionInfo)); + relation->rd_partinfo->partkeycxt = partkeycxt; + relation->rd_partinfo->partkey = key; } /* @@ -350,13 +353,13 @@ generate_partition_qual(Relation rel) check_stack_depth(); /* - * rd_partcheck/rd_partcheckvalid/rd_partcheckcxt only exist for - * table-like relations; they are overlaid in a union with index-only - * fields (see RelationData in rel_internal.h). generate_partition_qual() - * is also invoked for partitioned indexes (an index partition never has - * a partition bound of its own, so this always ends up computing an - * empty qual list for them), so guard against misinterpreting unrelated - * index data, and don't cache results for indexes. + * rd_partinfo only exists for table-like relations; it is overlaid in a + * union with index-only fields (see RelationData in rel_internal.h). + * generate_partition_qual() is also invoked for partitioned indexes (an + * index partition never has a partition bound of its own, so this always + * ends up computing an empty qual list for them), so guard against + * misinterpreting unrelated index data, and don't cache results for + * indexes. */ if (rel->rd_rel->relkind == RELKIND_INDEX || rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) @@ -365,8 +368,8 @@ generate_partition_qual(Relation rel) is_index = false; /* If we already cached the result, just return a copy */ - if (!is_index && rel->rd_partcheckvalid) - return copyObject(rel->rd_partcheck); + if (!is_index && 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 @@ -421,8 +424,12 @@ generate_partition_qual(Relation rel) } /* Assert that we're not leaking any old data during assignments below */ - Assert(rel->rd_partcheckcxt == NULL); - Assert(rel->rd_partcheck == NIL); + Assert(rel->rd_partinfo == NULL || rel->rd_partinfo->partcheckcxt == NULL); + Assert(rel->rd_partinfo == NULL || rel->rd_partinfo->partcheck == NIL); + + if (rel->rd_partinfo == NULL) + rel->rd_partinfo = MemoryContextAllocZero(CacheMemoryContext, + sizeof(RelationPartitionInfo)); /* * Save a copy in the relcache. The order of these operations is fairly @@ -457,12 +464,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; + rel->rd_partinfo->partcheckcxt = partctx; + rel->rd_partinfo->partcheck = partcheck; } else - rel->rd_partcheck = NIL; - rel->rd_partcheckvalid = true; + rel->rd_partinfo->partcheck = NIL; + rel->rd_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 21d31316432..7bc6e124f6d 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -1206,16 +1206,7 @@ retry: relation->rd_fkeyvalid = false; /* 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 @@ -2246,15 +2237,16 @@ RelationCloseCleanup(Relation relation) */ if (RelationHasReferenceCountZero(relation) && relation->rd_rel->relkind != RELKIND_INDEX && - relation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX) + relation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX && + relation->rd_partinfo != NULL) { - if (relation->rd_pdcxt != NULL && - relation->rd_pdcxt->firstchild != NULL) - MemoryContextDeleteChildren(relation->rd_pdcxt); + if (relation->rd_partinfo->pdcxt != NULL && + relation->rd_partinfo->pdcxt->firstchild != NULL) + MemoryContextDeleteChildren(relation->rd_partinfo->pdcxt); - if (relation->rd_pddcxt != NULL && - relation->rd_pddcxt->firstchild != NULL) - MemoryContextDeleteChildren(relation->rd_pddcxt); + if (relation->rd_partinfo->pddcxt != NULL && + relation->rd_partinfo->pddcxt->firstchild != NULL) + MemoryContextDeleteChildren(relation->rd_partinfo->pddcxt); } #ifdef RELCACHE_FORCE_RELEASE @@ -2456,7 +2448,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) Assert(RelationHasReferenceCountZero(relation)); /* - * rd_index/rd_indexcxt/etc and rd_rules/trigdesc/rd_partkey/etc are + * rd_index/rd_indexcxt/etc and rd_rules/trigdesc/rd_partinfo/etc are * overlaid in a union (see RelationData in rel_internal.h), since a * relation is never both an index and a table-like relation at once. * Capture which set of fields is "live" for this relation before @@ -2524,14 +2516,18 @@ 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); - if (relation->rd_partcheckcxt) - MemoryContextDelete(relation->rd_partcheckcxt); + if (relation->rd_partinfo) + { + if (relation->rd_partinfo->partkeycxt) + MemoryContextDelete(relation->rd_partinfo->partkeycxt); + if (relation->rd_partinfo->pdcxt) + MemoryContextDelete(relation->rd_partinfo->pdcxt); + if (relation->rd_partinfo->pddcxt) + MemoryContextDelete(relation->rd_partinfo->pddcxt); + if (relation->rd_partinfo->partcheckcxt) + MemoryContextDelete(relation->rd_partinfo->partcheckcxt); + pfree(relation->rd_partinfo); + } } else { @@ -2733,7 +2729,7 @@ RelationRebuildRelation(Relation relation) keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att); /* - * rd_rules/rd_rsdesc/rd_partkey only exist for table-like + * rd_rules/rd_rsdesc/rd_partinfo only exist for table-like * relations; they are overlaid in a union with index-only fields * (see RelationData in rel_internal.h). We normally only reach * this generic rebuild path for indexes whose access info hasn't @@ -2752,7 +2748,8 @@ 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); } /* @@ -2811,27 +2808,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); + if (relation->rd_partinfo == NULL) + relation->rd_partinfo = MemoryContextAllocZero(CacheMemoryContext, + sizeof(RelationPartitionInfo)); + relation->rd_partinfo->partkey = newrel->rd_partinfo->partkey; + relation->rd_partinfo->partkeycxt = newrel->rd_partinfo->partkeycxt; + newrel->rd_partinfo->partkey = NULL; + newrel->rd_partinfo->partkeycxt = NULL; } - if (newrel->rd_pdcxt != NULL || newrel->rd_pddcxt != NULL) + if (newrel->rd_partinfo != NULL && + (newrel->rd_partinfo->pdcxt != NULL || newrel->rd_partinfo->pddcxt != NULL)) { /* * 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. @@ -2844,23 +2853,26 @@ 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); + if (relation->rd_partinfo == NULL) + relation->rd_partinfo = MemoryContextAllocZero(CacheMemoryContext, + sizeof(RelationPartitionInfo)); + relation->rd_partinfo->partdesc = NULL; /* ensure partdesc is invalid */ + relation->rd_partinfo->partdesc_nodetached = NULL; + relation->rd_partinfo->partdesc_nodetached_xmin = InvalidTransactionId; + if (relation->rd_partinfo->pdcxt != NULL) /* probably never happens */ + MemoryContextSetParent(newrel->rd_partinfo->pdcxt, relation->rd_partinfo->pdcxt); else - relation->rd_pdcxt = newrel->rd_pdcxt; - if (relation->rd_pddcxt != NULL) - MemoryContextSetParent(newrel->rd_pddcxt, relation->rd_pddcxt); + relation->rd_partinfo->pdcxt = newrel->rd_partinfo->pdcxt; + if (relation->rd_partinfo->pddcxt != NULL) + MemoryContextSetParent(newrel->rd_partinfo->pddcxt, relation->rd_partinfo->pddcxt); else - relation->rd_pddcxt = newrel->rd_pddcxt; + relation->rd_partinfo->pddcxt = newrel->rd_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; + newrel->rd_partinfo->partdesc = NULL; + newrel->rd_partinfo->partdesc_nodetached = NULL; + newrel->rd_partinfo->partdesc_nodetached_xmin = InvalidTransactionId; + newrel->rd_partinfo->pdcxt = NULL; + newrel->rd_partinfo->pddcxt = NULL; } #undef SWAPFIELD @@ -6531,16 +6543,7 @@ 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_partcheck = NIL; - rel->rd_partcheckvalid = false; - rel->rd_partcheckcxt = NULL; + rel->rd_partinfo = NULL; rel->rd_keyattr = NULL; rel->rd_pkattr = NULL; rel->rd_idattr = NULL; diff --git a/src/include/utils/rel_internal.h b/src/include/utils/rel_internal.h index c96b3df1f85..9849c7b4406 100644 --- a/src/include/utils/rel_internal.h +++ b/src/include/utils/rel_internal.h @@ -29,6 +29,45 @@ typedef struct LockRelId Oid dbId; /* a database identifier */ } LockRelId; +/* + * 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. */ @@ -135,21 +174,12 @@ typedef struct RelationData /* data managed by RelationGetFKeyList: */ List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */ - /* 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 */ - - /* data managed by RelationGetPartitionQual: */ - List *rd_partcheck; /* partition CHECK quals */ - 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; /* data managed by RelationGetIndexAttrBitmap: */ Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */ @@ -170,17 +200,7 @@ typedef struct RelationData /* use "struct" here to avoid needing to include fdwapi.h: */ struct FdwRoutine *rd_fdwroutine; /* cached function pointers, or NULL */ - /* - * 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; - bool rd_fkeyvalid; /* true if rd_fkeylist has been computed */ - bool rd_partcheckvalid; /* true if rd_partcheck has been computed */ }; /* fields used only for an index relation */ -- 2.55.0