| From: | PG Bug reporting form <noreply(at)postgresql(dot)org> |
|---|---|
| To: | pgsql-bugs(at)lists(dot)postgresql(dot)org |
| Cc: | happydogly(at)gmail(dot)com |
| Subject: | BUG #19690: Possible stale partition descriptor after concurrent ATTACH PARTITION |
| Date: | 2026-09-15 09:55:16 |
| Message-ID: | 19690-5619e7d182d18d5d@postgresql.org |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
The following bug has been logged on the website:
Bug reference: 19690
Logged by: liuyu
Email address: happydogly(at)gmail(dot)com
PostgreSQL version: 17.9
Operating system: macOS 26.6.2, arm64
Description:
I would like to ask about a possible build-time invalidation race in
RelationBuildPartitionDesc().
This issue was reproduced in a clean PostgreSQL upstream checkout . The
checkout was current master at commit
ff39a858b984d8088caa2a9777948d211f549b65.
SELECT version(); returned:
PostgreSQL 20devel on aarch64-apple-darwin25.6.0, compiled by Apple clang
version 21.0.0 (clang-2100.1.1.101), 64-bit
The host runs macOS 26.6.2 on arm64.
Observed behavior
-----------------
Backend A starts a read that builds the partition descriptor when the parent
has one partition. During that build, backend B commits ATTACH PARTITION and
inserts a row into the newly attached partition. A subsequent statement in
the same reader connection still reports only the original row and
partition:
first_count
-------------
1
second_count | partitions_seen
--------------+-----------------
1 | 1
An independent query after the writer commits reports 2 rows across 2
partitions.
Both SELECT statements run in the same psql connection with normal
autocommit. The second SELECT follows SELECT pg_sleep(1). The writer command
returned ALTER TABLE and INSERT 0 1 before the reader finished.
Test-only instrumentation
-------------------------
Immediately after find_inheritance_children_extended() in
RelationBuildPartitionDesc(), and before processing the child list, this
temporary code was added:
#ifdef RELCACHE_PARTDESC_TEST_DELAY
if (debug_query_string != NULL &&
strstr(debug_query_string, "partdesc-race-reader") != NULL)
pg_usleep(8000000L);
#endif
partdesc.o was compiled with -DRELCACHE_PARTDESC_TEST_DELAY and the backend
was relinked. The hook was removed after the test. The sleep widens the
timing window; it is not a synchronization barrier, so this is an
instrumented timing reproduction.
Setup
-----
CREATE TABLE race_parent_clean(k int) PARTITION BY RANGE(k);
CREATE TABLE race_p1_clean PARTITION OF race_parent_clean
FOR VALUES FROM (0) TO (10);
CREATE TABLE race_p2_clean(k int);
INSERT INTO race_p1_clean VALUES (1);
Reader A, in one psql connection, sends these as separate statements:
/* partdesc-race-reader */ SELECT count(*) AS first_count FROM
race_parent_clean;
SELECT pg_sleep(1);
SELECT count(*) AS second_count,
count(DISTINCT tableoid) AS partitions_seen
FROM race_parent_clean;
About one second after starting the marked reader query, writer B runs:
BEGIN;
ALTER TABLE race_parent_clean ATTACH PARTITION race_p2_clean
FOR VALUES FROM (10) TO (20);
INSERT INTO race_p2_clean VALUES (11);
COMMIT;
Suspected mechanism
-------------------
1. RelationBuildPartitionDesc() obtains a child OID list using inheritance
snapshot S1.
2. B commits ATTACH after S1. ATTACH takes ShareUpdateExclusiveLock on the
parent, compatible with the reader's AccessShareLock.
3. A later child syscache miss can open pg_class. LockRelationOid() calls
AcceptInvalidationMessages() when the lock acquisition is not
LOCKACQUIRE_ALREADY_CLEAR, potentially consuming B's parent relcache
invalidation during the build.
4. The builder finishes from the S1 list and publishes rd_partdesc without
checking whether it was invalidated during construction.
5. Later statements may reuse that descriptor because the invalidation was
already consumed.
The existing boundspec retry appears to handle a child already in the list
whose bound is unavailable, but not an additional child committed after the
list was collected.
Is there an upstream mechanism that prevents this ordering, or should
descriptor construction track invalidations during the build before
publishing its cached result? Please confirm whether this is known or fixed.
The clean-upstream reproduction used the temporary delay hook described
above.
| From | Date | Subject | |
|---|---|---|---|
| Previous Message | Alexandre Felipe | 2026-09-15 08:59:53 | Re: BUG #19686: Rolling back SET TABLESPACE + INSERT leads to index corruption |