From 51b63a491872b0eb5b5678dc9a9014c39583c5c6 Mon Sep 17 00:00:00 2001
From: Gustavo William <gustavo.oliveira@enterprisedb.com>
Date: Tue, 15 Sep 2026 13:15:51 -0400
Subject: [PATCH v06112026 06/11] pg_basebackup: issue posix_fadvise() for more
 efficient cold-cache handling

Earlier sendFile() (now sendRelFile()) stall on synchronous pread() calls,
one block at a time, and backup throughput is bounded by read latency.
Give the kernel hints so that it can read ahead while we process the data
already read.

For full backups, issue POSIX_FADV_SEQUENTIAL on the file, which hints
to the kernel that the file will be read sequentially.  This applies to
both relation files (sendRelFile) and non-relation files (sendFile).

For incremental backups, the blocks we will read are known in advance, so
issue POSIX_FADV_WILLNEED for them before reading. Keep a window of N runs
ahead of the reader: initially N runs are requested, then after each
block read hint another run. A "run" means a single fadvise syscall; it
can include more than one block in case they are contiguous, up to
io_combine_limit blocks. To avoid overwhelming the kernel with too many
fadvise requests, ensure that the number runs does not exceed
maintenance_io_concurrency, or maintenance_io_concurrency * io_combine_limit
in blocks.

Co-authored-by: Jakub Wartak <jakub.wartak@enterprisedb.com>
---
 src/backend/backup/basebackup.c  | 141 +++++++++++++++++++++++++++++++
 src/tools/pgindent/typedefs.list |   1 +
 2 files changed, 142 insertions(+)

diff --git a/src/backend/backup/basebackup.c b/src/backend/backup/basebackup.c
index 3466750822d..e6f4fa403be 100644
--- a/src/backend/backup/basebackup.c
+++ b/src/backend/backup/basebackup.c
@@ -12,6 +12,7 @@
  */
 #include "postgres.h"
 
+#include <fcntl.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <time.h>
@@ -40,6 +41,7 @@
 #include "replication/slot.h"
 #include "replication/walsender.h"
 #include "replication/walsender_private.h"
+#include "storage/bufmgr.h"
 #include "storage/bufpage.h"
 #include "storage/checksum.h"
 #include "storage/dsm_impl.h"
@@ -83,6 +85,35 @@ typedef struct
 	pg_checksum_type manifest_checksum_type;
 } basebackup_options;
 
+#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
+/*
+ * State of incremental prefetching, used by sendRelFile().
+ */
+typedef struct IncrPrefetch
+{
+	/* Is prefetching enabled? */
+	bool		enabled;
+
+	/*
+	 * Number of prefetch runs (fadvise calls) to stay ahead of the current
+	 * block
+	 */
+	unsigned	readahead_window;
+
+	/* Max number of blocks hinted ahead of the current block */
+	unsigned	max_blks_ahead;
+
+	/*
+	 * Max number of blocks to combine into a single prefetch run (fadvise
+	 * call)
+	 */
+	unsigned	max_blks_per_run;
+
+	/* Index of the next block to be prefetched */
+	unsigned	prefetch_idx;
+} IncrPrefetch;
+#endif
+
 #define TAR_NUM_TERMINATION_BLOCKS 2
 
 StaticAssertDecl(TAR_NUM_TERMINATION_BLOCKS * TAR_BLOCK_SIZE <= BLCKSZ,
@@ -112,6 +143,12 @@ static BlockNumber read_relfile_data_into_buffer(bbsink *sink,
 												 BlockNumber start_blk, BlockNumber nblocks,
 												 bool verify_checksum,
 												 int *checksum_failures);
+#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
+static void prefetch_next_incremental_run(int fd,
+										  BlockNumber *incremental_blocks,
+										  unsigned num_incremental_blocks,
+										  IncrPrefetch *prefetch);
+#endif
 static void push_to_sink(bbsink *sink, pg_checksum_context *checksum_ctx,
 						 size_t *bytes_done, void *data, size_t length);
 static bool backup_checksums_verifiable(XLogRecPtr start_lsn);
@@ -1618,6 +1655,14 @@ sendFile(bbsink *sink, const char *readfilename, const char *tarfilename,
 				(errcode_for_file_access(),
 				 errmsg("could not stat file \"%s\": %m", readfilename)));
 
+	/*
+	 * Let the OS know that we are going to read the whole file. It's just a
+	 * hint, but it helps avoid longer synchronous read stalls.
+	 */
+#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_SEQUENTIAL)
+	(void) posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
+#endif
+
 	_tarWriteHeader(sink, tarfilename, NULL, &filestat, false);
 
 	/*
@@ -1731,6 +1776,14 @@ sendRelFile(bbsink *sink, const char *readfilename, const char *tarfilename,
 	pg_checksum_context checksum_ctx;
 	int			ibindex;
 	struct stat filestat = *statbuf;
+#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
+	IncrPrefetch incr_prefetch = {
+		.enabled = maintenance_io_concurrency > 0,
+		.readahead_window = maintenance_io_concurrency,
+		.max_blks_per_run = io_combine_limit,
+		.max_blks_ahead = maintenance_io_concurrency * io_combine_limit,
+	};
+#endif
 
 	Assert(RelFileNumberIsValid(relfilenumber));
 
@@ -1748,6 +1801,38 @@ sendRelFile(bbsink *sink, const char *readfilename, const char *tarfilename,
 				 errmsg("could not open file \"%s\": %m", readfilename)));
 	}
 
+	/*
+	 * On full backups, let the OS know that we are going to read the whole
+	 * file. It's just a hint, but it helps avoid longer synchronous read
+	 * stalls.
+	 */
+#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_SEQUENTIAL)
+	if (incremental_blocks == NULL)
+		(void) posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
+#endif
+
+	/*
+	 * On incremental backups, let the OS know the exact blocks we'll need.
+	 * For that, maintain a bounded window of readahead hints, sized by
+	 * incr_prefetch->readahead_window.  Initially, issue
+	 * incr_prefetch->readahead_window runs of prefetch to widen the window;
+	 * then, after each block read, prefetch another run.  The number of
+	 * blocks prefetched ahead of the reader is capped at
+	 * incr_prefetch->max_blks_ahead to avoid over-fetching.
+	 */
+#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
+	if (incremental_blocks != NULL && incr_prefetch.enabled)
+	{
+		unsigned	initial_prefetch = incr_prefetch.readahead_window;
+
+		while (initial_prefetch-- > 0 &&
+			   incr_prefetch.prefetch_idx < num_incremental_blocks)
+			prefetch_next_incremental_run(fd, incremental_blocks,
+										  num_incremental_blocks,
+										  &incr_prefetch);
+	}
+#endif
+
 	_tarWriteHeader(sink, tarfilename, NULL, &filestat, false);
 
 	/*
@@ -1894,6 +1979,18 @@ sendRelFile(bbsink *sink, const char *readfilename, const char *tarfilename,
 										  verify_checksum,
 										  &checksum_failures);
 
+#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
+		/* Advance the prefetch window by prefetching another run */
+		if (incremental_blocks != NULL &&
+			incr_prefetch.enabled &&
+			incr_prefetch.prefetch_idx < num_incremental_blocks &&
+			incr_prefetch.prefetch_idx - (unsigned) ibindex <
+			incr_prefetch.max_blks_ahead)
+			prefetch_next_incremental_run(fd, incremental_blocks,
+										  num_incremental_blocks,
+										  &incr_prefetch);
+#endif
+
 		/*
 		 * If we hit end-of-file, a concurrent truncation must have occurred.
 		 * That's not an error condition, because WAL replay will fix things
@@ -1960,6 +2057,50 @@ sendRelFile(bbsink *sink, const char *readfilename, const char *tarfilename,
 	return true;
 }
 
+#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
+
+/*
+ * Prefetch the next run of blocks to be read from an incremental file.
+ *
+ * incremental_blocks is the sorted array of block numbers that sendRelFile()
+ * will read, and prefetch->prefetch_idx is the position of the first block not
+ * yet hinted.  A single run can cover multiple blocks if they are adjacent, up
+ * to prefetch->max_blks_per_run.
+ *
+ * A "run" means a single fadvise request, not a single block.
+ *
+ * The hint is advisory only.  Failures are ignored, since the worst outcome
+ * is that we lose the intended readahead and sendRelFile() falls back to
+ * ordinary synchronous reads.
+ */
+static void
+prefetch_next_incremental_run(int fd, BlockNumber *incremental_blocks,
+							  unsigned num_incremental_blocks,
+							  IncrPrefetch *prefetch)
+{
+	BlockNumber start_blk = incremental_blocks[prefetch->prefetch_idx];
+	unsigned	blks_prefetched = 1;
+
+	prefetch->prefetch_idx++;
+
+	/*
+	 * Merge contiguous blocks into a single run, up to max_blks_per_run.
+	 */
+	while (prefetch->prefetch_idx < num_incremental_blocks &&
+		   blks_prefetched < prefetch->max_blks_per_run &&
+		   incremental_blocks[prefetch->prefetch_idx] ==
+		   start_blk + blks_prefetched)
+	{
+		blks_prefetched++;
+		prefetch->prefetch_idx++;
+	}
+
+	(void) posix_fadvise(fd, (off_t) start_blk * BLCKSZ,
+						 (off_t) blks_prefetched * BLCKSZ,
+						 POSIX_FADV_WILLNEED);
+}
+#endif
+
 /*
  * Read some more data from a relation file into the bbsink's buffer,
  * verifying checksums as required.
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 553162c0810..8ad789c767c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1287,6 +1287,7 @@ InProgressEnt
 InProgressIO
 IncludeWal
 InclusionOpaque
+IncrPrefetch
 IncrementVarSublevelsUp_context
 IncrementalBackupInfo
 IncrementalSort
-- 
2.43.0

