From cb1793f0f422ea20cf766c94eb39ba04db23d9fa Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Sun, 25 Oct 2026 18:00:07 +0100
Subject: [PATCH v1 7/9] Avoid duplicate entries in FPI history

Multiple FPIs for the same block within the look-ahead distance are
rare, but can happen e.g. when a checkpoint starts between modifications
of a block. Each duplicate wastes a slot, and may force out an entry for
a different block that's still needed.

When adding an FPI to the history, update the existing entry for the
block, if there is one. XLogPrefetcherAddFPI() already scans the group
when looking for the oldest entry, so this does not make it any more
expensive. The empty slots are always at the end anyway.

This also means XLogPrefetcherHasFPI() can be a bit smarter:

* It can stop at the first (and only) entry for the block. Previously it
  kept looking after finding an entry for an already-replayed FPI.

* It can stop at the first empty slot. Those are always at the end of a
  group, because new entries are added to the first empty slot, and
  slots only become empty when the whole history is reset. (In this
  context "empty" means an entry with invalid LSN.)
---
 src/backend/access/transam/xlogprefetcher.c | 33 +++++++++++++--------
 1 file changed, 20 insertions(+), 13 deletions(-)

diff --git a/src/backend/access/transam/xlogprefetcher.c b/src/backend/access/transam/xlogprefetcher.c
index 3c5f7644491..1f77e20c08b 100644
--- a/src/backend/access/transam/xlogprefetcher.c
+++ b/src/backend/access/transam/xlogprefetcher.c
@@ -1053,14 +1053,11 @@ XLogPrefetcherFPIGroup(XLogPrefetcher *prefetcher, RelFileLocator rlocator,
  * Remember that the given block is restored from an FPI in the WAL record
  * with the given LSN.
  *
- * We should not really see a FPI for the same block very often, not more
- * than once per checkpoint cycle.  So don't bother checking whether the
- * block already is in the history.  If we end up with a duplicate FPI, it
- * means the system is almost idle.
- *
- * XXX Could we see multiple FPIs for the same block within the same
- * checkpoint interval? E.g. during a backup, etc? Then maybe we should
- * try to deduplicate the entries a bit more aggressively.
+ * If the block already is in the history, the existing entry is updated, so
+ * that each block occupies at most one slot. It's not common to see multiple
+ * FPIs for the same block within the look-ahead distance, but it can happen,
+ * e.g. when a checkpoint starts between two modifications of the block, or
+ * with wal_consistency_checking (which adds an image to block references).
  */
 static inline void
 XLogPrefetcherAddFPI(XLogPrefetcher *prefetcher, RelFileLocator rlocator,
@@ -1071,7 +1068,11 @@ XLogPrefetcherAddFPI(XLogPrefetcher *prefetcher, RelFileLocator rlocator,
 	XLogPrefetcherFPI *slot = &slots[0];
 
 	/*
-	 * Add the FPI to the first empty slot in the group, or to the oldest one.
+	 * Use the slot with the same block, or the first empty slot, or the
+	 * oldest slot in the group.  Empty slots are always at the end of the
+	 * group, because we use the first empty slot, and slots only become empty
+	 * when the whole history is reset.  So if there is an entry for the same
+	 * block, we find it before the first empty slot.
 	 *
 	 * Entries for FPIs that have been replayed already are ignored by
 	 * XLogPrefetcherHasFPI(), so they are effectively empty.  They're also
@@ -1079,7 +1080,9 @@ XLogPrefetcherAddFPI(XLogPrefetcher *prefetcher, RelFileLocator rlocator,
 	 */
 	for (int i = 0; i < XLOGPREFETCHER_FPI_PER_GROUP; i++)
 	{
-		if (slots[i].lsn == InvalidXLogRecPtr)
+		if (slots[i].lsn == InvalidXLogRecPtr ||
+			(slots[i].blkno == blkno &&
+			 RelFileLocatorEquals(slots[i].rlocator, rlocator)))
 		{
 			slot = &slots[i];
 			break;
@@ -1114,10 +1117,14 @@ XLogPrefetcherHasFPI(XLogPrefetcher *prefetcher, RelFileLocator rlocator,
 
 	for (int i = 0; i < XLOGPREFETCHER_FPI_PER_GROUP; i++)
 	{
-		if (slots[i].lsn > replaying_lsn &&
-			slots[i].blkno == blkno &&
+		/* Empty slots are always at the end of the group. */
+		if (slots[i].lsn == InvalidXLogRecPtr)
+			break;
+
+		/* There's at most one entry for each block. */
+		if (slots[i].blkno == blkno &&
 			RelFileLocatorEquals(slots[i].rlocator, rlocator))
-			return true;
+			return (slots[i].lsn > replaying_lsn);
 	}
 
 	return false;
-- 
2.55.0

