Allow tuple visibility checks without hint-bit, maintenance

Started by Andrew Dunstan1 day ago4 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253595
psql -h localhost -U postgres

Built from patchset v1 (message #1), August 29, 2026 at 08:19 PM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253595_1 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253595_1 && git checkout t253595_1

Patchset v1 (message #1) is on t253595_1

Jump to latest
#1Andrew Dunstan
andrew@dunslane.net

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

Attachments:

t253595_1
0001-Allow-tuple-visibility-checks-without-hint-bit-maint.patchtext/x-patch; charset=UTF-8; name=0001-Allow-tuple-visibility-checks-without-hint-bit-maint.patchDownload+70-1
#2shihao zhong
zhong950419@gmail.com
In reply to: Andrew Dunstan (#1)
Re: Allow tuple visibility checks without hint-bit, maintenance

On Fri, Aug 28, 2026 at 4:06 PM Andrew Dunstan <andrew@dunslane.net> 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

#3shihao zhong
zhong950419@gmail.com
In reply to: shihao zhong (#2)
Re: Allow tuple visibility checks without hint-bit, maintenance

On Sat, Aug 29, 2026 at 12:43 AM shihao zhong <zhong950419@gmail.com> wrote:

I also create a commit feast patch for this thread:
https://commitfest.postgresql.org/patch/7215/

I didn't notice https://commitfest.postgresql.org/patch/7212/ at
first, so I've withdrawn
my duplicate entry https://commitfest.postgresql.org/patch/7215/ in favor of it.

#4Andres Freund
andres@anarazel.de
In reply to: Andrew Dunstan (#1)
Re: Allow tuple visibility checks without hint-bit, maintenance

Hi,

On 2026-08-28 16:06:46 -0400, Andrew Dunstan wrote:

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.

Why do we care about such an AM? What's the point of all of this? I guess I
could try searching the archives to look for other threads, but I shouldn't
need to, to be able to judge what the point of this patch is.

Increasing the code size of performance critical code can decrease performance
- in almost all the cases the new branches won't be able to be optimized out,
because the buffer value is not known at compile time that the values.

Also, having code that has no users in tree also makes it more likely to
bit-rot.

Greetings,

Andres Freund