Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1w3cCh-001OY4-1h for pgsql-hackers@arkaria.postgresql.org; Fri, 20 Mar 2026 15:56:03 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1w3cCe-006pww-2F for pgsql-hackers@arkaria.postgresql.org; Fri, 20 Mar 2026 15:56:01 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1w3cCe-006pwn-03 for pgsql-hackers@lists.postgresql.org; Fri, 20 Mar 2026 15:56:00 +0000 Received: from fout-b2-smtp.messagingengine.com ([202.12.124.145]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1w3cCc-00000000BoQ-05Py for pgsql-hackers@lists.postgresql.org; Fri, 20 Mar 2026 15:55:59 +0000 Received: from phl-compute-12.internal (phl-compute-12.internal [10.202.2.52]) by mailfout.stl.internal (Postfix) with ESMTP id 909BC1D00175; Fri, 20 Mar 2026 11:55:56 -0400 (EDT) Received: from phl-frontend-03 ([10.202.2.162]) by phl-compute-12.internal (MEProxy); Fri, 20 Mar 2026 11:55:56 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kurilemu.de; h= cc:cc:content-transfer-encoding:content-type:content-type:date :date:from:from:in-reply-to:in-reply-to:message-id:mime-version :reply-to:subject:subject:to:to; s=fm3; t=1774022156; x= 1774108556; bh=bGpYFzDFUSRJYF66qgB5+dcTl5L7miBn1ymP8+hxGgY=; b=U ZFRVM+g3XvKytsdUm7opaKOUlNq7VXe4gbgi4qazbynW/JlpPhe+n44/dtnBkDbD 8HhH8a0urnDXl6VW7bDdiui6Cefddn5A/dwzTyiN/F9DzNsbpMRepDwOVD70YP8G WwtqK1JOlyuEwZYzKG282fVrD3jCwUSC3wiEM7gYuTPUdhcIXUnMeXsV9k9rfzWp 2XGJVMfCea3sLRLsvSzlkVhFBUTYnYZpQvC+sbYLT8gFLfTQJ5cYkiMz9ByCVU8A igcQCjpVu7bBgO6fIhnl3nkkjflBmtVQ6zNuVBzhI3SYdyeF9lK44qmjvRQePRsl Z/76VOy13D8tmlvs5g7Kg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:content-type:date:date:feedback-id:feedback-id :from:from:in-reply-to:in-reply-to:message-id:mime-version :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm1; t=1774022156; x=1774108556; bh=b GpYFzDFUSRJYF66qgB5+dcTl5L7miBn1ymP8+hxGgY=; b=ATochCiEoetIQv0Nb Tvys9NaTxLIRyv8HBcBJbGUPxZ5ewdekb0lNIROz0vbTztdxdzSGapGQgn2zsOuU KQiLFVcIsock99geVMDJs7n/az0IjWqCmGxl2iAexHIrf4EP53wpIj5sBbrDQny0 hSZ+r+fBk2xVCnp1igmkk7HSNNy6X+ugfUuFQfDa2hvWDrp3QjarlhV4+A8wT37u lnTUGJNMOeVONKneIa1e62+sJs4q2FTVupKnTN6iMr03BPUB0s8PrfW9sKtexw98 z2ev/Hjakt1+6d9YIucWNZJzv78eljgt3sdZtoPYOF6XBdJRcV+lbWP5ApXQmcWv ayvSA== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefgedrtddtgdefuddtfeefucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujf gurhepfffhvfevuffkgggtugfgjgesmhekreertddtjeenucfhrhhomheplmhlvhgrrhho ucfjvghrrhgvrhgruceorghlvhhhvghrrhgvsehkuhhrihhlvghmuhdruggvqeenucggtf frrghtthgvrhhnpeegudetudejheduveevgeehjeegleevveevvdeutdejtdekuefhheeh geevtdejteenucffohhmrghinhepvghnthgvrhhprhhishgvuggsrdgtohhmnecuvehluh hsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheprghlvhhhvghrrhgv sehkuhhrihhlvghmuhdruggvpdhnsggprhgtphhtthhopeegpdhmohguvgepshhmthhpoh huthdprhgtphhtthhopegrnhgurhgvshesrghnrghrrgiivghlrdguvgdprhgtphhtthho pegrhhestgihsggvrhhtvggtrdgrthdprhgtphhtthhopehmihhhrghilhhnihhkrghlrg ihvghusehgmhgrihhlrdgtohhmpdhrtghpthhtohepphhgshhqlhdqhhgrtghkvghrshes lhhishhtshdrphhoshhtghhrvghsqhhlrdhorhhg X-ME-Proxy: Feedback-ID: ie3de48e3:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Fri, 20 Mar 2026 11:55:55 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kurilemu.de; s=schmee; t=1774022152; bh=g6EDsrBLRtoDdeLEkUboh7LoHgiHcPEN4qoFITpaxm0=; h=Date:From:To:Cc:Subject:In-Reply-To:From; b=nRWuUEoTQ2REAW7TsozMbR52wwP2pRtJmFltbV3OtKtWjygv+bds9bnq+XMqBu/Cj LuOgCdNAMrzOaVPR79xDOGn/4d5PY79efQ9s7yyAroKAUoGZHsRBj3tgTZILrliWHl Htmd2mhlZGhaFoHzIk1Z8/E/PK2QfbU0fMKEbQ+FyCaAIIoGPVqYBeclUhH5++w91D gK3LN29/ca9G4kxQyZmYuUglY4+fYO3mZjDcjx3BZhHsEZxXiCA2NPU2Q4nAvrE02T MzLSmbuKvIbn7tkIXpvsiCys4Y8wQCWuepiMizZviPykjQsTvVtCA7dLaYTj+qH/+g TPbP87kNg1WHw== Received: by schmee.kurilemu.internal (Postfix, from userid 1000) id 541045F; Fri, 20 Mar 2026 16:55:52 +0100 (CET) Date: Fri, 20 Mar 2026 16:55:52 +0100 From: =?utf-8?Q?=C3=81lvaro?= Herrera To: Andres Freund Cc: Antonin Houska , pgsql-hackers@lists.postgresql.org, Mihail Nikalayeu Subject: Re: Race conditions in logical decoding Message-ID: <202603201543.t6gxppyyk66p@alvherre.pgsql> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="obdbyrpgpb3edptv" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <5k2dfckyp6zv2fiovosvtbya5onvplgviz5n4kdamxupff4vi2@yytzfnwr2ox7> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --obdbyrpgpb3edptv Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit Hi, I realized that I hadn't posted the patch version I described somewhere downthread. Here it is, as 0001. While thinking about it for posting just now, I wondered if it would work to consider that any transaction whose commit record has been decoded but which nevertheless gets a true return from TransactionIdIsInProgress(), just is not committed yet and so should be omitted from the xip array of the snapshot. That is, if it's still-running, then we just don't copy it into the output snapshot. That's implemented as 0002 here. This seems somehow less controversial, as we don't have to test TransactionIdDidCommit() for a transaction that we haven't seen as not-running per PGPROC; and it should also be faster, because we don't have to wait for anybody to commit. However it gives me pause that perhaps the snapshot would not be fully correct. (Indeed there are a few failing tests in the subscription suite). Failing other ideas, I think we should just go with 0001. We'd need more commentary on why is TransactionIdDidCommit() OK, when we haven't scanned PGPROC for that xid, though. Thanks, -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "El que vive para el futuro es un iluso, y el que vive para el pasado, un imbécil" (Luis Adler, "Los tripulantes de la noche") --obdbyrpgpb3edptv Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-Fix-race-conditions-during-the-setup-of-logical-deco.patch" From 6bcc6c35e480ffa02117c1e6591f0bccdc70ad12 Mon Sep 17 00:00:00 2001 From: Antonin Houska Date: Mon, 19 Jan 2026 16:07:45 +0100 Subject: [PATCH 1/2] Fix race conditions during the setup of logical decoding. Although it's rather unlikely, it can happen that the snapshot builder considers transaction committed (according to WAL) before the commit could be recorded in CLOG. In an extreme case, snapshot can even be created and used in between. Since both snapshot and CLOG are needed for visibility checks, this inconsistency can make them work incorrectly. The typical symptom is that a transaction that the snapshot considers not running anymore is (per CLOG) considered aborted instead of committed. Thus a new tuple version can be evaluated as invisible (if xmin is incorrectly considered aborted) or a deleted tuple version can be evaluated as visible (if xmax is incorrectly considered aborted). This patch fixes the problem by checking if all the XIDs that the new snapshot considers committed are really committed per CLOG. If at least one is not, the check is repeated after a short delay. However, a single check is sufficient in almost all cases, so the performance impact should be minimal. --- src/backend/replication/logical/snapbuild.c | 27 ++++++++++++++++++- .../utils/activity/wait_event_names.txt | 1 + 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index 37f0c6028bd..d7ea098cb37 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -377,7 +377,7 @@ SnapBuildBuildSnapshot(SnapBuild *builder) /* * We misuse the original meaning of SnapshotData's xip and subxip fields - * to make the more fitting for our needs. + * to make them more fitting for our needs. * * In the 'xip' array we store transactions that have to be treated as * committed. Since we will only ever look at tuples from transactions @@ -402,6 +402,31 @@ SnapBuildBuildSnapshot(SnapBuild *builder) snapshot->xmin = builder->xmin; snapshot->xmax = builder->xmax; + /* + * Although it's very unlikely, it's possible that a commit WAL record was + * decoded but CLOG is not aware of the commit yet. Should the CLOG update + * be delayed even more, visibility checks that use this snapshot could + * work incorrectly. Therefore we check the CLOG status here. + */ + for (int i = 0; i < builder->committed.xcnt; i++) + { + for (;;) + { + if (TransactionIdDidCommit(builder->committed.xip[i])) + break; + else + { + (void) WaitLatch(MyLatch, + WL_LATCH_SET | WL_TIMEOUT | + WL_EXIT_ON_PM_DEATH, + 10L, + WAIT_EVENT_SNAPBUILD_CLOG); + ResetLatch(MyLatch); + } + CHECK_FOR_INTERRUPTS(); + } + } + /* store all transactions to be treated as committed by this snapshot */ snapshot->xip = (TransactionId *) ((char *) snapshot + sizeof(SnapshotData)); diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt index 4aa864fe3c3..987df777e47 100644 --- a/src/backend/utils/activity/wait_event_names.txt +++ b/src/backend/utils/activity/wait_event_names.txt @@ -181,6 +181,7 @@ PG_SLEEP "Waiting due to a call to pg_sleep or a sibling fu RECOVERY_APPLY_DELAY "Waiting to apply WAL during recovery because of a delay setting." RECOVERY_RETRIEVE_RETRY_INTERVAL "Waiting during recovery when WAL data is not available from any source (pg_wal, archive or stream)." REGISTER_SYNC_REQUEST "Waiting while sending synchronization requests to the checkpointer, because the request queue is full." +SNAPBUILD_CLOG "Waiting for CLOG update before building snapshot." SPIN_DELAY "Waiting while acquiring a contended spinlock." VACUUM_DELAY "Waiting in a cost-based vacuum delay point." VACUUM_TRUNCATE "Waiting to acquire an exclusive lock to truncate off any empty pages at the end of a table vacuumed." -- 2.47.3 --obdbyrpgpb3edptv Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0002-What-about-just-ignoring-the-xacts-if-they-re-still-.patch" From 54d22ff9cd46b00b3bdd06f3bdd4b22738645eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Fri, 20 Mar 2026 15:58:05 +0100 Subject: [PATCH 2/2] What about just ignoring the xacts if they're still running? --- src/backend/replication/logical/snapbuild.c | 37 +++++---------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index d7ea098cb37..2a4581b75a6 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -402,38 +402,17 @@ SnapBuildBuildSnapshot(SnapBuild *builder) snapshot->xmin = builder->xmin; snapshot->xmax = builder->xmax; - /* - * Although it's very unlikely, it's possible that a commit WAL record was - * decoded but CLOG is not aware of the commit yet. Should the CLOG update - * be delayed even more, visibility checks that use this snapshot could - * work incorrectly. Therefore we check the CLOG status here. - */ - for (int i = 0; i < builder->committed.xcnt; i++) - { - for (;;) - { - if (TransactionIdDidCommit(builder->committed.xip[i])) - break; - else - { - (void) WaitLatch(MyLatch, - WL_LATCH_SET | WL_TIMEOUT | - WL_EXIT_ON_PM_DEATH, - 10L, - WAIT_EVENT_SNAPBUILD_CLOG); - ResetLatch(MyLatch); - } - CHECK_FOR_INTERRUPTS(); - } - } - /* store all transactions to be treated as committed by this snapshot */ snapshot->xip = (TransactionId *) ((char *) snapshot + sizeof(SnapshotData)); - snapshot->xcnt = builder->committed.xcnt; - memcpy(snapshot->xip, - builder->committed.xip, - builder->committed.xcnt * sizeof(TransactionId)); + + for (int i = 0; i < builder->committed.xcnt; i++) + { + if (!TransactionIdIsInProgress(builder->committed.xip[i])) + { + snapshot->xip[snapshot->xcnt++] = builder->committed.xip[i]; + } + } /* sort so we can bsearch() */ qsort(snapshot->xip, snapshot->xcnt, sizeof(TransactionId), xidComparator); -- 2.47.3 --obdbyrpgpb3edptv--