| From: | David Geier <geidav(dot)pg(at)gmail(dot)com> |
|---|---|
| To: | Haibo Yan <tristan(dot)yim(at)gmail(dot)com>, Matthias van de Meent <boekewurm+postgres(at)gmail(dot)com> |
| Cc: | Pg Hackers <pgsql-hackers(at)postgresql(dot)org> |
| Subject: | Re: Reducing relcache memory usage 2: shrink sizeof(RelationData) |
| Date: | 2026-10-16 13:02:46 |
| Message-ID: | e660a303-ac59-45bd-9471-6c7a5a20b4a3@gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi Haibo,
Thanks for your review and kudos for providing test cases and fixes.
Much appreciated.
> I tested v3 independently on Linux x86_64, based on 9dbc53591a2. All
> eight attachments apply there without context changes. I concentrated
> on v3 0001-0006, following the cost/benefit concerns already raised by
> Matthias about 0007 and 0008.
I've removed 0007 and 0008 from the new patch set.
> I found a predicate-locking crash introduced by the union in 0004. The
> predicate code uses rd_index (and, with 0008, RelationGetIndex()) to
> decide whether a Relation is an index. For a rule-bearing table,
> rd_rules occupies the same union storage, so that test can interpret a
> RuleLock as index metadata.
> This reproduces with 0001-0006; 0008 is not required. A minimal case is:
>
> CREATE TABLE t (a int PRIMARY KEY);
> CREATE RULE r AS ON INSERT TO t DO ALSO SELECT 1;
> BEGIN ISOLATION LEVEL SERIALIZABLE;
> INSERT INTO t VALUES (1);
> TRUNCATE t;
> In a cassert build, TRUNCATE aborts at
> CheckTableForSerializableConflictIn(). At that point relkind is 'r',
> and rd_rules and the overlaid rd_index contain the same non-NULL address.
> The server process exits with SIGABRT. The unpatched baseline passes the
> same pg_regress test.
> The first attached patch adds RelationIsIndex(), based on relkind, and> uses it at the three predicate-locking classification sites. The second
> adds the pg_regress test. Both ordinary and partitioned indexes remain
> covered by the predicate.
Fixed the problem using your patch. I don't think we should include the
test cases as it's very specific. Opinions?
> I also reproduced an ERROR-path problem from 0006 using an injection
> point at its new RelationPartitionInfo allocation inside
> RelationRebuildRelation(). After an ALTER TABLE added a column to a
> partitioned table, the injected ERROR occurred after the descriptor swap.
> Rollback restored the two-column catalog state, but the same backend kept
> the three-column descriptor and then reported:
>
> could not find inherited attribute "key_added"
Good catch!
> An equivalent general swap window exists without this series. The v3
> regression is specifically that 0006 adds an allocation failure point to
> the region. I did not observe or claim a real OOM.
>
> The third patch allocates destination partition metadata before the swap
> and before reparenting a completed partition-key context. It also avoids
> reading table-side partition metadata for index relations. With the
> allocation moved before the swap, an ERROR leaves the old descriptor intact
> and marked invalid; the next lookup rebuilds it normally after rollback.
> Ten repeated subtransaction failures, partitioned indexes, and
> debug_discard_caches=1 also passed without an external invalidation.
Fixed the problem using your patch.
> Finally, the init-file loader reconstructs index access fields but does
> not reset rd_indexprs, rd_indpred, rd_exclops, rd_exclprocs, or
> rd_exclstrats. Those are serialized process pointers whose contents are
> supposed to be initialized lazily. The fourth patch resets the five
> fields without changing the init-file format. I did not find a reachable
> failure with current catalog indexes, so I regard this as an
> initialization correctness hazard rather than a demonstrated SQL crash.
Fixed as well. Additionally moved into the else branch of the check
if (!RelationIsIndex(rel))
so that all code that resets lazily built fields is in one place.
> On Linux, sizeof(RelationData) is 496 bytes at the baseline, 272 through
> 0006 and 0007, and 264 through 0008. Under the same 8 KiB Slab setup,
> 0006 and 0007 both used 302 blocks and 2,474,160 total bytes for a workload
> loading 8,731 relations. This supports the limited-benefit point already
> made in the review discussion.
With the latest patch set (excluding 0007 and 0008), we go from 496
bytes on master to 248 bytes.
> One additional issue: original v3 0008 does not build on this base because
> execReplication.c still has two rd_index references. I did not repair it
> silently or use that configuration for runtime results.
Removed in latest patch set.
Attached is v4 of the patch set.
Beyond that:
1. Bumped RELCACHE_INIT_FILEMAGIC in any patch that makes structural
changes to struct RelationData, so that PostgreSQL rebuilds old init
files if necessary.
2. Moved many more members that are relation-only to the corresponding
union leg.
3. Moved members related to partitioning out of the relation-only table
leg because indexes can be partitioned as well.
--
David Geier
| Attachment | Content-Type | Size |
|---|---|---|
| v4-0001-Assert-CacheMemoryContext-exists-in-relcache-call.patch | text/plain | 1.7 KB |
| v4-0002-Allocate-RelationData-structs-in-a-dedicated-slab.patch | text/plain | 4.1 KB |
| v4-0003-Move-RelationData-to-an-internal-header.patch | text/plain | 22.3 KB |
| v4-0004-Overlay-relation-kind-specific-relcache-fields.patch | text/plain | 27.2 KB |
| v4-0005-Allocate-partition-cache-data-lazily.patch | text/plain | 20.5 KB |
| v4-0006-Pack-RelationData-to-eliminate-padding.patch | text/plain | 5.1 KB |
| v4-0007-Compute-relation-lock-identifiers-on-demand.patch | text/plain | 21.1 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Fujii Masao | 2026-10-16 13:46:50 | Re: [PATCH] Doc fix for autovacuum_max_parallel_workers |
| Previous Message | David Geier | 2026-10-16 13:02:38 | Re: Reducing relcache memory usage 2: shrink sizeof(RelationData) |