From: Bharath Rupireddy Date: Sat, 24 Oct 2026 15:37:18 +0000 Subject: [PATCH v1] Use hash table for the recent-blocks window in recovery prefetch. Blah blah... --- src/backend/access/transam/xlogprefetcher.c | 109 +++++++++++++++++++++------- 1 file changed, 83 insertions(+), 26 deletions(-) diff --git a/src/backend/access/transam/xlogprefetcher.c b/src/backend/access/transam/xlogprefetcher.c index dff57642ab4..a27266869c3 100644 --- a/src/backend/access/transam/xlogprefetcher.c +++ b/src/backend/access/transam/xlogprefetcher.c @@ -32,6 +32,7 @@ #include "catalog/pg_control.h" #include "catalog/storage_xlog.h" #include "commands/dbcommands_xlog.h" +#include "common/hashfn.h" #include "funcapi.h" #include "miscadmin.h" #include "port/atomics.h" @@ -53,10 +54,10 @@ #define XLOGPREFETCHER_STATS_DISTANCE BLCKSZ /* - * To detect repeated access to the same block and skip useless extra system - * calls, we remember a small window of recently prefetched blocks. + * Size of the direct-mapped table of recently seen blocks. A collision + * between live entries only costs a repeated prefetch. */ -#define XLOGPREFETCHER_SEQ_WINDOW_SIZE 4 +#define XLOGPREFETCHER_RECENT_BLOCKS 4096 /* * When maintenance_io_concurrency is not saturated, we're prepared to look @@ -120,6 +121,14 @@ typedef struct LsnReadQueue } queue[FLEXIBLE_ARRAY_MEMBER]; } LsnReadQueue; +/* A recently seen block, and the LSN of the record that referenced it. */ +typedef struct XLogPrefetcherRecentBlock +{ + RelFileLocator rlocator; + BlockNumber blkno; + XLogRecPtr lsn; +} XLogPrefetcherRecentBlock; + /* * A prefetcher. This is a mechanism that wraps an XLogReader, prefetching * blocks that will be soon be referenced, to try to avoid IO stalls. @@ -139,9 +148,7 @@ struct XLogPrefetcher dlist_head filter_queue; /* Book-keeping to avoid repeat prefetches. */ - RelFileLocator recent_rlocator[XLOGPREFETCHER_SEQ_WINDOW_SIZE]; - BlockNumber recent_block[XLOGPREFETCHER_SEQ_WINDOW_SIZE]; - int recent_idx; + XLogPrefetcherRecentBlock recent_blocks[XLOGPREFETCHER_RECENT_BLOCKS]; /* Book-keeping to disable prefetching temporarily. */ XLogRecPtr no_readahead_until; @@ -196,6 +203,14 @@ static inline bool XLogPrefetcherIsFiltered(XLogPrefetcher *prefetcher, BlockNumber blockno); static inline void XLogPrefetcherCompleteFilters(XLogPrefetcher *prefetcher, XLogRecPtr replaying_lsn); +static inline void XLogPrefetcherAddRecent(XLogPrefetcher *prefetcher, + RelFileLocator rlocator, + BlockNumber blockno, + XLogRecPtr lsn); +static inline bool XLogPrefetcherIsRecent(XLogPrefetcher *prefetcher, + RelFileLocator rlocator, + BlockNumber blockno, + XLogRecPtr replaying_lsn); static LsnReadQueueNextStatus XLogPrefetcherNextBlock(uintptr_t pgsr_private, XLogRecPtr *lsn); @@ -672,6 +687,25 @@ XLogPrefetcherNextBlock(uintptr_t pgsr_private, XLogRecPtr *lsn) return LRQ_NEXT_NO_IO; } + /* + * There is no point in repeatedly prefetching the same block. + * This comes before the FPI and WILL_INIT checks below, so that + * the blocks they skip are remembered too. + */ + if (XLogPrefetcherIsRecent(prefetcher, block->rlocator, block->blkno, + replaying_lsn)) + { + /* + * XXX If we also remembered where it was, we could set + * recent_buffer so that recovery could skip smgropen() and a + * buffer table lookup. + */ + XLogPrefetchIncrement(&SharedStats->skip_rep); + return LRQ_NEXT_NO_IO; + } + XLogPrefetcherAddRecent(prefetcher, block->rlocator, block->blkno, + record->lsn); + /* * If there is a full page image attached, we won't be reading the * page, so don't bother trying to prefetch. @@ -696,26 +730,6 @@ XLogPrefetcherNextBlock(uintptr_t pgsr_private, XLogRecPtr *lsn) return LRQ_NEXT_NO_IO; } - /* There is no point in repeatedly prefetching the same block. */ - for (int i = 0; i < XLOGPREFETCHER_SEQ_WINDOW_SIZE; ++i) - { - if (block->blkno == prefetcher->recent_block[i] && - RelFileLocatorEquals(block->rlocator, prefetcher->recent_rlocator[i])) - { - /* - * XXX If we also remembered where it was, we could set - * recent_buffer so that recovery could skip smgropen() - * and a buffer table lookup. - */ - XLogPrefetchIncrement(&SharedStats->skip_rep); - return LRQ_NEXT_NO_IO; - } - } - prefetcher->recent_rlocator[prefetcher->recent_idx] = block->rlocator; - prefetcher->recent_block[prefetcher->recent_idx] = block->blkno; - prefetcher->recent_idx = - (prefetcher->recent_idx + 1) % XLOGPREFETCHER_SEQ_WINDOW_SIZE; - /* * We could try to have a fast path for repeated references to the * same relation (with some scheme to handle invalidations @@ -913,6 +927,49 @@ XLogPrefetcherCompleteFilters(XLogPrefetcher *prefetcher, XLogRecPtr replaying_l } } +/* Find the slot for a given block. */ +static inline XLogPrefetcherRecentBlock * +XLogPrefetcherRecentSlot(XLogPrefetcher *prefetcher, RelFileLocator rlocator, + BlockNumber blockno) +{ + uint32 hash; + + hash = hash_combine(hash_bytes((const unsigned char *) &rlocator, + sizeof(rlocator)), + hash_bytes_uint32(blockno)); + return &prefetcher->recent_blocks[hash % XLOGPREFETCHER_RECENT_BLOCKS]; +} + +/* Remember that the record at 'lsn' references a given block. */ +static inline void +XLogPrefetcherAddRecent(XLogPrefetcher *prefetcher, RelFileLocator rlocator, + BlockNumber blockno, XLogRecPtr lsn) +{ + XLogPrefetcherRecentBlock *recent; + + recent = XLogPrefetcherRecentSlot(prefetcher, rlocator, blockno); + recent->rlocator = rlocator; + recent->blkno = blockno; + recent->lsn = lsn; +} + +/* + * Check if a given block was already referenced by a record that hasn't been + * replayed yet. Stale entries are just left to be overwritten. + */ +static inline bool +XLogPrefetcherIsRecent(XLogPrefetcher *prefetcher, RelFileLocator rlocator, + BlockNumber blockno, XLogRecPtr replaying_lsn) +{ + XLogPrefetcherRecentBlock *recent; + + recent = XLogPrefetcherRecentSlot(prefetcher, rlocator, blockno); + return XLogRecPtrIsValid(recent->lsn) && + recent->lsn >= replaying_lsn && + recent->blkno == blockno && + RelFileLocatorEquals(recent->rlocator, rlocator); +} + /* * Check if a given block should be skipped due to a filter. */ -- 2.43.0