| From: | Peter Geoghegan <pg(at)bowt(dot)ie> |
|---|---|
| To: | PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org> |
| Cc: | Andres Freund <andres(at)anarazel(dot)de>, scott(at)scottray(dot)io |
| Subject: | Snapshot export on a standby corrupts hint bits on subxact overflow |
| Date: | 2026-07-27 02:17:21 |
| Message-ID: | CAH2-WzmHVeYY=pjz9x8DhhxVjXHX0pvoQ-MdiB1Tt6=o2GTiKg@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
I have heard periodic reports that indicate that pg_export_snapshot
can cause a standby to incorrectly set hint bits, which I suspected
was tied to subxact overflow. For example, Francisco Reinolds 2023 bug
report [1] describes symptoms that match what I'd heard elsewhere. I
was reminded of this by a recent bug report from Scott Ray [2] that
also involves snapshot import/export. That turned out to be an
unrelated issue, though it looks legit.
I decided to reinvestigate the problem today, with help from Claude
code. I found a bug that exactly matches the known symptoms. Attached
patch 0001 has a reproducer + draft bug fix. This is likely a bug in
2017 commit 6c2003f8. There's also a second patch 0002 that fixes
another bug found along the way (though that's much less serious than
the one that 0001 deals with).
The test case in 0001 shows a scenario where pg_export_snapshot on a
standby hands out a snapshot that claims no transaction is running,
which is wrong. A backend that imports it writes wrong hint bits,
which are then seen by every other session on that standby --
including sessions that never touched the exported snapshot.
User-visible symptoms include rows reappearing after deletion, rows
vanishing after insertion, and duplicate entries in unique indexes
(all symptoms that I've personally seen in the wild). pg_dump -j can
hit this when run on a replica.
Recall that HeapTupleSatisfiesMVCC treats an XID that the snapshot
says isn't running, and that clog doesn't show as committed, as
aborted. That's how it's possible for a snapshot that shows no running
xacts to corrupt hint bits instead of just giving temporary wrong
answers (of course an FPI is also likely to mask the problem in
practice, I saw that myself when I investigated the problem a few
years back).
0002 is a separate bug of the same general nature.
pg_current_snapshot() copies the active snapshot's xip array, so on a
standby it returns a pg_snapshot claiming nothing is in progress, and
pg_visible_in_snapshot answers "visible" for transactions that are
still running:
primary pg_current_snapshot() = 696:698:696 visible(696) = f
standby pg_current_snapshot() = 696:698: visible(696) = t
This is what we see for transaction 696, which is still running (and
whose deleted row is correctly still visible on both nodes).
Both bug fixes were entirely authored by Claude, so treat them
skeptically. But I'm sure that at least the first one is a real bug.
[1] https://postgr.es/m/17846-1a0e5ce976f4c01a@postgresql.org
[2] https://postgr.es/m/QpAansP4iVg_ttSs9x81PFAptL2sqR3AS06u8Jksm3_bHJvUwQjHOocRajbxBc3iiLdf9ZMC6gtXjZsxdxvOmqp98hLZcZuWyBDuQxS6uZc=@scottray.io
--
Peter Geoghegan
| Attachment | Content-Type | Size |
|---|---|---|
| v1-0002-Read-the-in-progress-set-from-subxip-in-pg_curren.patch | application/x-patch | 3.9 KB |
| v1-0001-Don-t-discard-subxip-when-exporting-a-snapshot-ta.patch | application/x-patch | 15.8 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Richard Guo | 2026-07-27 02:34:56 | Re: Fix missing FORMAT when deparsing JSON_ARRAY(query) |
| Previous Message | Tom Lane | 2026-07-27 02:14:00 | Re: Fix missing FORMAT when deparsing JSON_ARRAY(query) |