From 3ed4a353be6a8b68bf399c9913f475d03116ce1e Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Thu, 29 Oct 2026 21:52:17 -0400 Subject: [PATCH v1 3/4] Delay checkpoint start while allocating hash index buckets _hash_alloc_buckets() WAL-logs the last block of a new splitpoint with log_newpage(), and then extends the file with smgrextend(). Nothing kept a checkpoint from setting its redo pointer between the two steps. Recovery from such a checkpoint does not replay the log_newpage() record, and redo of the later records only extends the file up to the bucket pages that are actually used. If the extension did not make it into the checkpoint, the file ends up shorter than the metapage says. This can happen if the fsync request of smgrextend() arrives after the checkpoint has absorbed its requests, followed by an OS crash. It can also happen with a base backup whose start checkpoint lands in the window, if the index file is copied before the extension. Later, when an overflow page is needed past the end of the file, inserts fail with "access to noncontiguous page in hash index", until the index is rebuilt. Fix by setting DELAY_CHKPT_START around the two steps, as bulk_write.c does for the same reason. We hold the exclusive lock on the metapage here, but a checkpoint does not need that lock before it sets its redo pointer, so that is fine. Backpatch-through: 14 --- src/backend/access/hash/hashpage.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c index a0ab2528167..d7cb7984e64 100644 --- a/src/backend/access/hash/hashpage.c +++ b/src/backend/access/hash/hashpage.c @@ -34,6 +34,7 @@ #include "miscadmin.h" #include "port/pg_bitutils.h" #include "storage/predicate.h" +#include "storage/proc.h" #include "storage/smgr.h" #include "utils/injection_point.h" #include "utils/rel.h" @@ -1025,17 +1026,32 @@ _hash_alloc_buckets(Relation rel, BlockNumber firstblock, uint32 nblocks) ovflopaque->hasho_flag = LH_UNUSED_PAGE; ovflopaque->hasho_page_id = HASHO_PAGE_ID; + /* + * Prevent a checkpoint from starting between the WAL record and the + * extension. Recovery from such a checkpoint would not replay the + * record, while the checkpoint might not fsync the new block, and a base + * backup started by it might be copied without the new block. The file + * would then be shorter than the metapage says. + */ if (RelationNeedsWAL(rel)) + { + Assert((MyProc->delayChkptFlags & DELAY_CHKPT_START) == 0); + MyProc->delayChkptFlags |= DELAY_CHKPT_START; + log_newpage(&rel->rd_locator, MAIN_FORKNUM, lastblock, zerobuf.data, true); + } PageSetChecksum(page, lastblock); smgrextend(RelationGetSmgr(rel), MAIN_FORKNUM, lastblock, zerobuf.data, false); + if (RelationNeedsWAL(rel)) + MyProc->delayChkptFlags &= ~DELAY_CHKPT_START; + return true; } -- 2.37.1 (Apple Git-137.1)