From c32179221dbcc0ec58427b256755803a0fc9d93f Mon Sep 17 00:00:00 2001 From: Jakub Wartak Date: Fri, 31 Jul 2026 13:51:21 +0200 Subject: [PATCH v22092026 07/11] pg_basebackup: preallocate extracted files with posix_fallocate() When extracting a basebackup, each output (segment/data file) grows each one write() at a time. On filesystems with delayed allocation (e.g. ext4/XFS) this makes every write() to find space, which shows up as top bottleneck of the single-threaded/CPU-bount pg_basebackups's time. The tar member header already gives us the final file size up front, so preallocate the whole file in one go. Author: Jakub Wartak --- src/fe_utils/astreamer_file.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/fe_utils/astreamer_file.c b/src/fe_utils/astreamer_file.c index b867e9489cd..32df6b09fb9 100644 --- a/src/fe_utils/astreamer_file.c +++ b/src/fe_utils/astreamer_file.c @@ -63,7 +63,7 @@ static void astreamer_extractor_free(astreamer *streamer); static void extract_directory(const char *filename, mode_t mode); static void extract_link(const char *filename, const char *linktarget); static int create_file_for_extract(const char *filename, mode_t mode, - bool discard_backup); + bool discard_backup, pgoff_t size); static void write_file_range(int fd, const char *filename, const char *data, int len); @@ -241,7 +241,8 @@ astreamer_extractor_content(astreamer *streamer, astreamer_member *member, mystreamer->fd = create_file_for_extract(mystreamer->filename, member->mode, - mystreamer->discard_backup); + mystreamer->discard_backup, + member->size); else if (member->is_directory) { if (!mystreamer->discard_backup) @@ -377,7 +378,7 @@ extract_link(const char *filename, const char *linktarget) */ static int create_file_for_extract(const char *filename, mode_t mode, - bool discard_backup) + bool discard_backup, pgoff_t size) { int fd; @@ -400,6 +401,28 @@ create_file_for_extract(const char *filename, mode_t mode, filename); #endif + /* + * Preallocate the file to its final size. We know the size up front + * from the tar member header, so this lets the filesystem allocate all + * the blocks in one go rather than growing the file on every write. + */ +#ifdef HAVE_POSIX_FALLOCATE + if (size > 0) + { + int rc = posix_fallocate(fd, 0, size); + + /* + * This is just an optimization, so we ignore failures such as + * EINVAL/EOPNOTSUPP, however we need to properly fail on ENOSPC. + */ + if (rc == ENOSPC) + { + errno = rc; + pg_fatal("could not preallocate file \"%s\": %m", filename); + } + } +#endif + return fd; } -- 2.43.5