On Fri, Aug 28, 2026 at 4:06 PM Andrew Dunstan <[email protected]> wrote: > > Hi, > > Table AMs that store heap-format tuples on pages managed by another WAL > scheme (generic WAL, for example) can't tolerate > HeapTupleSatisfiesVisibility() and friends opportunistically writing > hint bits to the buffer: an unlogged write between two WAL operations > invalidates the before-image a later generic-WAL delta is computed > against, and standby replay ends up with a corrupt page. > > The attached patch adds HeapTupleSatisfiesVisibilityNoHints() and > HeapTupleSatisfiesUpdateNoHints(), thin wrappers that return the same > verdict without touching the page. The sentinel that makes this work > (NoHintBitsBuffer) stays private to heapam_visibility.c. > > Since NoHintBitsBuffer is negative, it passes BufferIsLocal(), so any > code reached from the wrappers that uses the buffer for something > besides hint bits needs to know about it. The one such case is > SNAPSHOT_HISTORIC_MVCC, which needs the buffer to recover the tuple's > relfilelocator; HeapTupleSatisfiesVisibilityNoHints() rejects that > snapshot type outright rather than let the sentinel reach it. In > practice this shouldn't fire: that snapshot type is only used for > logical decoding's catalog lookups, always against pg_catalog, which > is always heap. > > (Thanks to Euler Taveira, who helped me with this, particularly with > criticizing an earlier and more invasive proposal.) > > > cheers > > > andrew > > -- > Andrew Dunstan > EDB: https://www.enterprisedb.com
Hi Andrew, Thanks for the patch. Took me a while to figure out what it's actually doing, so let me restate it and you can tell me if I'm off. The setup is a table AM that stores heap tuples but WAL-logs its pages through generic WAL instead of heapam. Hint bits are unlogged, so a normal heap standby doesn't really get them anyway -- they only show up on the standby when something happens to log a full-page image. Generic WAL is different though: it ships the page as a delta. So say this happens on the primary: 1. Log an FPI for page 2. A visibility check sets a hint bit (unlogged) 3. Redo changes the page through generic WAL The delta from step 3 is computed against the current page, which already has the hint bit from step 2. The standby's copy doesn't have that hint bit, and the delta doesn't carry it, so the standby never picks it up. My question is whether this actually corrupts anything. It looks to me like we just drop the hint-bit update on the standby, and hint bits are unlogged by design anyway, so that part seems fine -- visibility is still correct since it comes from clog. Where it does bite is wal_consistency_checking: generic_mask() doesn't mask hint bits (it can't, the page is opaque to it), so the check trips on the difference. If that's really the issue, maybe the commit message should say so -- "corrupt page" sounds scarier than what's actually happening. Couple of questions on the patch itself: 1. Any reason not to just use InvalidBuffer here? If we use InvalidBuffer, then we do not need to check, right? 2. The "historic MVCC snapshots require a buffer" message threw me off a bit -- could we go with "historic MVCC snapshots are not supported" instead? I also create a commit feast patch for this thread: https://commitfest.postgresql.org/patch/7215/ Thanks, Shihao
