From 84ec75dd4c81dc51db11b1a093deae6b5330036d Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Sun, 25 Oct 2026 18:10:10 +0100
Subject: [PATCH v1 5/9] Use a proper hash function for FPI history

The history of recent FPIs is a set-associative table, with each block
mapped to one of 16 groups using a trivial hash function

    (blkno + 31 * spcOid + 83 * dbOid + 127 * relNumber) % 16

The number of groups is a power of two, so only the lowest 4 bits of
each input affect the result.  And because 31 and 127 are -1 and 83 is
3 modulo 16, the group is (blkno - spcOid + 3 * dbOid - relNumber) % 16.
For blocks of a given relation, the group is determined simply by the
lowest 4 bits of the block number.

That's not a particularly great hash function, obviously. If blocks are
modified with a power-of-2 stride, only some of the groups would get
used. With a stride of 16, all blocks map to the same group, and only
the last 16 FPIs would be remembered.

We could probably fix this by picking different coefficients in the
formula, and/or maybe combine the values in a different way. But it's
better (and ultimately cheaper, considering the consequences of poor
caching) to use a proven hash function, combined with hash_combine().
---
 src/backend/access/transam/xlogprefetcher.c | 25 ++++++++++++---------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/transam/xlogprefetcher.c b/src/backend/access/transam/xlogprefetcher.c
index 86478c01708..8d8d1a1ed2b 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"
@@ -70,15 +71,6 @@
 #define XLOGPREFETCHER_FPI_HISTORY_SIZE	\
 	(XLOGPREFETCHER_FPI_GROUPS * XLOGPREFETCHER_FPI_PER_GROUP)
 
-/*
- * Trivial hash function mapping block to a group in the hash table, using
- * small prime numbers.
- */
-#define XLOGPREFETCHER_FPI_HASH(blkno, rlocator)	\
-	((blkno) + 31 * (rlocator).spcOid + 83 * (rlocator).dbOid + 127 * (rlocator).relNumber)
-#define XLOGPREFETCHER_FPI_GROUP(blkno, rlocator)	\
-	(XLOGPREFETCHER_FPI_HASH((blkno), (rlocator)) % XLOGPREFETCHER_FPI_GROUPS)
-
 /*
  * When maintenance_io_concurrency is not saturated, we're prepared to look
  * ahead up to N times that number of block references.
@@ -1029,12 +1021,25 @@ XLogPrefetcherIsFiltered(XLogPrefetcher *prefetcher, RelFileLocator rlocator,
 /*
  * Return the group of slots in the history of recent FPIs, that the given
  * block maps to.
+ *
+ * The hash has to depend on all bits of the block number and the relation,
+ * otherwise blocks modified with a regular stride (or blocks of relations
+ * with some relfilenumbers) would map to only some of the groups, and the
+ * history would forget FPIs while other groups have plenty of space.
  */
 static inline XLogPrefetcherFPI *
 XLogPrefetcherFPIGroup(XLogPrefetcher *prefetcher, RelFileLocator rlocator,
 					   BlockNumber blkno)
 {
-	int			group = XLOGPREFETCHER_FPI_GROUP(blkno, rlocator);
+	uint32		hash;
+	uint32		group;
+
+	hash = murmurhash32(blkno);
+	hash = hash_combine(hash, murmurhash32(rlocator.relNumber));
+	hash = hash_combine(hash, murmurhash32(rlocator.dbOid));
+	hash = hash_combine(hash, murmurhash32(rlocator.spcOid));
+
+	group = hash % XLOGPREFETCHER_FPI_GROUPS;
 
 	return &prefetcher->recent_fpi[group * XLOGPREFETCHER_FPI_PER_GROUP];
 }
-- 
2.55.0

