From 308367ba3464a3575034dd050f01b26303f4f6dd Mon Sep 17 00:00:00 2001
From: Jakub Wartak <jakub.wartak@enterprisedb.com>
Date: Fri, 31 Jul 2026 13:51:21 +0200
Subject: [PATCH v06082026 07/10] 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 <jakub.wartak@enterprisedb.com>
---
 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.0

