| From: | vignesh C <vignesh21(at)gmail(dot)com> |
|---|---|
| To: | Nikhil Sontakke <nikhil(at)planetscale(dot)com> |
| Cc: | Zhijie Hou <houzhijie22(at)gmail(dot)com>, shveta malik <shveta(dot)malik(at)gmail(dot)com>, "Zhijie Hou (Fujitsu)" <houzj(dot)fnst(at)fujitsu(dot)com>, PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org> |
| Subject: | Re: Publication DDL can race with a concurrent UPDATE |
| Date: | 2026-10-12 09:37:40 |
| Message-ID: | CALDaNm25XWdK1BBzcMma2xhJXEqyTbKRm82u0VCeHu2WDCUwaQ@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
On Fri, 9 Oct 2026 at 15:16, Nikhil Sontakke <nikhil(at)planetscale(dot)com> wrote:
>
> Moving toward taking a stronger lock during DDL without penalizing DML sounds attractive.
>
> I tried v3 locally and found a lock-upgrade deadlock involving
> pg_publication, even when the publications cover disjoint schemas.
Thanks Nikhil for reporting the issue, I could reproduce the issue
with the steps provided.
> CreatePublication() already holds RowExclusiveLock on pg_publication when
> LockTablesWithoutReplicaIdentity() requests ShareLock on the same catalog.
The synchronization protocol in LockTablesWithoutReplicaIdentity() /
RelationBuildPublicationDesc() was using the ordinary relation lock on
pg_publication via LockRelationOid(). Any concurrent CREATE/ALTER/DROP
PUBLICATION already holds a RowExclusiveLock on the same relation
while modifying the catalog. Since RowExclusiveLock is
self-compatible, these catalog locks do not block each other directly.
However, when each session subsequently requests a ShareLock, that
lock conflicts with the other session's still-held RowExclusiveLock,
creating a deadlock cycle.
> Although two ShareLock requests are compatible, each conflicts with the
> other transaction's existing RowExclusiveLock.
>
> Here is a reproducer (the spec file is attached with this email as well):
>
> -- Setup
> CREATE SCHEMA review_a;
> CREATE TABLE review_a.t (id int PRIMARY KEY, val int);
>
> CREATE SCHEMA review_b;
> CREATE TABLE review_b.t (id int PRIMARY KEY, val int);
>
> CREATE TABLE review_outside (id int, val int);
> INSERT INTO review_outside VALUES (1, 1);
>
> -- Session 1
> BEGIN;
> UPDATE review_outside SET val = 2;
>
> Although review_outside is not published, its descriptor build acquires
> RowExclusiveLock on pg_publication because it lacks replica identity.
> That lock is retained until transaction end.
>
> -- Session 2
> CREATE PUBLICATION review_pb FOR TABLES IN SCHEMA review_b;
>
> Session 2 blocks requesting ShareLock on pg_publication while also holding
> the RowExclusiveLock acquired by CreatePublication().
>
> -- Session 1
> CREATE PUBLICATION review_pa FOR TABLES IN SCHEMA review_a;
>
> This deadlocks: Session 1 requests ShareLock while Session 2 holds
> RowExclusiveLock, and Session 2 is waiting for Session 1's RowExclusiveLock.
>
> The server reports both sessions waiting for ShareLock on relation
> (pg_publication), each blocked by the other. The same sequence completes
> without blocking on the unpatched build.
>
> This cycle involves only the publication catalog locks, so sorting the
> user-table locks does not address it.
>
> A dedicated synchronization lock would avoid colliding with ordinary catalog
> write locks and remove this additional lock-upgrade cycle.
Agreed, How about we use dedicated LockDatabaseObject() tag instead
of LockRelationOid():
/* LockTablesWithoutReplicaIdentity(), publicationcmds.c */
LockDatabaseObject(PublicationRelationId, InvalidOid, 0, ShareLock);
/* RelationBuildPublicationDesc(), relcache.c */
LockDatabaseObject(PublicationRelationId, InvalidOid, 0, RowExclusiveLock);
Using InvalidOid as the object ID is safe as a sentinel because it is
reserved and can never be a valid publication OID. Therefore, it
cannot conflict with AlterPublication()'s existing per-publication
lock on the same catalog.
However, that would
> not address the table/interlock deadlocks already acknowledged in the function
> comment.
Confirmed, and that is expected. The fix only removes the deadlock
cycle between operations that do not actually share any objects.
The attached top-up patch (on top of v3 patch shared at [1]) has the
changes for the same.
Thoughts?
Regards,
Vignesh
| Attachment | Content-Type | Size |
|---|---|---|
| Fix_deadlock_issue.patch | application/octet-stream | 2.7 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | David Rowley | 2026-10-12 09:44:46 | Re: Material node doesn't show "Maximum Storage" for parallel workers |
| Previous Message | Ashutosh Sharma | 2026-10-12 09:33:26 | Re: Reduce logging during prepared transaction recovery |