Bug in multi-level nbtree page deletion

From: Peter Geoghegan <pg(at)bowt(dot)ie>
To: PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Bug in multi-level nbtree page deletion
Date: 2026-10-17 16:19:29
Message-ID: CAH2-Wznr2Gw5OQE9KMP_Xd92F9mV=gv7qE9Q6taAvfrKqsE5Qg@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Claude Code found a long standing race condition bug in multi-level
page deletion (deletion that removes a subtree including at least one
internal page, in addition to a leaf page). We aren't sufficiently
careful about a concurrent page split in the half-dead leaf page's
left sibling, at the point where we delete an internal target page (an
ancestor of that half-dead page). Such a concurrent split can change
the half-dead page's left sibling link during a window when no lock is
held on any leaf page. When that happens, the WAL record describing
how to change the half-dead leaf page (as part of deleting the target
internal page) will change the half-dead page's left link field to its
pre-split sibling's block number, not to its new left sibling block
number, which is wrong -- wal_consistency_checking fails. This bug
dates all the way back to the page deletion overhaul introduced in
Postgres 9.4.

The bug is probably very unlikely to affect users. Original execution
correctly doesn't change the half-dead page's left sibling link at
all. The reason we need to change its left sibling during recovery is
because its buffer is REGBUF_WILL_INIT for efficiency reasons. So not
only does this bug require a concurrent split at just the right/wrong
time, with a multi-level page deletion (both of which are unlikely in
general): even after that happens, the resulting corruption on
standbys only persists until the half-dead page is marked fully
deleted -- which typically happens immediately after the corruption is
introduced.

Attached v1-0001-* patch fixes the bug. v1-0002-* is the test case
that Claude Code authored to prove the bug exists. I doubt that the
test case is worth committing.

--
Peter Geoghegan

Attachment Content-Type Size
v1-0002-Add-test-for-stale-left-link-logged-by-btree-page.patch application/octet-stream 8.2 KB
v1-0001-Re-read-sibling-links-during-nbtree-page-unlink.patch application/octet-stream 1.6 KB

Browse pgsql-hackers by date

  From Date Subject
Next Message Noah Misch 2026-10-17 16:38:59 Re: Add Dockerfile for OSS Scanner
Previous Message Mihail Nikalayeu 2026-10-17 16:10:00 Re: Resetting snapshots during the first phase of [CREATE |RE]INDEX CONCURRENTLY