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 1vJEOn-00FTGR-1j for pgsql-general@arkaria.postgresql.org; Wed, 12 Nov 2025 17:12:48 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vJEOl-00D5OB-14 for pgsql-general@arkaria.postgresql.org; Wed, 12 Nov 2025 17:12:47 +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 1vJEOk-00D5O1-3D for pgsql-general@lists.postgresql.org; Wed, 12 Nov 2025 17:12:46 +0000 Received: from mail-yx1-xb129.google.com ([2607:f8b0:4864:20::b129]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1vJEOj-006lOv-1L for pgsql-general@lists.postgresql.org; Wed, 12 Nov 2025 17:12:46 +0000 Received: by mail-yx1-xb129.google.com with SMTP id 956f58d0204a3-640c9c85255so1339964d50.3 for ; Wed, 12 Nov 2025 09:12:45 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1762967564; x=1763572364; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=ezRZQzkDe8qfC7e/KWcBAX/rclwvd1M2T4k3Qloy2HU=; b=XrZGOIi8oT192bQAPL15rYoPTSF1ddzCZsSjSgRsporkoIiKyE865YoIBGRWc/2XKM Jv1KEdknyU97cTqVNje8G4rOZE4PERlXYHqcM/wBFFloY67NotZ1V3NUxmwLGkYfx0vD qaeNk+jEa/MUzO9MerVgL7q3zFxmNWWSirFuNbSWv5LuHJkbkEW1GuxvfwM02ISPX14Z xLCXnLzNd6Kj4n4fBlqF2ascXMTRvkJQfmYUK3iAPU3LkvO+oGw985HCsejBJW0rDnFB 3r7hTdh5TDLoVnIqgDRHWq+Tu5QlDpAp2hy/F9woxFvKgA6OnzCE8huL9bku84ukhfRS Q7Bw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1762967564; x=1763572364; h=cc:to:subject:message-id:date:from:mime-version:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=ezRZQzkDe8qfC7e/KWcBAX/rclwvd1M2T4k3Qloy2HU=; b=a+IQ2WLPnHzs1UAAL8zk4uAThGyobtIEfTMOfZjJ3BLFIVXFeDUiscXvrVWXeORBqU Tk0EawbTRZN6PH/Hb5FrZLmO36hd7DeS3O3mGTCgg2IFXlIgbmNbI7lQgdiZWrlfK2At H0FDYy/VdDyCTiu0DVW42xVFYJMuaaMRUoQFj8tZ4t4P6dpxTW3USydGtU9AUYndL6Sm 3tBUuwo6gsLjSQrOw8B8ox3Q0dn6X2OvSUv0Ld2XAdip+T/Ir5UsKs8w4yC7+sEj//AX XWQWaUqfA8lChfXWgVXqLdH0oP0OS8OhoJ2HYboEiIG2/GpV1KLVLHrTNKEnW1NlD3er Tbaw== X-Gm-Message-State: AOJu0YwDc0VHDg8lk7CUcCzJZ2iziqWwbSYG9jF03qX2DivIvAncgrrA CLXIzq0DlYrD3zZGnfwlDkCpmMs4uYexLIUindtPhmK+oi1l7ySUPuRjJnB33pezw1VRK66JCqX 4iGgwoSt5zS02yLTrkQS19fLNVQBIOit0mlZI X-Gm-Gg: ASbGncu/9gipxEXwVqO5aJexxst1G8ByROH97vixUTX18oIVfqT0RakeF6bfdotChcK BGiItryKn5x/VIeukwHwRGyRu9e3hf0PizHhQCoZXOVTbZl0J+n0vEqyEUQpd5Am6MwwaSVALmV SM3ZimUF0hFc8wC/x4C8r/n7C4rB9prF7nADBVWDmlDPyrubtfJZF3TcjHgLzO4A+oYQINsuEa5 MgbN17QtBJaVvQr5/rtJiOQyJ5RQ1uJArEQUZg6Iu+TT3d7+4vw9HMDPhZZNRsAB5cosgGIEm8R FZkVrYlBSQ== X-Google-Smtp-Source: AGHT+IFh90b/RX8ZMDGszAFH02DEGOwX9SOLZjlOzv+GUHYHcRnZ1EoVAAyTSdclJpaLT7DEHFbjjejTHg6BnYGK5CU= X-Received: by 2002:a53:d004:0:b0:63f:bd67:7c50 with SMTP id 956f58d0204a3-64101b14e07mr2498837d50.44.1762967564431; Wed, 12 Nov 2025 09:12:44 -0800 (PST) MIME-Version: 1.0 From: Bernice Southey Date: Wed, 12 Nov 2025 17:12:08 +0000 X-Gm-Features: AWmQ_blvpGOkoi5IitOcdzfzG4gyq0mQUmJzpzxRHyZanlzkEkhfV_W46LyHppE Message-ID: Subject: Is this expected concurrency behaviour for EvalPlanQual and ctid? To: pgsql-general@lists.postgresql.org Cc: pg@sophiebits.com Content-Type: text/plain; charset="UTF-8" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi all, I've run into a concurrency issue and am hoping someone here with more internal PostgreSQL knowledge than me can explain why. My scenario is keeping a sync table up to date with the latest message by recipient key. If I use ctid, all the updated records are dropped by the concurrent query, after the first query releases the locks. If I use a standard column it works as expected. I'm hopeful that the recently accepted patch [1] from Sophie is the same issue, but I'm not knowledgeable enough to tell. I tried setting enable_tidscan to 0 based on the discussion, but it made no difference, so I thought I best ask. If this behaviour is as expected, I'd really like to understand why, so I can better reason about ctid in concurrent updates. To test, I created 10 sync records and 200 messages for each of them. I processed 1000 messages at a time concurrently in two different sessions. I duplicated the test for the ctid case and my key column (k) case. The only difference is the final where clause for the update. The output should be each key synced to 200. --setup CREATE TABLE msg(k INT, m INT); CREATE TABLE msg2(k INT, m INT); CREATE TABLE sync(k INT PRIMARY KEY, m INT DEFAULT 0); CREATE TABLE sync2(k INT PRIMARY KEY, m INT DEFAULT 0); INSERT INTO sync(k) SELECT i FROM generate_series(1, 10) i; INSERT INTO sync2(k) SELECT i FROM generate_series(1, 10) i; INSERT INTO msg SELECT k, i FROM sync, generate_series(1, 200) i ORDER BY random(); INSERT INTO msg2 SELECT k, i FROM sync, generate_series(1, 200) i ORDER BY random(); SELECT pg_advisory_lock(1); --run in two new sessions SELECT pg_advisory_lock_shared(1); SELECT pg_advisory_unlock_shared(1); WITH --by ctid lock_msg AS ( SELECT ctid FROM msg FOR UPDATE SKIP LOCKED LIMIT 1000), delete_msg AS ( DELETE FROM msg m USING lock_msg l WHERE m.ctid = l.ctid RETURNING k, m), msg_max AS ( SELECT DISTINCT ON (k) k, m FROM delete_msg ORDER BY k, m DESC), lock_sync AS ( SELECT s.ctid, k, m.m from msg_max m join sync s USING (k) ORDER BY k FOR UPDATE OF s), update_sync AS ( UPDATE sync s set m = l.m FROM lock_sync l WHERE s.ctid = l.ctid AND l.m > s.m --by ctid RETURNING s.k, s.m, s.ctid), --by k lock_msg2 AS ( SELECT ctid FROM msg2 FOR UPDATE SKIP LOCKED LIMIT 1000), delete_msg2 AS ( DELETE FROM msg2 m USING lock_msg2 l WHERE m.ctid = l.ctid RETURNING k, m), msg_max2 AS ( SELECT DISTINCT ON (k) k, m FROM delete_msg2 ORDER BY k, m DESC), lock_sync2 AS ( SELECT s.ctid, k, m.m FROM msg_max2 m join sync2 s USING (k) ORDER BY k FOR UPDATE OF s), update_sync2 AS ( UPDATE sync2 s SET m = l.m FROM lock_sync2 l WHERE s.k = l.k AND l.m > s.m --by k RETURNING s.k, s.m, s.ctid) --results SELECT 'ctid', * FROM update_sync UNION ALL SELECT 'k', * FROM update_sync2; --run last in original session SELECT pg_advisory_unlock(1); All the records in both msg tables are deleted. In the sync tables, 'k' is updated as expected across the two sessions, but 'ctid' dropped all the records in the second session. Session 1 ctid 1 199 (0,11) ctid 2 200 (0,12) ctid 3 200 (0,13) ctid 4 199 (0,14) ctid 5 200 (0,15) ctid 6 200 (0,16) ctid 7 199 (0,17) ctid 8 200 (0,18) ctid 9 200 (0,19) ctid 10 200 (0,20) k 1 200 (0,11) k 2 195 (0,12) k 3 200 (0,13) k 4 200 (0,14) k 5 199 (0,15) k 6 199 (0,16) k 7 196 (0,17) k 8 194 (0,18) k 9 200 (0,19) k 10 196 (0,20) Session 2 k 2 200 (0,21) k 5 200 (0,22) k 6 200 (0,23) k 7 200 (0,24) k 8 200 (0,25) k 10 200 (0,26) Thanks, Bernice 1 [https://www.postgresql.org/message-id/flat/4a6268ff-3340-453a-9bf5-c98d51a6f729%40app.fastmail.com]