On Thu, 10 Sept 2026 at 11:43, David Geier <[email protected]> wrote: > > To further reduce relcache memory usage we can additionally shrink the > size consumed by struct RelationData: [...] > All RelationData objects are allocated in CacheMemoryContext. The size > of CacheMemoryContext can be inspected via pg_backend_memory_contexts: > > branch | used_bytes > --------|--------------------------------- > master | 617,984,696 bytes = ~589.36 MiB > patched | 562,507,232 bytes = ~536.45 MiB > > We save ~53 MiB or ~10% of CacheMemoryContext per backend!
That's quite nice! > The attached patch set passes tests and consists of the following > individual patches: [0001-0003] I Haven't looked at these in detail. Yes, they provide the largest savings, but that also makes them most complicated to review in detail. I'll stave that off for now. > - 0004: Removes the rd_lockinfo member. RelationGetLockRelId() now > computes it when needed. This also removes RelationInitLockInfo() and > the initialization work associated with it. > => sizeof(RelationData) == 304 bytes Can we really rely on rd_rel always being valid when we need the LockInfo? I'm OK with avoiding duplicating rd_id/rd_lockinfo.lockRelId.relId, but I'm not sure dbId can always be derived with rd_rel->relisshared whenever we need it. > - 0005: Replaces the embedded partition key, descriptors, partition > qual, validity flag, and memory contexts with one lazily allocated > RelationPartitionInfo pointer. > => sizeof(RelationData) == 264 bytes I haven't worked on partitioning, so I'm not fully confident that this has sufficiently low additional overhead to be worth applying. > - 0006: Removes rd_fkeyvalid by using RELCACHE_FKEYLIST_NOT_LOADED as > the initial state of rd_fkeylist. NIL continues to mean that the list > was computed and no foreign keys were found. This patch is not strictly > needed because it currently doesn't further reduce the size. The same we > could with RelationPartitionInfo::partcheckvalid. > => sizeof(RelationData) == 264 bytes I'm not a fan of this change. Sentinel values *can* have their place, but I really don't like non-NULL values being used to signal "invalid" states, and seeing that it doesn't actually save any bytes I'd prefer to not add this complication. > - 0007: Removes rd_index member that always pointed inside > rd_indextuple. Callers now use RelationGetIndex(), which applies > GETSTRUCT() to that tuple. > => sizeof(RelationData) == 256 bytes This seems fine to me. > - 0008: Adds a static assertion that sizeof(RelationData) <= 256. > => sizeof(RelationData) == 256 bytes I'm not a fan of this, because we generally don't add assertions on struct sizes unless it's critically important a struct remains the asserted size (or, in this case, doesn't become larger than that). In this case, I don't think it's critically important that the struct fits in the 256-byte aset bucket, given that relcache memory usage has never been capped, and a slab context would similarly do the trick for avoiding memory usage cliffs when the size of the struct is increased. > Note that the necessity to arrive at 256 bytes stems from ASET's > allocation granularity being powers of two. To profit from further size > reductions of RelationData, we would need to use a SLAB memory context > because it's unlikely that we'll get to 128 bytes. Let's start with palloc-ing all RelationData into Slab contexts (like attached, tagged with .nocfbot to avoid your patch's CI). This allows us to start saving bytes immediately, and immediately improves memory usage for every reduction in size so that the merits of each later patch can be evaluated separately. This will be very useful if we can't agree on some of the changes that are necessary to reduce the size down to the next smaller aset size bucket. Kind regards, Matthias van de Meent Databricks (https://www.databricks.com)
From dc4466ddddcf87edd586da641a2d88a46cef7fc6 Mon Sep 17 00:00:00 2001 From: Matthias van de Meent <[email protected]> Date: Mon, 14 Sep 2026 14:02:26 +0200 Subject: [PATCH v1] relcache: Use Slab context for RelationData allocs This avoids the alignment wastage that AllocSet contexts have, saving lots when a backend has lots of relations in its relcache. --- src/backend/utils/cache/relcache.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index f475d703977..7deaf041f94 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -137,6 +137,14 @@ typedef struct relidcacheent static HTAB *RelationIdCache; +/* + * Slab context for all RelationData allocations. + * + * In large catalogs, aset.c's chunk sizing rules would waste significant + * amounts of memory, so a Slab context is used instead. + */ +static MemoryContext RelCacheMemoryContext; + /* * This flag is false until we have prepared the critical relcache entries * that are needed to do indexscans on the tables read by relcache building. @@ -421,13 +429,15 @@ AllocateRelationDesc(Form_pg_class relp) Form_pg_class relationForm; /* Relcache entries must live in CacheMemoryContext */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); /* * allocate and zero space for new relation descriptor */ relation = palloc0_object(RelationData); + MemoryContextSwitchTo(CacheMemoryContext); + /* make sure relation is marked as having no open file yet */ relation->rd_smgr = NULL; @@ -1892,11 +1902,14 @@ formrdesc(const char *relationName, Oid relationReltype, int i; bool has_not_null; + MemoryContextSwitchTo(RelCacheMemoryContext); /* * allocate new relation desc, clear all fields of reldesc */ relation = palloc0_object(RelationData); + MemoryContextSwitchTo(CacheMemoryContext); + /* make sure relation is marked as having no open file yet */ relation->rd_smgr = NULL; @@ -3575,13 +3588,15 @@ RelationBuildLocalRelation(const char *relname, if (!CacheMemoryContext) CreateCacheMemoryContext(); - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(RelCacheMemoryContext); /* * allocate a new relation descriptor and fill in basic state fields. */ rel = palloc0_object(RelationData); + MemoryContextSwitchTo(CacheMemoryContext); + /* make sure relation is marked as having no open file yet */ rel->rd_smgr = NULL; @@ -4021,6 +4036,10 @@ RelationCacheInitialize(void) RelationIdCache = hash_create("Relcache by OID", INITRELCACHESIZE, &ctl, HASH_ELEM | HASH_BLOBS); + RelCacheMemoryContext = + SlabContextCreate(CacheMemoryContext, "Relation bump cxt", + 8192, sizeof(RelationData)); + /* * reserve enough in_progress_list slots for many cases */ @@ -6264,7 +6283,8 @@ load_relcache_init_file(bool shared) rels = repalloc_array(rels, Relation, max_rels); } - rel = rels[num_rels++] = (Relation) palloc(len); + rel = rels[num_rels++] = + (Relation) MemoryContextAlloc(RelCacheMemoryContext, len); /* then, read the Relation structure */ if (fread(rel, 1, len, fp) != len) -- 2.54.0 (Apple Git-157)
