From ca099d7bc6b4598592048c3370b82b09d317d98b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 18 Jun 2025 16:23:16 +0900
Subject: [PATCH v2 07/13] Introduce global 64-bit TOAST ID counter in control
 file

An 8 byte counter is added to the control file, providing a unique
64-bit-wide source for toast value IDs, with the same guarantees as OIDs
in terms of durability.  SQL functions and tools looking at the control
file are updated.  A WAL record is generated every 8k values generated,
that can be adjusted if required.

Requires a bump of WAL format.
Requires a bump of control file version.
Requires a catalog version bump.
---
 src/include/access/toast_counter.h            | 35 +++++++
 src/include/access/xlog.h                     |  1 +
 src/include/catalog/pg_control.h              |  4 +-
 src/include/catalog/pg_proc.dat               |  6 +-
 src/include/storage/lwlocklist.h              |  1 +
 src/backend/access/common/Makefile            |  1 +
 src/backend/access/common/meson.build         |  1 +
 src/backend/access/common/toast_counter.c     | 98 +++++++++++++++++++
 src/backend/access/rmgrdesc/xlogdesc.c        | 10 ++
 src/backend/access/transam/xlog.c             | 44 +++++++++
 src/backend/replication/logical/decode.c      |  1 +
 src/backend/storage/ipc/ipci.c                |  5 +-
 .../utils/activity/wait_event_names.txt       |  1 +
 src/backend/utils/misc/pg_controldata.c       | 23 +++--
 src/bin/pg_controldata/pg_controldata.c       |  2 +
 src/bin/pg_resetwal/pg_resetwal.c             |  2 +
 doc/src/sgml/func.sgml                        |  5 +
 src/tools/pgindent/typedefs.list              |  1 +
 18 files changed, 226 insertions(+), 15 deletions(-)
 create mode 100644 src/include/access/toast_counter.h
 create mode 100644 src/backend/access/common/toast_counter.c

diff --git a/src/include/access/toast_counter.h b/src/include/access/toast_counter.h
new file mode 100644
index 000000000000..80749cba0f87
--- /dev/null
+++ b/src/include/access/toast_counter.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * toast_counter.h
+ *	  Machinery for TOAST value counter.
+ *
+ * Copyright (c) 2000-2025, PostgreSQL Global Development Group
+ *
+ * src/include/access/toast_counter.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TOAST_COUNTER_H
+#define TOAST_COUNTER_H
+
+#define InvalidToastId	0		/* Invalid TOAST value ID */
+#define FirstToastId	1		/* First TOAST value ID assigned */
+
+/*
+ * Structure in shared memory to track TOAST value counter activity.
+ * These are protected by ToastIdGenLock.
+ */
+typedef struct ToastCounterData
+{
+	uint64		nextId;			/* next TOAST value ID to assign */
+	uint32		idCount;		/* IDs available before WAL work */
+} ToastCounterData;
+
+extern PGDLLIMPORT ToastCounterData *ToastCounter;
+
+/* external declarations */
+extern Size ToastCounterShmemSize(void);
+extern void ToastCounterShmemInit(void);
+extern uint64 GetNewToastId(void);
+
+#endif							/* TOAST_TYPE_H */
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index d313099c027f..a50296736242 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -245,6 +245,7 @@ extern bool CreateCheckPoint(int flags);
 extern bool CreateRestartPoint(int flags);
 extern WALAvailability GetWALAvailability(XLogRecPtr targetLSN);
 extern void XLogPutNextOid(Oid nextOid);
+extern void XLogPutNextToastId(uint64 nextId);
 extern XLogRecPtr XLogRestorePoint(const char *rpName);
 extern void UpdateFullPageWrites(void);
 extern void GetFullPageWriteInfo(XLogRecPtr *RedoRecPtr_p, bool *doPageWrites_p);
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index 63e834a6ce47..1194b4928155 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -22,7 +22,7 @@
 
 
 /* Version identifier for this pg_control format */
-#define PG_CONTROL_VERSION	1800
+#define PG_CONTROL_VERSION	1900
 
 /* Nonce key length, see below */
 #define MOCK_AUTH_NONCE_LEN		32
@@ -45,6 +45,7 @@ typedef struct CheckPoint
 	Oid			nextOid;		/* next free OID */
 	MultiXactId nextMulti;		/* next free MultiXactId */
 	MultiXactOffset nextMultiOffset;	/* next free MultiXact offset */
+	uint64		nextToastId;	/* next free TOAST ID */
 	TransactionId oldestXid;	/* cluster-wide minimum datfrozenxid */
 	Oid			oldestXidDB;	/* database with minimum datfrozenxid */
 	MultiXactId oldestMulti;	/* cluster-wide minimum datminmxid */
@@ -80,6 +81,7 @@ typedef struct CheckPoint
 /* 0xC0 is used in Postgres 9.5-11 */
 #define XLOG_OVERWRITE_CONTRECORD		0xD0
 #define XLOG_CHECKPOINT_REDO			0xE0
+#define XLOG_NEXT_TOAST_ID				0xF0
 
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d4650947c63a..ff0871037c53 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12306,9 +12306,9 @@
   descr => 'pg_controldata checkpoint state information as a function',
   proname => 'pg_control_checkpoint', provolatile => 'v',
   prorettype => 'record', proargtypes => '',
-  proallargtypes => '{pg_lsn,pg_lsn,text,int4,int4,bool,text,oid,xid,xid,xid,oid,xid,xid,oid,xid,xid,timestamptz}',
-  proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{checkpoint_lsn,redo_lsn,redo_wal_file,timeline_id,prev_timeline_id,full_page_writes,next_xid,next_oid,next_multixact_id,next_multi_offset,oldest_xid,oldest_xid_dbid,oldest_active_xid,oldest_multi_xid,oldest_multi_dbid,oldest_commit_ts_xid,newest_commit_ts_xid,checkpoint_time}',
+  proallargtypes => '{pg_lsn,pg_lsn,text,int4,int4,bool,text,oid,xid,xid,int8,xid,oid,xid,xid,oid,xid,xid,timestamptz}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{checkpoint_lsn,redo_lsn,redo_wal_file,timeline_id,prev_timeline_id,full_page_writes,next_xid,next_oid,next_multixact_id,next_multi_offset,next_toast_id,oldest_xid,oldest_xid_dbid,oldest_active_xid,oldest_multi_xid,oldest_multi_dbid,oldest_commit_ts_xid,newest_commit_ts_xid,checkpoint_time}',
   prosrc => 'pg_control_checkpoint' },
 
 { oid => '3443',
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index a9681738146e..7f7ca92382b5 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -84,3 +84,4 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, ToastIdGen)
diff --git a/src/backend/access/common/Makefile b/src/backend/access/common/Makefile
index 1ef86a245886..6e9a3a430c19 100644
--- a/src/backend/access/common/Makefile
+++ b/src/backend/access/common/Makefile
@@ -27,6 +27,7 @@ OBJS = \
 	syncscan.o \
 	tidstore.o \
 	toast_compression.o \
+	toast_counter.o \
 	toast_external.o \
 	toast_internals.o \
 	tupconvert.o \
diff --git a/src/backend/access/common/meson.build b/src/backend/access/common/meson.build
index c20f2e88921e..4254132c8dfd 100644
--- a/src/backend/access/common/meson.build
+++ b/src/backend/access/common/meson.build
@@ -15,6 +15,7 @@ backend_sources += files(
   'syncscan.c',
   'tidstore.c',
   'toast_compression.c',
+  'toast_counter.c',
   'toast_external.c',
   'toast_internals.c',
   'tupconvert.c',
diff --git a/src/backend/access/common/toast_counter.c b/src/backend/access/common/toast_counter.c
new file mode 100644
index 000000000000..94d361d0d5c4
--- /dev/null
+++ b/src/backend/access/common/toast_counter.c
@@ -0,0 +1,98 @@
+/*-------------------------------------------------------------------------
+ *
+ * toast_counter.c
+ *	  Functions for TOAST value counter.
+ *
+ * Copyright (c) 2025, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/common/toast_counter.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/toast_counter.h"
+#include "access/xlog.h"
+#include "miscadmin.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+
+/* Number of TOAST values to preallocate before WAL work */
+#define TOAST_ID_PREFETCH		8192
+
+/* pointer to variables struct in shared memory */
+ToastCounterData *ToastCounter = NULL;
+
+/*
+ * Initialization of shared memory for ToastCounter.
+ */
+Size
+ToastCounterShmemSize(void)
+{
+	return sizeof(ToastCounterData);
+}
+
+void
+ToastCounterShmemInit(void)
+{
+	bool		found;
+
+	/* Initialize shared state struct */
+	ToastCounter = ShmemInitStruct("ToastCounter",
+								   sizeof(ToastCounterData),
+								   &found);
+	if (!IsUnderPostmaster)
+	{
+		Assert(!found);
+		memset(ToastCounter, 0, sizeof(ToastCounterData));
+	}
+	else
+		Assert(found);
+}
+
+/*
+ * GetNewToastId
+ *
+ * Toast IDs are generated as a cluster-wide counter.  They are 64 bits
+ * wide, hence wraparound will unlikely happen.
+ */
+uint64
+GetNewToastId(void)
+{
+	uint64		result;
+
+	if (RecoveryInProgress())
+		elog(ERROR, "cannot assign TOAST IDs during recovery");
+
+	LWLockAcquire(ToastIdGenLock, LW_EXCLUSIVE);
+
+	/*
+	 * Check for initialization or wraparound of the toast counter ID.
+	 * InvalidToastId (0) should never be returned.  We are 64 bit-wide, hence
+	 * wraparound is unlikely going to happen, but this check is cheap so
+	 * let's play it safe.
+	 */
+	if (ToastCounter->nextId < ((uint64) FirstToastId))
+	{
+		/* Most-likely first bootstrap or initdb assignment */
+		ToastCounter->nextId = FirstToastId;
+		ToastCounter->idCount = 0;
+	}
+
+	/* If running out of logged for TOAST IDs, log more */
+	if (ToastCounter->idCount == 0)
+	{
+		XLogPutNextToastId(ToastCounter->nextId + TOAST_ID_PREFETCH);
+		ToastCounter->idCount = TOAST_ID_PREFETCH;
+	}
+
+	result = ToastCounter->nextId;
+	(ToastCounter->nextId)++;
+	(ToastCounter->idCount)--;
+
+	LWLockRelease(ToastIdGenLock);
+
+	return result;
+}
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index 58040f28656f..0940040b33ab 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -96,6 +96,13 @@ xlog_desc(StringInfo buf, XLogReaderState *record)
 		memcpy(&nextOid, rec, sizeof(Oid));
 		appendStringInfo(buf, "%u", nextOid);
 	}
+	else if (info == XLOG_NEXT_TOAST_ID)
+	{
+		uint64		nextId;
+
+		memcpy(&nextId, rec, sizeof(uint64));
+		appendStringInfo(buf, "%" PRIu64, nextId);
+	}
 	else if (info == XLOG_RESTORE_POINT)
 	{
 		xl_restore_point *xlrec = (xl_restore_point *) rec;
@@ -218,6 +225,9 @@ xlog_identify(uint8 info)
 		case XLOG_CHECKPOINT_REDO:
 			id = "CHECKPOINT_REDO";
 			break;
+		case XLOG_NEXT_TOAST_ID:
+			id = "NEXT_TOAST_ID";
+			break;
 	}
 
 	return id;
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 47ffc0a23077..4ae1ef8bb2c2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -53,6 +53,7 @@
 #include "access/rewriteheap.h"
 #include "access/subtrans.h"
 #include "access/timeline.h"
+#include "access/toast_counter.h"
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -5269,6 +5270,7 @@ BootStrapXLOG(uint32 data_checksum_version)
 	checkPoint.nextOid = FirstGenbkiObjectId;
 	checkPoint.nextMulti = FirstMultiXactId;
 	checkPoint.nextMultiOffset = 0;
+	checkPoint.nextToastId = FirstToastId;
 	checkPoint.oldestXid = FirstNormalTransactionId;
 	checkPoint.oldestXidDB = Template1DbOid;
 	checkPoint.oldestMulti = FirstMultiXactId;
@@ -5281,6 +5283,10 @@ BootStrapXLOG(uint32 data_checksum_version)
 	TransamVariables->nextXid = checkPoint.nextXid;
 	TransamVariables->nextOid = checkPoint.nextOid;
 	TransamVariables->oidCount = 0;
+
+	ToastCounter->nextId = checkPoint.nextToastId;
+	ToastCounter->idCount = 0;
+
 	MultiXactSetNextMXact(checkPoint.nextMulti, checkPoint.nextMultiOffset);
 	AdvanceOldestClogXid(checkPoint.oldestXid);
 	SetTransactionIdLimit(checkPoint.oldestXid, checkPoint.oldestXidDB);
@@ -5757,6 +5763,8 @@ StartupXLOG(void)
 	TransamVariables->nextXid = checkPoint.nextXid;
 	TransamVariables->nextOid = checkPoint.nextOid;
 	TransamVariables->oidCount = 0;
+	ToastCounter->nextId = checkPoint.nextToastId;
+	ToastCounter->idCount = 0;
 	MultiXactSetNextMXact(checkPoint.nextMulti, checkPoint.nextMultiOffset);
 	AdvanceOldestClogXid(checkPoint.oldestXid);
 	SetTransactionIdLimit(checkPoint.oldestXid, checkPoint.oldestXidDB);
@@ -7299,6 +7307,12 @@ CreateCheckPoint(int flags)
 		checkPoint.nextOid += TransamVariables->oidCount;
 	LWLockRelease(OidGenLock);
 
+	LWLockAcquire(ToastIdGenLock, LW_SHARED);
+	checkPoint.nextToastId = ToastCounter->nextId;
+	if (!shutdown)
+		checkPoint.nextToastId += ToastCounter->idCount;
+	LWLockRelease(ToastIdGenLock);
+
 	MultiXactGetCheckptMulti(shutdown,
 							 &checkPoint.nextMulti,
 							 &checkPoint.nextMultiOffset,
@@ -8238,6 +8252,22 @@ XLogPutNextOid(Oid nextOid)
 	 */
 }
 
+/*
+ * Write a NEXT_TOAST_ID log record.
+ */
+void
+XLogPutNextToastId(uint64 nextId)
+{
+	XLogBeginInsert();
+	XLogRegisterData(&nextId, sizeof(uint64));
+	(void) XLogInsert(RM_XLOG_ID, XLOG_NEXT_TOAST_ID);
+
+	/*
+	 * The next TOAST value ID is not flushed immediately, for the same reason
+	 * as above for the OIDs in XLogPutNextOid().
+	 */
+}
+
 /*
  * Write an XLOG SWITCH record.
  *
@@ -8453,6 +8483,16 @@ xlog_redo(XLogReaderState *record)
 		TransamVariables->oidCount = 0;
 		LWLockRelease(OidGenLock);
 	}
+	else if (info == XLOG_NEXT_TOAST_ID)
+	{
+		uint64		nextToastId;
+
+		memcpy(&nextToastId, XLogRecGetData(record), sizeof(uint64));
+		LWLockAcquire(ToastIdGenLock, LW_EXCLUSIVE);
+		ToastCounter->nextId = nextToastId;
+		ToastCounter->idCount = 0;
+		LWLockRelease(ToastIdGenLock);
+	}
 	else if (info == XLOG_CHECKPOINT_SHUTDOWN)
 	{
 		CheckPoint	checkPoint;
@@ -8467,6 +8507,10 @@ xlog_redo(XLogReaderState *record)
 		TransamVariables->nextOid = checkPoint.nextOid;
 		TransamVariables->oidCount = 0;
 		LWLockRelease(OidGenLock);
+		LWLockAcquire(ToastIdGenLock, LW_EXCLUSIVE);
+		ToastCounter->nextId = checkPoint.nextToastId;
+		ToastCounter->idCount = 0;
+		LWLockRelease(ToastIdGenLock);
 		MultiXactSetNextMXact(checkPoint.nextMulti,
 							  checkPoint.nextMultiOffset);
 
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index cc03f0706e9c..bb0337d37201 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -188,6 +188,7 @@ xlog_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		case XLOG_FPI:
 		case XLOG_OVERWRITE_CONTRECORD:
 		case XLOG_CHECKPOINT_REDO:
+		case XLOG_NEXT_TOAST_ID:
 			break;
 		default:
 			elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info);
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f6..9102c267d7b0 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -20,6 +20,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/syncscan.h"
+#include "access/toast_counter.h"
 #include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
@@ -119,6 +120,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, ProcGlobalShmemSize());
 	size = add_size(size, XLogPrefetchShmemSize());
 	size = add_size(size, VarsupShmemSize());
+	size = add_size(size, ToastCounterShmemSize());
 	size = add_size(size, XLOGShmemSize());
 	size = add_size(size, XLogRecoveryShmemSize());
 	size = add_size(size, CLOGShmemSize());
@@ -280,8 +282,9 @@ CreateOrAttachShmemStructs(void)
 	DSMRegistryShmemInit();
 
 	/*
-	 * Set up xlog, clog, and buffers
+	 * Set up TOAST counter, xlog, clog, and buffers
 	 */
+	ToastCounterShmemInit();
 	VarsupShmemInit();
 	XLOGShmemInit();
 	XLogPrefetchShmemInit();
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 4da68312b5f9..9aa44de58770 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -352,6 +352,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+ToastIdGen	"Waiting to allocate a new TOAST value ID."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
index 6d036e3bf328..e4abf8593b8d 100644
--- a/src/backend/utils/misc/pg_controldata.c
+++ b/src/backend/utils/misc/pg_controldata.c
@@ -69,8 +69,8 @@ pg_control_system(PG_FUNCTION_ARGS)
 Datum
 pg_control_checkpoint(PG_FUNCTION_ARGS)
 {
-	Datum		values[18];
-	bool		nulls[18];
+	Datum		values[19];
+	bool		nulls[19];
 	TupleDesc	tupdesc;
 	HeapTuple	htup;
 	ControlFileData *ControlFile;
@@ -130,30 +130,33 @@ pg_control_checkpoint(PG_FUNCTION_ARGS)
 	values[9] = TransactionIdGetDatum(ControlFile->checkPointCopy.nextMultiOffset);
 	nulls[9] = false;
 
-	values[10] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestXid);
+	values[10] = UInt64GetDatum(ControlFile->checkPointCopy.nextToastId);
 	nulls[10] = false;
 
-	values[11] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestXidDB);
+	values[11] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestXid);
 	nulls[11] = false;
 
-	values[12] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestActiveXid);
+	values[12] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestXidDB);
 	nulls[12] = false;
 
-	values[13] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestMulti);
+	values[13] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestActiveXid);
 	nulls[13] = false;
 
-	values[14] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestMultiDB);
+	values[14] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestMulti);
 	nulls[14] = false;
 
-	values[15] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestCommitTsXid);
+	values[15] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestMultiDB);
 	nulls[15] = false;
 
-	values[16] = TransactionIdGetDatum(ControlFile->checkPointCopy.newestCommitTsXid);
+	values[16] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestCommitTsXid);
 	nulls[16] = false;
 
-	values[17] = TimestampTzGetDatum(time_t_to_timestamptz(ControlFile->checkPointCopy.time));
+	values[17] = TransactionIdGetDatum(ControlFile->checkPointCopy.newestCommitTsXid);
 	nulls[17] = false;
 
+	values[18] = TimestampTzGetDatum(time_t_to_timestamptz(ControlFile->checkPointCopy.time));
+	nulls[18] = false;
+
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
 	PG_RETURN_DATUM(HeapTupleGetDatum(htup));
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index 7bb801bb8861..d83368ba4910 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -266,6 +266,8 @@ main(int argc, char *argv[])
 		   ControlFile->checkPointCopy.nextMulti);
 	printf(_("Latest checkpoint's NextMultiOffset:  %u\n"),
 		   ControlFile->checkPointCopy.nextMultiOffset);
+	printf(_("Latest checkpoint's NextToastID:      %" PRIu64 "\n"),
+		   ControlFile->checkPointCopy.nextToastId);
 	printf(_("Latest checkpoint's oldestXID:        %u\n"),
 		   ControlFile->checkPointCopy.oldestXid);
 	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index e876f35f38ed..bb324c710911 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -45,6 +45,7 @@
 
 #include "access/heaptoast.h"
 #include "access/multixact.h"
+#include "access/toast_counter.h"
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
@@ -686,6 +687,7 @@ GuessControlValues(void)
 	ControlFile.checkPointCopy.nextOid = FirstGenbkiObjectId;
 	ControlFile.checkPointCopy.nextMulti = FirstMultiXactId;
 	ControlFile.checkPointCopy.nextMultiOffset = 0;
+	ControlFile.checkPointCopy.nextToastId = FirstToastId;
 	ControlFile.checkPointCopy.oldestXid = FirstNormalTransactionId;
 	ControlFile.checkPointCopy.oldestXidDB = InvalidOid;
 	ControlFile.checkPointCopy.oldestMulti = FirstMultiXactId;
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 810b2b50f0da..74bd691b0a8f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -28157,6 +28157,11 @@ acl      | {postgres=arwdDxtm/postgres,foo=r/postgres}
        <entry><type>xid</type></entry>
       </row>
 
+      <row>
+       <entry><structfield>next_toast_id</structfield></entry>
+       <entry><type>bigint</type></entry>
+      </row>
+
       <row>
        <entry><structfield>oldest_xid</structfield></entry>
        <entry><type>xid</type></entry>
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3b98606a1701..25ab35ea350f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3050,6 +3050,7 @@ TmFromChar
 TmToChar
 ToastAttrInfo
 ToastCompressionId
+ToastCounterData
 ToastTupleContext
 ToastedAttribute
 TocEntry
-- 
2.50.0

