From d575b532c110c0e2c0ec7f12c9200e93075583c6 Mon Sep 17 00:00:00 2001 From: Gustavo William Date: Tue, 15 Sep 2026 13:15:51 -0400 Subject: [PATCH v19102026 5/5] pg_basebackup: issue posix_fadvise() for more efficient cold-cache handling sendFile() stalls 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. 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 --- src/backend/backup/basebackup.c | 132 +++++++++++++++++++++++++++++++ src/tools/pgindent/typedefs.list | 1 + 2 files changed, 133 insertions(+) diff --git a/src/backend/backup/basebackup.c b/src/backend/backup/basebackup.c index b06e513f4e0..8d5df94f434 100644 --- a/src/backend/backup/basebackup.c +++ b/src/backend/backup/basebackup.c @@ -12,6 +12,7 @@ */ #include "postgres.h" +#include #include #include #include @@ -38,6 +39,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" @@ -80,6 +82,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 sendFile(). + */ +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, @@ -106,6 +137,12 @@ static off_t read_file_data_into_buffer(bbsink *sink, BlockNumber blkno, 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); @@ -1593,6 +1630,14 @@ sendFile(bbsink *sink, const char *readfilename, const char *tarfilename, bool verify_checksum = false; pg_checksum_context checksum_ctx; int ibindex = 0; +#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 if (pg_checksum_init(&checksum_ctx, manifest->checksum_type) < 0) elog(ERROR, "could not initialize checksum of file \"%s\"", @@ -1608,6 +1653,38 @@ sendFile(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, statbuf, false); /* @@ -1738,6 +1815,17 @@ sendFile(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 (incr_prefetch.enabled && + incr_prefetch.prefetch_idx < num_incremental_blocks && + incr_prefetch.prefetch_idx - ibindex < + incr_prefetch.max_blks_ahead) + prefetch_next_incremental_run(fd, incremental_blocks, + num_incremental_blocks, + &incr_prefetch); +#endif + /* * read_file_data_into_buffer() retries short reads, so if we got * fewer bytes than requested we must have hit EOF (which means @@ -1842,6 +1930,50 @@ sendFile(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 sendFile() 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 sendFile() 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 the 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 a7a8dbdd23f..c4c1218c46c 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.52.0