From 1e387dbcf45d2c2adce7ec697f0e959e80ef23b7 Mon Sep 17 00:00:00 2001
From: David Christensen <david.christensen@crunchydata.com>
Date: Wed, 7 Feb 2024 10:49:31 -0500
Subject: [PATCH v1 2/4] Split MaxHeapTupleSize into runtime and max values

---
 src/backend/access/heap/heapam.c              | 12 +++++-----
 src/backend/access/heap/hio.c                 |  6 ++---
 src/backend/access/heap/rewriteheap.c         |  4 ++--
 .../replication/logical/reorderbuffer.c       |  2 +-
 src/backend/storage/freespace/freespace.c     |  2 +-
 src/include/access/heaptoast.h                |  2 +-
 src/include/access/htup_details.h             | 22 ++++++++++++++-----
 src/test/regress/expected/insert.out          |  2 +-
 src/test/regress/sql/insert.sql               |  2 +-
 9 files changed, 32 insertions(+), 22 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index f740d4225e..dcbb926916 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -9231,7 +9231,7 @@ heap_xlog_insert(XLogReaderState *record)
 	union
 	{
 		HeapTupleHeaderData hdr;
-		char		data[MaxHeapTupleSize];
+		char		data[MaxHeapTupleSizeLimit];
 	}			tbuf;
 	HeapTupleHeader htup;
 	xl_heap_header xlhdr;
@@ -9287,7 +9287,7 @@ heap_xlog_insert(XLogReaderState *record)
 		data = XLogRecGetBlockData(record, 0, &datalen);
 
 		newlen = datalen - SizeOfHeapHeader;
-		Assert(datalen > SizeOfHeapHeader && newlen <= MaxHeapTupleSize);
+		Assert(datalen > SizeOfHeapHeader && newlen <= ClusterMaxHeapTupleSize);
 		memcpy((char *) &xlhdr, data, SizeOfHeapHeader);
 		data += SizeOfHeapHeader;
 
@@ -9353,7 +9353,7 @@ heap_xlog_multi_insert(XLogReaderState *record)
 	union
 	{
 		HeapTupleHeaderData hdr;
-		char		data[MaxHeapTupleSize];
+		char		data[MaxHeapTupleSizeLimit];
 	}			tbuf;
 	HeapTupleHeader htup;
 	uint32		newlen;
@@ -9431,7 +9431,7 @@ heap_xlog_multi_insert(XLogReaderState *record)
 			tupdata = ((char *) xlhdr) + SizeOfMultiInsertTuple;
 
 			newlen = xlhdr->datalen;
-			Assert(newlen <= MaxHeapTupleSize);
+			Assert(newlen <= ClusterMaxHeapTupleSize);
 			htup = &tbuf.hdr;
 			MemSet((char *) htup, 0, SizeofHeapTupleHeader);
 			/* PG73FORMAT: get bitmap [+ padding] [+ oid] + data */
@@ -9510,7 +9510,7 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 	union
 	{
 		HeapTupleHeaderData hdr;
-		char		data[MaxHeapTupleSize];
+		char		data[MaxHeapTupleSizeLimit];
 	}			tbuf;
 	xl_heap_header xlhdr;
 	uint32		newlen;
@@ -9666,7 +9666,7 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
 		recdata += SizeOfHeapHeader;
 
 		tuplen = recdata_end - recdata;
-		Assert(tuplen <= MaxHeapTupleSize);
+		Assert(tuplen <= ClusterMaxHeapTupleSize);
 
 		htup = &tbuf.hdr;
 		MemSet((char *) htup, 0, SizeofHeapTupleHeader);
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 58f0a384c8..3e3963503b 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -530,11 +530,11 @@ RelationGetBufferForTuple(Relation relation, Size len,
 	/*
 	 * If we're gonna fail for oversize tuple, do it right away
 	 */
-	if (len > MaxHeapTupleSize)
+	if (len > ClusterMaxHeapTupleSize)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("row is too big: size %zu, maximum size %zu",
-						len, MaxHeapTupleSize)));
+						len, ClusterMaxHeapTupleSize)));
 
 	/* Compute desired extra freespace due to fillfactor option */
 	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
@@ -546,7 +546,7 @@ RelationGetBufferForTuple(Relation relation, Size len,
 	 * somewhat arbitrary, but it should prevent most unnecessary relation
 	 * extensions while inserting large tuples into low-fillfactor tables.
 	 */
-	nearlyEmptyFreeSpace = MaxHeapTupleSize -
+	nearlyEmptyFreeSpace = ClusterMaxHeapTupleSize -
 		(ClusterMaxHeapTuplesPerPage / 8 * sizeof(ItemIdData));
 	if (len + saveFreeSpace > nearlyEmptyFreeSpace)
 		targetFreeSpace = Max(len, nearlyEmptyFreeSpace);
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 34107323ff..75e0d1ffe8 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -653,11 +653,11 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 	/*
 	 * If we're gonna fail for oversize tuple, do it right away
 	 */
-	if (len > MaxHeapTupleSize)
+	if (len > ClusterMaxHeapTupleSize)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("row is too big: size %zu, maximum size %zu",
-						len, MaxHeapTupleSize)));
+						len, ClusterMaxHeapTupleSize)));
 
 	/* Compute desired extra freespace due to fillfactor option */
 	saveFreeSpace = RelationGetTargetPageFreeSpace(state->rs_new_rel,
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index bbf0966182..07441d630b 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -4879,7 +4879,7 @@ ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
 	 * the tuplebuf because attrs[] will point back into the current content.
 	 */
 	tmphtup = heap_form_tuple(desc, attrs, isnull);
-	Assert(newtup->t_len <= MaxHeapTupleSize);
+	Assert(newtup->t_len <= ClusterMaxHeapTupleSize);
 	Assert(newtup->t_data == (HeapTupleHeader) ((char *) newtup + HEAPTUPLESIZE));
 
 	memcpy(newtup->t_data, tmphtup->t_data, tmphtup->t_len);
diff --git a/src/backend/storage/freespace/freespace.c b/src/backend/storage/freespace/freespace.c
index 15e3a07341..f31edadabf 100644
--- a/src/backend/storage/freespace/freespace.c
+++ b/src/backend/storage/freespace/freespace.c
@@ -63,7 +63,7 @@
  */
 #define FSM_CATEGORIES	256
 #define FSM_CAT_STEP	(BLCKSZ / FSM_CATEGORIES)
-#define MaxFSMRequestSize	MaxHeapTupleSize
+#define MaxFSMRequestSize	ClusterMaxHeapTupleSize
 
 /*
  * Depth of the on-disk tree. We need to be able to address 2^32-1 blocks,
diff --git a/src/include/access/heaptoast.h b/src/include/access/heaptoast.h
index c376dff48d..6fe836f7d1 100644
--- a/src/include/access/heaptoast.h
+++ b/src/include/access/heaptoast.h
@@ -65,7 +65,7 @@
  * compress it (we can't move it out-of-line, however).  Note that this
  * number is per-datum, not per-tuple, for simplicity in index_form_tuple().
  */
-#define TOAST_INDEX_TARGET		(MaxHeapTupleSize / 16)
+#define TOAST_INDEX_TARGET		(ClusterMaxHeapTupleSize / 16)
 
 /*
  * When we store an oversize datum externally, we divide it into chunks
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 7a35baf4b1..bb5c746b98 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -550,17 +550,27 @@ StaticAssertDecl(MaxOffsetNumber < SpecTokenOffsetNumber,
 #define BITMAPLEN(NATTS)	(((int)(NATTS) + 7) / 8)
 
 /*
- * MaxHeapTupleSize is the maximum allowed size of a heap tuple, including
- * header and MAXALIGN alignment padding.  Basically it's BLCKSZ minus the
- * other stuff that has to be on a disk page.  Since heap pages use no
- * "special space", there's no deduction for that.
+ * ClusterMaxHeapTupleSize is a cluster-specific maximum allowed size of a
+ * heap tuple, including header and MAXALIGN alignment padding.  Basically
+ * it's BLCKSZ minus the other stuff that has to be on a disk page.  Since
+ * heap pages use no "special space", there's no deduction for that.
+ *
+ * MaxHeapTuplesSizeLimit is the largest value that ClusterMaxHeapTupleSize
+ * could be.  While these currently evaluate to the same value, these are
+ * being split out so ClusterMaxHeapTupleSize can become a variable
+ * instead of a constant.
+ *
+ * The CalcMaxHeapTupleSize() macro is used to determine the appropriate
+ * values, given the usable page space on a given page.
  *
  * NOTE: we allow for the ItemId that must point to the tuple, ensuring that
  * an otherwise-empty page can indeed hold a tuple of this size.  Because
  * ItemIds and tuples have different alignment requirements, don't assume that
- * you can, say, fit 2 tuples of size MaxHeapTupleSize/2 on the same page.
+ * you can, say, fit 2 tuples of size ClusterMaxHeapTupleSize/2 on the same page.
  */
-#define MaxHeapTupleSize  (BLCKSZ - MAXALIGN(SizeOfPageHeaderData + sizeof(ItemIdData)))
+#define CalcMaxHeapTupleSize(size)  (size - sizeof(ItemIdData))
+#define ClusterMaxHeapTupleSize CalcMaxHeapTupleSize(BLCKSZ - SizeOfPageHeaderData)
+#define MaxHeapTupleSizeLimit CalcMaxHeapTupleSize(BLCKSZ - SizeOfPageHeaderData)
 #define MinHeapTupleSize  MAXALIGN(SizeofHeapTupleHeader)
 
 /*
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index dd4354fc7d..eebf3c6d4d 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -86,7 +86,7 @@ drop table inserttest;
 --
 CREATE TABLE large_tuple_test (a int, b text) WITH (fillfactor = 10);
 ALTER TABLE large_tuple_test ALTER COLUMN b SET STORAGE plain;
--- create page w/ free space in range [nearlyEmptyFreeSpace, MaxHeapTupleSize)
+-- create page w/ free space in range [nearlyEmptyFreeSpace, ClusterMaxHeapTupleSize)
 INSERT INTO large_tuple_test (select 1, NULL);
 -- should still fit on the page
 INSERT INTO large_tuple_test (select 2, repeat('a', 1000));
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index bdcffd0314..53f46e7960 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -43,7 +43,7 @@ drop table inserttest;
 CREATE TABLE large_tuple_test (a int, b text) WITH (fillfactor = 10);
 ALTER TABLE large_tuple_test ALTER COLUMN b SET STORAGE plain;
 
--- create page w/ free space in range [nearlyEmptyFreeSpace, MaxHeapTupleSize)
+-- create page w/ free space in range [nearlyEmptyFreeSpace, ClusterMaxHeapTupleSize)
 INSERT INTO large_tuple_test (select 1, NULL);
 
 -- should still fit on the page
-- 
2.40.1

