Fix amcheck's handling of half-dead B-tree pages
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Tue, 2 Dec 2025 19:11:15 +0000 (21:11 +0200)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Tue, 2 Dec 2025 19:11:15 +0000 (21:11 +0200)
amcheck incorrectly reported the following error if there were any
half-dead pages in the index:

ERROR:  mismatch between parent key and child high key in index
"amchecktest_id_idx"

It's expected that a half-dead page does not have a downlink in the
parent level, so skip the test.

Reported-by: Konstantin Knizhnik <knizhnik@garret.ru>
Reviewed-by: Peter Geoghegan <pg@bowt.ie>
Reviewed-by: Mihail Nikalayeu <mihailnikalayeu@gmail.com>
Discussion: https://www.postgresql.org/message-id/33e39552-6a2a-46f3-8b34-3f9f8004451f@garret.ru
Backpatch-through: 14

contrib/amcheck/verify_nbtree.c
src/test/modules/nbtree/expected/nbtree_half_dead_pages.out
src/test/modules/nbtree/sql/nbtree_half_dead_pages.sql

index f26c20b59aa3afde677f1c7f58262927b3d1c134..75751e2a1e90da762e6a81d852b8873e258405cd 100644 (file)
@@ -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;
index 8fd472f8df2b9fc19bc6ab672f44dd8c03f07c2b..e94f016696d980be9a931f104900403ad037d100 100644 (file)
@@ -11,6 +11,7 @@
 -- This uses injection points to interrupt some page deletions
 set client_min_messages TO 'warning';
 create extension if not exists injection_points;
+create extension if not exists amcheck;
 reset client_min_messages;
 -- Make all injection points local to this process, for concurrency.
 SELECT injection_points_set_local();
@@ -57,6 +58,13 @@ select * from nbtree_half_dead_pages where id > 99998 and id < 120002;
  120001
 (4 rows)
 
+-- Also check the index with amcheck
+select bt_index_parent_check('nbtree_half_dead_pages_id_idx'::regclass, true, true);
+ bt_index_parent_check 
+-----------------------
+(1 row)
+
 -- Finish the deletion and re-check
 vacuum nbtree_half_dead_pages;
 NOTICE:  notice triggered for injection point nbtree-finish-half-dead-page-vacuum
@@ -69,3 +77,9 @@ select * from nbtree_half_dead_pages where id > 99998 and id < 120002;
  120001
 (4 rows)
 
+select bt_index_parent_check('nbtree_half_dead_pages_id_idx'::regclass, true, true);
+ bt_index_parent_check 
+-----------------------
+(1 row)
+
index d4b9a3f824d0910e097b8fede5e192ac5d55df07..fd279b87e0e7ddeb9d67031d07f7bde6bd5e78c4 100644 (file)
@@ -12,6 +12,7 @@
 -- This uses injection points to interrupt some page deletions
 set client_min_messages TO 'warning';
 create extension if not exists injection_points;
+create extension if not exists amcheck;
 reset client_min_messages;
 
 -- Make all injection points local to this process, for concurrency.
@@ -38,6 +39,10 @@ SELECT injection_points_detach('nbtree-leave-page-half-dead');
 
 select * from nbtree_half_dead_pages where id > 99998 and id < 120002;
 
+-- Also check the index with amcheck
+select bt_index_parent_check('nbtree_half_dead_pages_id_idx'::regclass, true, true);
+
 -- Finish the deletion and re-check
 vacuum nbtree_half_dead_pages;
 select * from nbtree_half_dead_pages where id > 99998 and id < 120002;
+select bt_index_parent_check('nbtree_half_dead_pages_id_idx'::regclass, true, true);