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 1wgnxA-006Pim-1X for pgsql-bugs@arkaria.postgresql.org; Mon, 06 Jul 2026 18:22:00 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wgnx7-002OnX-2l for pgsql-bugs@arkaria.postgresql.org; Mon, 06 Jul 2026 18:21:57 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wgnx7-002OnP-1o for pgsql-bugs@lists.postgresql.org; Mon, 06 Jul 2026 18:21:57 +0000 Received: from mail-lf1-x12c.google.com ([2a00:1450:4864:20::12c]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wgnx5-000000026cT-1iY5 for pgsql-bugs@lists.postgresql.org; Mon, 06 Jul 2026 18:21:57 +0000 Received: by mail-lf1-x12c.google.com with SMTP id 2adb3069b0e04-5aebfa21c62so3988374e87.3 for ; Mon, 06 Jul 2026 11:21:55 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1783362114; cv=none; d=google.com; s=arc-20260327; b=HZf7GzEDvkr8MOx7zXk9Gi87TH1ONXKXZ9hZoPx2mv3+0ukKbtqco0blnbl7V0xupR QYDZFFdzJ0IofqzVQS4W2p3DVw8wbqJ+pP4kIR5wEyxwpxO0Ze7W5+BDwWckL9EbiZWy T8WmWj9pu/XKsR81DYviNzK9OIOLrlKP9RxOT0TEPs8ItLW7b/XtNppgrEHUNq/9C3Ju jh9NGmtlQgXLOhxfD0s7HTLBog2i8AKPE+HJSdaHZLsq1ndcJZs9vEAtceFhrVtu57XH aUOpjZsAlr7cKOVRuWKVSPtQyORsjzIWi6JxFzC2FQVlixCaccX1ksbjLpKPnMnw5dzh yG2g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=9A5m7vFiwsY5pBCG1/DsaDpjFT6tRBps/TnJxETkseY=; fh=5M7GrRzHPDzM5leuyEV2ZjfVeG77ZwYr9N/6XlMHKsU=; b=gcTMC/LEk+nqqV3xjnemjnBtTDogDhybpkzClj8wFpKQ5el/Iq58HXh/+e2XXXIb7o izir9T9u5eeWGsSGGzHiimEkSzk87yREyoM1A7gBv+2MXlQqbRroFvqrpnaq9ZWYvqLO +obIXki+/O62y2QkBrddWkl0puMLem2OBzFoqQqc0MdLu+1pI5TPAlE4t+/IcdwmVSZt 0mrq6SxYOstSi6Gc4Wc3BdDe39/9qMEgLfv/Bwq+fhtU1VET2sY27YeJacB88j8IYvGI G5WmEn8DUhE54yzXj7Isyd6YtglTT/ZbKLhDw69lxHrv5TqsYj6xd+72kJcxIrn2a64t CKCg==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783362114; x=1783966914; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=9A5m7vFiwsY5pBCG1/DsaDpjFT6tRBps/TnJxETkseY=; b=GAUaJv5cIgNx66K1oDHsTqcmRgWWTlfBcHIp9bjjhSCcQgo2TrUbA9FYWQ+jeDu/Fg jPodZ3cabcLr7j+Kqh4J/Gf5fI7FOduBovg/Er6Ia+LHc5pfH2JMZZnAdabEB3E+8egv mPc5ZZCDngyno7Q7wehfi844dFkpt1yKKH9Z49GeaT/vhOVzK8e7qI48No6H15YJtQu2 nARNXX2Rh/ELfwugxw+DSw8RDVT+PFCMgM9EwULeAAff8enB+czI+zCukNkQ8sBcIo+O 4/csqvhwLgp3tseZcEftWfZVdeHoLANWSaMaSST073BXz9J1jlikwfDBJuh2l2hHKX44 VG+g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783362114; x=1783966914; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=9A5m7vFiwsY5pBCG1/DsaDpjFT6tRBps/TnJxETkseY=; b=FXvmcU7499zdDdM3M/iHbETDXaeK89N1+tC8cwqMfNiKQiM/DtgHPGfmjz5Jq39BPP 9uQ+FvSd/0X2p2E/zTQseNtSRUwT4ID7lbHxNm/0BAUzjQZFFKqxCLpMFJfeDrC8o1C3 RC/Tnd64YvqcBGuc0bRbRx7EUdhVNT5agN+zR5QL9RWBK7iLNl84RSiPfN3aOaKB5LzJ 8IJUX2EMpZgppGRyj0VCx2xrK8QCKsJtGcIgm/MU49gIBg/dYzezx1G35GsO3Vt9jzab SYfaKKzLukIJujc40zThuYLWMfsDdYxUqAeJhdNp+fRAP91/T90Pw1qY45daQ9k1XtGN fVPw== X-Forwarded-Encrypted: i=1; AHgh+RrQCO8oAGSm5gXtP96/d9wweyyEMmFoHsr5HDJg0cfns0v7mg/tiVFptJ92yhdusViUaB7FDkqXAka3@lists.postgresql.org X-Gm-Message-State: AOJu0YxhNjMRZA6/etSiXjwDnA3EIWa391rnSUmzm17utz57v5S9tx8w Xo/pq545VGI0qawO7jXpV7p3C8LqMV6iUKqLX99oPuuBxVIEdhGqlcJBAI7JWbllyVmpTRFPeCJ jZWs6+jfxmrDFXb17zYTXC3EbzBFV4Qg= X-Gm-Gg: AfdE7cmpdGE9hfidINNePALgrJsH7k0gr1MfM9YozdR5KFwAlvgZXXnMoIKN8+xErtZ u1hb1k/f0fXqu8njWjN6thvcbhSGkJRjYhPRBcRnUsCwJGN3eZKqAcfQtJkgkw04vsC+HNoHjMA SR4o4RsODp5xBy6Uq/ZiOWQ+dwlAWmE85E3IO2opQ5KWJs18xA8/DHXIW/DV1bpH9kZaBcq5uXe ouAHM1h0S1aJBvS9PibXjDD5nn7wiCnjnr+LuUqN59KE/h8Fi1DW88QVlah85XSzmhzlwrVH3Jq /+0bpypV1UsDzNct1eDF6DFXq8pEs+MEP4r1wKNwOrwFeWpQkxgLgRK9kC2yUx0d2aJKrA== X-Received: by 2002:ac2:596a:0:b0:5ad:229e:385e with SMTP id 2adb3069b0e04-5b007c69ea6mr290403e87.44.1783362113893; Mon, 06 Jul 2026 11:21:53 -0700 (PDT) MIME-Version: 1.0 References: <19519-fe02d8ff679d834d@postgresql.org> In-Reply-To: From: Matthias van de Meent Date: Mon, 6 Jul 2026 20:21:41 +0200 X-Gm-Features: AVVi8Ce0r-JlnmVbdocENQ5qOhxBEmj7ur_c0-nWF-aKbQMnVkIEPk5uRIRRieo Message-ID: Subject: Re: BUG #19519: REPACK can fail due to missing chunk for toast value To: Michael Paquier Cc: Imran Zaheer , Srinath Reddy Sadipiralla , exclusion@gmail.com, pgsql-bugs@lists.postgresql.org, Konstantin Knizhnik Content-Type: text/plain; charset="UTF-8" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Mon, 6 Jul 2026 at 18:40, Michael Paquier wrote: > > On Fri, Jul 03, 2026 at 11:45:42AM +0500, Imran Zaheer wrote: > > Can we safely use the same fix in the index build path too? Can we use > > GetOldestNonRemovableTransactionIdforRewrite or something similar > > here? Normal serial index builds use SnapShotAny and concurrent index > > builds use MVCC, but the bug only exists in the serial index build > > path. > > Yeah, I think that we should be able to safely reuse your ForRewrite() > API for the index build case (SnapshotAny part) as well to make the > horizon computations more conservative, exclusing one own XID as we > do in the lazy VACUUM case. > > > Other than that, after applying your patch, the bug was not > > reproducible with either this repro or the other report's repro [1] in > > the rewrite path. However, the create index bug is still there. You > > can use the following repro as mentioned in the thread [1]. > > The assymetry between the global xmin computation and the per-database > horizon is what's killing us here. All these patterns are complicated > enough that they warrant some tests, even if it is necessary to have > multiple databases to trigger the buggy horizon computations. TAP > tests would be an obvious choice for that. Now I have a set of tricks > in my sleeves to make an isolation test fully deterministic: > - dblink() that opens a transaction to a different database than the > one of the isolation regression database. > - Rename of a TOAST table using allow_system_table_mods, for a VACUUM. > > The second one is one of the dirtiest tricks I've done in the past for > a REINDEX CONCURRENTLY test with TOAST. Quite useful. If the backend > side change is reverted, the tests fail. > > The attached includes both test suites for all four cases reported > (REPACK, VACUUM, CLUSTER, non-MVCC index builds). We'll need to > remove one of them, just keeping both posted as I am not sure if the > isolation tests would be entirely stable in the buildfarm.. > > I still need to dive more into the code, but for now this bit stands > out: > vacuum_get_cutoffs(OldHeap, ¶ms, &cutoffs); > > + /* > + * vacuum_get_cutoffs() folds our own backend's xmin into OldestXmin. For > + * a rewrite that is too conservative: the snapshot we hold exists only to > + * evaluate index expressions against other relations, not to read > + * OldHeap's historical rows. If our xmin is held back by a transaction > + * that cannot even see OldHeap (e.g. one in another database), we would > + * preserve a recently-dead tuple whose TOAST chunks a concurrent or prior > + * lazy vacuum was free to remove, and then fail with "missing chunk" while > + * copying it. Recompute OldestXmin ignoring our own backend so it matches > + * the horizon lazy vacuum uses. This can only move OldestXmin forward, so > + * the freeze cutoffs derived above remain valid. > + */ > + cutoffs.OldestXmin = GetOldestNonRemovableTransactionIdForRewrite(OldHeap); > > In copy_table_data() (for the data copy with repack, cluster, vacuum > full), I think that this is incorrect. For one, this breaks the > FreezeLimit which should always be older than OldestXmin, and this > patch enforces a new recomputation of OldestXmin ignoring most of the > internals of vacuum_get_cutoffs(). That's brittle, to say the least > if we change the way the vacuum cutoffs are calculated in the future. > > Thoughts and comments from others are welcome for now. My day is > almost out, at least I got all these scenarios working some tests. I don't think this approach actually fixes the underlying issue: As I understand it; the problem is this: 1. OldestNonRemovableTransactionId() is based on the xmin state across backends. It may increase at any point in time, could even decrease in certain circumstances[0], and can be in an inconsistent state across backends. 2. Data is only protected against concurrent cleanup while it's visible to a MVCC snapshot in some backend, and this is advertised through the xmin of the backend holding the snapshot being <= the xmin of the snapshot. This relation-level cleanup horizon is determined with GlobalVisTestFor(), which uses the same data source as OldestNonRemovableTransactionId(). 3. All relevant cases of REPACK and CREATE INDEX use SnapshotAny + a cached output of OldestNonRemovableTransactionId() The issue here is that CREATE INDEX and REPACK (CI/R) can start with an OldestXmin of (say) 100, then another backend that held back that OldestXmin stops holding back the xmin (allowing the output of a new invocation of OldestNonRemovableTransactionId() to return e.g. 200), after which the cleanup starts for tuples deleted with transaction ID < 200, rather than the <100 expected by INDEX/REPACK. Because both CI and R use SnapshotAny, and stored the result of OldestNonRemovableTransactionId() from the start of their command processing, they'll consider any still-present tuple >= 100 to be visible for their scan. However, every other backend has stopped considering these tuples visible for their MVCC, scans and therefore may have started cleaning up the tuple data. For main relation's heap pages, this cleanup isn't (much) of an issue, because the fully dead and removed tuples would have to be cleaned up later anyway and are not interesting for the scan, but the related toast tuples can also be cleaned up without first having to wait for the main tuple to be cleaned up, which is exactly what this issue shows: TOAST expects to be used in an MVCC context, and thus errors when it fails to find the tuples that were reclaimed. It's a de-sync between what this backend and what other backends expect. So, in effect, this is an issue that's quite similar to the CIC/RIC bug of PG14<14.4, except that 1.) in this case it's SnapshotAny instead of a registered-but-invisible MVCC snapshot; 2.) the issue in this case only happens when toast pointers were cleaned up and then are dereferenced, rather than HOT chains updated and reclaimed since the scan started; and finally 3.) this issue dates back a very long time, likely since TOAST, but defininitely since PG14 introduced the GlobalVisTestFor apis. So I think the only way to fix this is either 1.) we update every maintenance process that makes use of SnapshotAny and could access TOAST tables to handle errors caused by missing TOAST data when the base relation's tuple is RECENTLY_DEAD, and treat those as "tuple cleanup has already started, we just weren't aware of it yet, so ignore this tuple" (as I mentioned in [1]), or 2.) we set the xmin of the CI/R backend to the current OldestXmin for the relation(s) we're currently processing, and so block all cleanup of the relevant (toast) data for the duration of this statement or whenever we check for a newer xmin for the relation (whilst ignoring our current one). Kind regards, Matthias van de Meent Databricks (https://www.databricks.com) [0]: "BUG #17257: (auto)vacuum hangs within lazy_scan_prune()" https://www.postgresql.org/message-id/flat/CAH2-WznTo_nkbTAoBO2mCR5TzWBj8fEa4%252Bk-K3x9YUYZ0AKVUQ%2540mail.gmail.com [1]: "Re: VACUUM FULL or CREATE INDEX fails with error: missing chunk number 0 for toast value XXX" at -hackers: https://www.postgresql.org/message-id/CAEze2WgRUfoR%3DtSQQm8zexVNeBbEYi9ZSPztrK63ObyfPxKpBw%40mail.gmail.com