Re: Bug in amcheck?
Heikki Linnakangas <hlinnaka@iki.fi>
From: Heikki Linnakangas <hlinnaka@iki.fi>
To: Peter Geoghegan <pg@bowt.ie>
Cc: Mihail Nikalayeu <mihailnikalayeu@gmail.com>,
Konstantin Knizhnik <knizhnik@garret.ru>, akorotkov@postgresql.org,
aekorotkov@gmail.com, PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>
Date: 2025-11-26T08:20:40Z
Lists: pgsql-hackers
Commits
Same data as JSON:
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Fix amcheck's handling of half-dead B-tree pages
- fbb4b607832d 14.21 landed
- 7792bdc45860 15.16 landed
- 1829016268c3 16.12 landed
- e8ae594458a3 17.8 landed
- 19e786727c4f 18.2 landed
- cbe04e5d729f 19 (unreleased) landed
-
Fix amcheck's handling of incomplete root splits in B-tree
- a4cb21ea9cd7 14.21 landed
- 721b58fbec5f 15.16 landed
- f2a6df9fd56d 16.12 landed
- 5a2d1df00718 17.8 landed
- 50c63ebb05fc 18.2 landed
- 6c05ef5729c0 19 (unreleased) landed
-
Add a test for half-dead pages in B-tree indexes
- c085aab27819 19 (unreleased) landed
-
Add a test for incomplete splits in B-tree indexes
- 1e4e5783e7d7 19 (unreleased) landed
-
Improve checking of child pages in contrib/amcheck.
- d114cc538715 13.0 cited
On 25/11/2025 22:51, Peter Geoghegan wrote:
> On Tue, Nov 25, 2025 at 3:03 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
>> To fix this, I guess we need to teach bt_index_parent_check() about
>> half-dead pages. Anyone volunteer to write that patch?
>
> It's not like bt_index_parent_check doesn't generally know about them.
> For example, bt_downlink_missing_check goes to great lengths to
> distinguish between legitimate "missing" downlinks caused by an
> interrupted page deletion, and real missing downlinks caused by
> corruption.
>
> The problem we're seeing here seems likely limited to code added by
> commit d114cc53, which enhanced bt_index_parent_check by adding the
> new bt_child_highkey_check check. bt_child_highkey_check actually
> reuses bt_downlink_missing_check (which deals with half-dead pages
> correctly), but still isn't careful enough about half-dead pages. This
> is kind of surprising, since it *does* account for incomplete splits,
> which are similar.
+1. It took me a moment to understand bt_downlink_missing_check(). The
comments there talk about interrupted page deletions and incomplete
splits, but it's actually never called for half-dead pages. The caller
checks !P_IGNORE(opaque), and P_IGNORE means BTP_DELETED | BTP_HALF_DEAD.
I don't think BTP_DELETED pages should be reachable here at all. So
perhaps we should specifically check BTP_DELETED and give an ERROR on
those. And for clarity, perhaps move the check for BTP_HALF_DEAD into
bt_downlink_missing_check(), alongside the incomplete-split check, so
that both cases would be checked at the same place. Just for clarity,
it's not wrong as it is.
> In short, I think that we need to track something like
> BtreeCheckState.previncompletesplit, but for half-dead pages. And then
> actually use that within bt_child_highkey_check, to avoid spurious
> false-positive reports of corruption.
I think it's even simpler than that, and we can just do this:
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -2268,7 +2268,7 @@ bt_child_highkey_check(BtreeCheckState *state,
* If we visit page with high key, check that it is equal to the
* target key next to corresponding downlink.
*/
- if (!rightsplit && !P_RIGHTMOST(opaque))
+ if (!rightsplit && !P_RIGHTMOST(opaque) && !P_ISHALFDEAD(opaque))
{
BTPageOpaque topaque;
IndexTuple highkey;
Both BTP_HALF_DEAD and BTP_INCOMPLETE_SPLIT indicate that a downlink is
missing, but they work slightly differently. If a page is marked as
BTP_INCOMPLETE_SPLIT, it means that its right sibling has no downlink,
but if a page is marked as BTP_HALF_DEAD, it means that the page itself
has no downlink.
- Heikki