From 264803abd3aa616edb90b418d98280373aa3c71d Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Tue, 10 Nov 2026 14:47:58 -0500
Subject: [PATCH v2 2/2] Reduce WAL generated when copying relation storage

RelationCopyStorage() included the region between pd_lower and pd_upper
in full-page images, even when that region was known to be free space,
because it had no information about the page layout. It conservatively
treated every page as nonstandard, so this was not a correctness bug,
but it could generate unnecessary WAL.

A recent commit fixed RelationCopyStorageUsingBuffer(), which previously
treated every page as standard and could therefore omit meaningful data
between pd_lower and pd_upper. It now omits that region only for heap
and B-tree main-fork pages, which are known to use the standard page
layout, or when the region contains only zeroes.

Apply the same policy to RelationCopyStorage(). This reduces WAL volume
for known standard page layouts while preserving the full contents of
unknown layouts unless omission is demonstrably lossless.
---
 src/backend/access/heap/heapam_handler.c |  4 ++--
 src/backend/catalog/storage.c            | 19 ++++++++++++++-----
 src/backend/commands/tablecmds.c         |  6 ++++--
 src/include/catalog/storage.h            |  3 ++-
 4 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 05a81e4557a..ff2345b9233 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -555,7 +555,7 @@ heapam_relation_copy_data(Relation rel, const RelFileLocator *newrlocator)
 
 	/* copy main fork */
 	RelationCopyStorage(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
-						rel->rd_rel->relpersistence);
+						rel->rd_rel->relpersistence, true);
 
 	/* copy those extra forks that exist */
 	for (ForkNumber forkNum = MAIN_FORKNUM + 1;
@@ -574,7 +574,7 @@ heapam_relation_copy_data(Relation rel, const RelFileLocator *newrlocator)
 				 forkNum == INIT_FORKNUM))
 				log_smgrcreate(newrlocator, forkNum);
 			RelationCopyStorage(RelationGetSmgr(rel), dstrel, forkNum,
-								rel->rd_rel->relpersistence);
+								rel->rd_rel->relpersistence, false);
 		}
 	}
 
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index e443a4993c5..1a4397d15da 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -476,7 +476,7 @@ RelationPreTruncate(Relation rel)
  */
 void
 RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
-					ForkNumber forkNum, char relpersistence)
+					ForkNumber forkNum, char relpersistence, bool page_std)
 {
 	bool		use_wal;
 	bool		copying_initfork;
@@ -508,6 +508,7 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
 	for (blkno = 0; blkno < nblocks; blkno++)
 	{
 		BulkWriteBuffer buf;
+		bool		allow_page_std = page_std;
 		int			piv_flags;
 		bool		checksum_failure;
 		bool		verified;
@@ -551,11 +552,19 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
 		}
 
 		/*
-		 * Queue the page for WAL-logging and writing out.  Unfortunately we
-		 * don't know what kind of a page this is, so we have to log the full
-		 * page including any unused space.
+		 * For page layouts we don't recognize, hole removal is still safe if
+		 * the exact range that WAL would omit contains only zeroes.
 		 */
-		smgr_bulk_write(bulkstate, blkno, buf, false);
+		if (!allow_page_std && PageFreeSpaceBoundsAreValid((Page) buf))
+		{
+			PageHeader	phdr = (PageHeader) buf;
+
+			allow_page_std =
+				pg_memory_is_all_zeros((char *) buf + phdr->pd_lower,
+									   phdr->pd_upper - phdr->pd_lower);
+		}
+
+		smgr_bulk_write(bulkstate, blkno, buf, allow_page_std);
 	}
 	smgr_bulk_finish(bulkstate);
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index aa89c9a4637..cbd2bf0b2b7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -36,6 +36,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/partition.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_am_d.h"
 #include "catalog/pg_attrdef.h"
 #include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
@@ -18527,7 +18528,8 @@ index_copy_data(Relation rel, RelFileLocator newrlocator)
 
 	/* copy main fork */
 	RelationCopyStorage(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
-						rel->rd_rel->relpersistence);
+						rel->rd_rel->relpersistence,
+						rel->rd_rel->relam == BTREE_AM_OID);
 
 	/* copy those extra forks that exist */
 	for (ForkNumber forkNum = MAIN_FORKNUM + 1;
@@ -18546,7 +18548,7 @@ index_copy_data(Relation rel, RelFileLocator newrlocator)
 				 forkNum == INIT_FORKNUM))
 				log_smgrcreate(&newrlocator, forkNum);
 			RelationCopyStorage(RelationGetSmgr(rel), dstrel, forkNum,
-								rel->rd_rel->relpersistence);
+								rel->rd_rel->relpersistence, false);
 		}
 	}
 
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 70f619a6d6f..68211e51c1e 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -30,7 +30,8 @@ extern void RelationPreserveStorage(RelFileLocator rlocator, bool atCommit);
 extern void RelationPreTruncate(Relation rel);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
 extern void RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
-								ForkNumber forkNum, char relpersistence);
+								ForkNumber forkNum, char relpersistence,
+								bool page_std);
 extern bool RelFileLocatorSkippingWAL(RelFileLocator rlocator);
 extern Size EstimatePendingSyncsSpace(void);
 extern void SerializePendingSyncs(Size maxSize, char *startAddress);
-- 
2.43.0

