v1-0001-Fix-unexpected-zero-page-in-pgstattuple-for-hash-.patch

application/x-patch

Filename: v1-0001-Fix-unexpected-zero-page-in-pgstattuple-for-hash-.patch
Type: application/x-patch
Part: 0
Message: pgstattuple "unexpected zero page" for gist and hash indexes

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v1-0001
Subject: Fix "unexpected zero page" in pgstattuple for hash and gist indexes
File+
contrib/pgstattuple/pgstattuple.c 9 6
From cdff70751e7b146a73a72842f7be2ada44cfbb73 Mon Sep 17 00:00:00 2001
From: Nitin Motiani <nitinmotiani@google.com>
Date: Fri, 12 Sep 2025 08:49:07 +0000
Subject: [PATCH v1] Fix "unexpected zero page" in pgstattuple for hash and
 gist indexes

* pgstattuple calls check functions for all the pages in hash and
  gist indexes. These calls are made from pgstat_hash_page() and
  pgstat_gist_page(). For zero pages, these call lead to ERRCODE_INDEX_CORRUPTED.

* Since zero pages are normal after a crash, these errors are false alarms.
  pgstat_btree_page() avoids these errors by not running these checks.

* In this patch, we do the same for gist and hash indexes. For hash index,
  ReadBufferExtended() replaces _hash_getbuf_with_strategy() which was
  calling _hash_check_page(). Instead we do the required check for special
  size explicitly for the non-PageIsNew() cases.

* Similarly for gist index, we remove gistcheckpage() and explicitly
  checks the special size for the non-PageIsNew() cases before checking
  if the page is a leaf.

* The comments in the else clauses have been updated accordingly.

Reported-by: Noah Misch <noah@leadboat.com>
---
 contrib/pgstattuple/pgstattuple.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index b5de68b7232..cb0c6c70758 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -458,10 +458,12 @@ pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
 	Buffer		buf;
 	Page		page;
 
-	buf = _hash_getbuf_with_strategy(rel, blkno, HASH_READ, 0, bstrategy);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
+	LockBuffer(buf, HASH_READ);
 	page = BufferGetPage(buf);
 
-	if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HashPageOpaqueData)))
+	if (!PageIsNew(page) &&
+		PageGetSpecialSize(page) == MAXALIGN(sizeof(HashPageOpaqueData)))
 	{
 		HashPageOpaque opaque;
 
@@ -484,7 +486,7 @@ pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
 	}
 	else
 	{
-		/* maybe corrupted */
+		/* maybe corrupted or a zero page */
 	}
 
 	_hash_relbuf(rel, buf);
@@ -502,17 +504,18 @@ pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
 
 	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
 	LockBuffer(buf, GIST_SHARE);
-	gistcheckpage(rel, buf);
 	page = BufferGetPage(buf);
 
-	if (GistPageIsLeaf(page))
+	if (!PageIsNew(page) &&
+		PageGetSpecialSize(page) == MAXALIGN(sizeof(GISTPageOpaqueData)) &&
+		GistPageIsLeaf(page))
 	{
 		pgstat_index_page(stat, page, FirstOffsetNumber,
 						  PageGetMaxOffsetNumber(page));
 	}
 	else
 	{
-		/* root or node */
+		/* root or node or zero page or corrupted */
 	}
 
 	UnlockReleaseBuffer(buf);
-- 
2.51.0.384.g4c02a37b29-goog