agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
enhance wraparound warnings
25+ messages / 8 participants
[nested] [flat]

* enhance wraparound warnings
@ 2025-11-14 17:05 Nathan Bossart <nathandbossart@gmail.com>
  2025-12-11 20:28 ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  0 siblings, 2 replies; 25+ messages in thread

From: Nathan Bossart @ 2025-11-14 17:05 UTC (permalink / raw)
  To: pgsql-hackers

varsup.c has the following comment:

	/*
	 * We'll start complaining loudly when we get within 40M transactions of
	 * data loss.  This is kind of arbitrary, but if you let your gas gauge
	 * get down to 2% of full, would you be looking for the next gas station?
	 * We need to be fairly liberal about this number because there are lots
	 * of scenarios where most transactions are done by automatic clients that
	 * won't pay attention to warnings.  (No, we're not gonna make this
	 * configurable.  If you know enough to configure it, you know enough to
	 * not get in this kind of trouble in the first place.)
	 */

I don't know about you, but I start getting antsy around a quarter tank.
In any case, I'm told that even 40M transactions aren't enough time to
react these days.  Attached are a few patches to enhance the wraparound
warnings.

* 0001 adds a "percent remaining" detail message to the existing WARNING.
The idea is that "1.86% of transaction IDs" is both easier to understand
and better indicates urgency than "39985967 transactions".

* 0002 bumps the warning limit from 40M to 100M to give folks some more
time to react.

* 0003 adds an early warning system for when fewer than 500M transactions
remain.  This system sends a LOG only to the server log every 1M
transactions.  The hope is that this gets someone's attention sooner
without flooding the application and server log.

Thoughts?

-- 
nathan
From 4fd750a67e98cb7b86970ecca5ff3a26fd1f7690 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 09:59:15 -0600
Subject: [PATCH v1 1/3] Add percentage of transaction IDs that are available
 to wraparound warnings.

---
 doc/src/sgml/maintenance.sgml          |  1 +
 src/backend/access/transam/multixact.c | 10 ++++++++++
 src/backend/access/transam/varsup.c    |  8 ++++++++
 3 files changed, 19 insertions(+)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 120bac8875f..b89278ef032 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,6 +674,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
+DETAIL:  Approximately 1.86% of transactions IDs are available for use.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 9d5f130af7e..63eb2548da1 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1118,6 +1118,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / PG_INT32_MAX * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -1127,6 +1129,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / PG_INT32_MAX * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -1225,6 +1229,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 							   MultiXactState->offsetStopLimit - nextOffset + nmembers,
 							   MultiXactState->oldestMultiXactDB,
 							   MultiXactState->offsetStopLimit - nextOffset + nmembers),
+				 errdetail("Approximately %.2f%% of multixact members are available for use.",
+						   (double) (MultiXactState->offsetStopLimit - nextOffset + nmembers) / PG_INT32_MAX * 100),
 				 errhint("Execute a database-wide VACUUM in that database with reduced \"vacuum_multixact_freeze_min_age\" and \"vacuum_multixact_freeze_table_age\" settings.")));
 
 	ExtendMultiXactMember(nextOffset, nmembers);
@@ -2414,6 +2420,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / PG_INT32_MAX * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -2423,6 +2431,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / PG_INT32_MAX * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index f8c4dada7c9..962396bae10 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -175,6 +175,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / PG_INT32_MAX * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -182,6 +184,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / PG_INT32_MAX * 100),
 						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -490,6 +494,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / PG_INT32_MAX * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -497,6 +503,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / PG_INT32_MAX * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
-- 
2.39.5 (Apple Git-154)
From 6555ccd8910420f365a253f03327e0067bc0ca66 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 10:28:52 -0600
Subject: [PATCH v1 2/3] Bump transaction ID limit to warn at 100M.

---
 doc/src/sgml/maintenance.sgml          | 4 ++--
 src/backend/access/transam/multixact.c | 6 +++---
 src/backend/access/transam/varsup.c    | 6 +++---
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index b89278ef032..8a12bd5f65a 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -670,7 +670,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
    <para>
     If for some reason autovacuum fails to clear old XIDs from a table, the
     system will begin to emit warning messages like this when the database's
-    oldest XIDs reach forty million transactions from the wraparound point:
+    oldest XIDs reach one hundred million transactions from the wraparound point:
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
@@ -824,7 +824,7 @@ HINT:  Execute a database-wide VACUUM in that database.
 
     <para>
      Similar to the XID case, if autovacuum fails to clear old MXIDs from a table, the
-     system will begin to emit warning messages when the database's oldest MXIDs reach forty
+     system will begin to emit warning messages when the database's oldest MXIDs reach one hundred
      million transactions from the wraparound point.  And, just as in the XID case, if these
      warnings are ignored, the system will refuse to generate new MXIDs once there are fewer
      than three million left until wraparound.
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 63eb2548da1..67810ea489a 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -2327,16 +2327,16 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 		multiStopLimit -= FirstMultiXactId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M multis of data
+	 * We'll start complaining loudly when we get within 100M multis of data
 	 * loss.  This is kind of arbitrary, but if you let your gas gauge get
-	 * down to 2% of full, would you be looking for the next gas station?  We
+	 * down to 5% of full, would you be looking for the next gas station?  We
 	 * need to be fairly liberal about this number because there are lots of
 	 * scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	multiWarnLimit = multiWrapLimit - 40000000;
+	multiWarnLimit = multiWrapLimit - 100000000;
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 962396bae10..5585381bc8c 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -411,16 +411,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		xidStopLimit -= FirstNormalTransactionId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M transactions of
+	 * We'll start complaining loudly when we get within 100M transactions of
 	 * data loss.  This is kind of arbitrary, but if you let your gas gauge
-	 * get down to 2% of full, would you be looking for the next gas station?
+	 * get down to 5% of full, would you be looking for the next gas station?
 	 * We need to be fairly liberal about this number because there are lots
 	 * of scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	xidWarnLimit = xidWrapLimit - 40000000;
+	xidWarnLimit = xidWrapLimit - 100000000;
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
-- 
2.39.5 (Apple Git-154)
From 65c2776462f6023108b1586afc9b9b17927a5bd9 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 10:48:35 -0600
Subject: [PATCH v1 3/3] Perodically emit server logs when fewer than 500M
 remaining transaction IDs.

---
 src/backend/access/transam/multixact.c | 40 +++++++++++++++++++++++---
 src/backend/access/transam/varsup.c    | 40 +++++++++++++++++++++++---
 src/include/access/transam.h           |  5 ++--
 3 files changed, 75 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 67810ea489a..159ae5efb80 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -264,6 +264,7 @@ typedef struct MultiXactStateData
 
 	/* support for anti-wraparound measures */
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -1048,6 +1049,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 	 * If we're past multiVacLimit or the safe threshold for member storage
 	 * space, or we don't know what the safe threshold for member storage is,
 	 * start trying to force autovacuum cycles.
+	 * If we're past multiLogLimit, start issuing logs periodically.
 	 * If we're past multiWarnLimit, start issuing warnings.
 	 * If we're past multiStopLimit, refuse to create new MultiXactIds.
 	 *
@@ -1063,6 +1065,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		MultiXactId multiLogLimit = MultiXactState->multiLogLimit;
 		MultiXactId multiWarnLimit = MultiXactState->multiWarnLimit;
 		MultiXactId multiStopLimit = MultiXactState->multiStopLimit;
 		MultiXactId multiWrapLimit = MultiXactState->multiWrapLimit;
@@ -1106,13 +1109,27 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		if (IsUnderPostmaster && (result % 65536) == 0)
 			SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-		if (!MultiXactIdPrecedes(result, multiWarnLimit))
+		if (!MultiXactIdPrecedes(result, multiWarnLimit) ||
+			(!MultiXactIdPrecedes(result, multiLogLimit) &&
+			 result % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M multis at a time).  Once the warning limit is
+			 * reached, we emit a proper WARNING every time.
+			 */
+			if (!MultiXactIdPrecedes(result, multiWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database \"%s\" must be vacuumed before %u more MultiXactId is used",
 									   "database \"%s\" must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -1123,7 +1140,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database with OID %u must be vacuumed before %u more MultiXactId is used",
 									   "database with OID %u must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -2299,6 +2316,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 					bool is_startup)
 {
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -2340,6 +2358,15 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
+	/*
+	 * We'll start complaining every 1M multis when we get within 500M multis
+	 * of data loss.  The idea is to provide an early warning system that is
+	 * less noisy than multiWarnLimit but provides ample time to react.
+	 */
+	multiLogLimit = multiWrapLimit - 500000000;
+	if (multiLogLimit < FirstMultiXactId)
+		multiLogLimit -= FirstMultiXactId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datminmxid gets to
 	 * be more than autovacuum_multixact_freeze_max_age mxids old.
@@ -2357,6 +2384,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 	MultiXactState->oldestMultiXactId = oldest_datminmxid;
 	MultiXactState->oldestMultiXactDB = oldest_datoid;
 	MultiXactState->multiVacLimit = multiVacLimit;
+	MultiXactState->multiLogLimit = multiLogLimit;
 	MultiXactState->multiWarnLimit = multiWarnLimit;
 	MultiXactState->multiStopLimit = multiStopLimit;
 	MultiXactState->multiWrapLimit = multiWrapLimit;
@@ -2394,7 +2422,11 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 		 needs_offset_vacuum) && IsUnderPostmaster)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with multiLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewMultiXactId() instead.
+	 */
 	if (MultiXactIdPrecedes(multiWarnLimit, curMulti))
 	{
 		char	   *oldest_datname;
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 5585381bc8c..74ba958eb7a 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -112,6 +112,7 @@ GetNewTransactionId(bool isSubXact)
 	 * catastrophic data loss due to XID wraparound.  The basic rules are:
 	 *
 	 * If we're past xidVacLimit, start trying to force autovacuum cycles.
+	 * If we're past xidLogLimit, start issuing logs periodically.
 	 * If we're past xidWarnLimit, start issuing warnings.
 	 * If we're past xidStopLimit, refuse to execute transactions, unless
 	 * we are running in single-user mode (which gives an escape hatch
@@ -129,6 +130,7 @@ GetNewTransactionId(bool isSubXact)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		TransactionId xidLogLimit = TransamVariables->xidLogLimit;
 		TransactionId xidWarnLimit = TransamVariables->xidWarnLimit;
 		TransactionId xidStopLimit = TransamVariables->xidStopLimit;
 		TransactionId xidWrapLimit = TransamVariables->xidWrapLimit;
@@ -165,13 +167,27 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
-		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit) ||
+				 (TransactionIdFollowsOrEquals(xid, xidLogLimit) &&
+				  xid % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M transactions at a time).  Once the warning
+			 * limit is reached, we emit a proper WARNING every time.
+			 */
+			if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
@@ -180,7 +196,7 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
@@ -376,6 +392,7 @@ void
 SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 {
 	TransactionId xidVacLimit;
+	TransactionId xidLogLimit;
 	TransactionId xidWarnLimit;
 	TransactionId xidStopLimit;
 	TransactionId xidWrapLimit;
@@ -424,6 +441,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
+	/*
+	 * We'll start complaining every 1M transactions when we get within 500M
+	 * transactions of data loss.  The idea is to provide an early warning
+	 * system that is less noisy than xidWarnLimit but provides ample time to
+	 * react.
+	 */
+	xidLogLimit = xidWrapLimit - 500000000;
+	if (xidLogLimit < FirstNormalTransactionId)
+		xidLogLimit -= FirstNormalTransactionId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datfrozenxid gets
 	 * to be more than autovacuum_freeze_max_age transactions old.
@@ -447,6 +474,7 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 	TransamVariables->oldestXid = oldest_datfrozenxid;
 	TransamVariables->xidVacLimit = xidVacLimit;
+	TransamVariables->xidLogLimit = xidLogLimit;
 	TransamVariables->xidWarnLimit = xidWarnLimit;
 	TransamVariables->xidStopLimit = xidStopLimit;
 	TransamVariables->xidWrapLimit = xidWrapLimit;
@@ -470,7 +498,11 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		IsUnderPostmaster && !InRecovery)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with xidLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewTransactionId() instead.
+	 */
 	if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit) && !InRecovery)
 	{
 		char	   *oldest_datname;
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index c9e20418275..a1bd4259f86 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -203,8 +203,8 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  * LWLocks.
  *
  * Note: xidWrapLimit and oldestXidDB are not "active" values, but are
- * used just to generate useful messages when xidWarnLimit or xidStopLimit
- * are exceeded.
+ * used just to generate useful messages when xidLogLimit, xidWarnLimit, or
+ * xidStopLimit are exceeded.
  */
 typedef struct TransamVariablesData
 {
@@ -221,6 +221,7 @@ typedef struct TransamVariablesData
 
 	TransactionId oldestXid;	/* cluster-wide minimum datfrozenxid */
 	TransactionId xidVacLimit;	/* start forcing autovacuums here */
+	TransactionId xidLogLimit;	/* start logging periodically here */
 	TransactionId xidWarnLimit; /* start complaining here */
 	TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */
 	TransactionId xidWrapLimit; /* where the world ends */
-- 
2.39.5 (Apple Git-154)

Attachments:

  [text/plain] v1-0001-Add-percentage-of-transaction-IDs-that-are-availa.patch (6.5K, ../../aRdhSSFb9zZH_0zc@nathan/2-v1-0001-Add-percentage-of-transaction-IDs-that-are-availa.patch)
  download | inline diff:
From 4fd750a67e98cb7b86970ecca5ff3a26fd1f7690 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 09:59:15 -0600
Subject: [PATCH v1 1/3] Add percentage of transaction IDs that are available
 to wraparound warnings.

---
 doc/src/sgml/maintenance.sgml          |  1 +
 src/backend/access/transam/multixact.c | 10 ++++++++++
 src/backend/access/transam/varsup.c    |  8 ++++++++
 3 files changed, 19 insertions(+)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 120bac8875f..b89278ef032 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,6 +674,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
+DETAIL:  Approximately 1.86% of transactions IDs are available for use.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 9d5f130af7e..63eb2548da1 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1118,6 +1118,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / PG_INT32_MAX * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -1127,6 +1129,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / PG_INT32_MAX * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -1225,6 +1229,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 							   MultiXactState->offsetStopLimit - nextOffset + nmembers,
 							   MultiXactState->oldestMultiXactDB,
 							   MultiXactState->offsetStopLimit - nextOffset + nmembers),
+				 errdetail("Approximately %.2f%% of multixact members are available for use.",
+						   (double) (MultiXactState->offsetStopLimit - nextOffset + nmembers) / PG_INT32_MAX * 100),
 				 errhint("Execute a database-wide VACUUM in that database with reduced \"vacuum_multixact_freeze_min_age\" and \"vacuum_multixact_freeze_table_age\" settings.")));
 
 	ExtendMultiXactMember(nextOffset, nmembers);
@@ -2414,6 +2420,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / PG_INT32_MAX * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -2423,6 +2431,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / PG_INT32_MAX * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index f8c4dada7c9..962396bae10 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -175,6 +175,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / PG_INT32_MAX * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -182,6 +184,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / PG_INT32_MAX * 100),
 						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -490,6 +494,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / PG_INT32_MAX * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -497,6 +503,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / PG_INT32_MAX * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
-- 
2.39.5 (Apple Git-154)

  [text/plain] v1-0002-Bump-transaction-ID-limit-to-warn-at-100M.patch (4.2K, ../../aRdhSSFb9zZH_0zc@nathan/3-v1-0002-Bump-transaction-ID-limit-to-warn-at-100M.patch)
  download | inline diff:
From 6555ccd8910420f365a253f03327e0067bc0ca66 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 10:28:52 -0600
Subject: [PATCH v1 2/3] Bump transaction ID limit to warn at 100M.

---
 doc/src/sgml/maintenance.sgml          | 4 ++--
 src/backend/access/transam/multixact.c | 6 +++---
 src/backend/access/transam/varsup.c    | 6 +++---
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index b89278ef032..8a12bd5f65a 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -670,7 +670,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
    <para>
     If for some reason autovacuum fails to clear old XIDs from a table, the
     system will begin to emit warning messages like this when the database's
-    oldest XIDs reach forty million transactions from the wraparound point:
+    oldest XIDs reach one hundred million transactions from the wraparound point:
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
@@ -824,7 +824,7 @@ HINT:  Execute a database-wide VACUUM in that database.
 
     <para>
      Similar to the XID case, if autovacuum fails to clear old MXIDs from a table, the
-     system will begin to emit warning messages when the database's oldest MXIDs reach forty
+     system will begin to emit warning messages when the database's oldest MXIDs reach one hundred
      million transactions from the wraparound point.  And, just as in the XID case, if these
      warnings are ignored, the system will refuse to generate new MXIDs once there are fewer
      than three million left until wraparound.
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 63eb2548da1..67810ea489a 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -2327,16 +2327,16 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 		multiStopLimit -= FirstMultiXactId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M multis of data
+	 * We'll start complaining loudly when we get within 100M multis of data
 	 * loss.  This is kind of arbitrary, but if you let your gas gauge get
-	 * down to 2% of full, would you be looking for the next gas station?  We
+	 * down to 5% of full, would you be looking for the next gas station?  We
 	 * need to be fairly liberal about this number because there are lots of
 	 * scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	multiWarnLimit = multiWrapLimit - 40000000;
+	multiWarnLimit = multiWrapLimit - 100000000;
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 962396bae10..5585381bc8c 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -411,16 +411,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		xidStopLimit -= FirstNormalTransactionId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M transactions of
+	 * We'll start complaining loudly when we get within 100M transactions of
 	 * data loss.  This is kind of arbitrary, but if you let your gas gauge
-	 * get down to 2% of full, would you be looking for the next gas station?
+	 * get down to 5% of full, would you be looking for the next gas station?
 	 * We need to be fairly liberal about this number because there are lots
 	 * of scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	xidWarnLimit = xidWrapLimit - 40000000;
+	xidWarnLimit = xidWrapLimit - 100000000;
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
-- 
2.39.5 (Apple Git-154)

  [text/plain] v1-0003-Perodically-emit-server-logs-when-fewer-than-500M.patch (11.2K, ../../aRdhSSFb9zZH_0zc@nathan/4-v1-0003-Perodically-emit-server-logs-when-fewer-than-500M.patch)
  download | inline diff:
From 65c2776462f6023108b1586afc9b9b17927a5bd9 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 10:48:35 -0600
Subject: [PATCH v1 3/3] Perodically emit server logs when fewer than 500M
 remaining transaction IDs.

---
 src/backend/access/transam/multixact.c | 40 +++++++++++++++++++++++---
 src/backend/access/transam/varsup.c    | 40 +++++++++++++++++++++++---
 src/include/access/transam.h           |  5 ++--
 3 files changed, 75 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 67810ea489a..159ae5efb80 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -264,6 +264,7 @@ typedef struct MultiXactStateData
 
 	/* support for anti-wraparound measures */
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -1048,6 +1049,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 	 * If we're past multiVacLimit or the safe threshold for member storage
 	 * space, or we don't know what the safe threshold for member storage is,
 	 * start trying to force autovacuum cycles.
+	 * If we're past multiLogLimit, start issuing logs periodically.
 	 * If we're past multiWarnLimit, start issuing warnings.
 	 * If we're past multiStopLimit, refuse to create new MultiXactIds.
 	 *
@@ -1063,6 +1065,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		MultiXactId multiLogLimit = MultiXactState->multiLogLimit;
 		MultiXactId multiWarnLimit = MultiXactState->multiWarnLimit;
 		MultiXactId multiStopLimit = MultiXactState->multiStopLimit;
 		MultiXactId multiWrapLimit = MultiXactState->multiWrapLimit;
@@ -1106,13 +1109,27 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		if (IsUnderPostmaster && (result % 65536) == 0)
 			SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-		if (!MultiXactIdPrecedes(result, multiWarnLimit))
+		if (!MultiXactIdPrecedes(result, multiWarnLimit) ||
+			(!MultiXactIdPrecedes(result, multiLogLimit) &&
+			 result % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M multis at a time).  Once the warning limit is
+			 * reached, we emit a proper WARNING every time.
+			 */
+			if (!MultiXactIdPrecedes(result, multiWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database \"%s\" must be vacuumed before %u more MultiXactId is used",
 									   "database \"%s\" must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -1123,7 +1140,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database with OID %u must be vacuumed before %u more MultiXactId is used",
 									   "database with OID %u must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -2299,6 +2316,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 					bool is_startup)
 {
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -2340,6 +2358,15 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
+	/*
+	 * We'll start complaining every 1M multis when we get within 500M multis
+	 * of data loss.  The idea is to provide an early warning system that is
+	 * less noisy than multiWarnLimit but provides ample time to react.
+	 */
+	multiLogLimit = multiWrapLimit - 500000000;
+	if (multiLogLimit < FirstMultiXactId)
+		multiLogLimit -= FirstMultiXactId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datminmxid gets to
 	 * be more than autovacuum_multixact_freeze_max_age mxids old.
@@ -2357,6 +2384,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 	MultiXactState->oldestMultiXactId = oldest_datminmxid;
 	MultiXactState->oldestMultiXactDB = oldest_datoid;
 	MultiXactState->multiVacLimit = multiVacLimit;
+	MultiXactState->multiLogLimit = multiLogLimit;
 	MultiXactState->multiWarnLimit = multiWarnLimit;
 	MultiXactState->multiStopLimit = multiStopLimit;
 	MultiXactState->multiWrapLimit = multiWrapLimit;
@@ -2394,7 +2422,11 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid,
 		 needs_offset_vacuum) && IsUnderPostmaster)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with multiLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewMultiXactId() instead.
+	 */
 	if (MultiXactIdPrecedes(multiWarnLimit, curMulti))
 	{
 		char	   *oldest_datname;
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 5585381bc8c..74ba958eb7a 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -112,6 +112,7 @@ GetNewTransactionId(bool isSubXact)
 	 * catastrophic data loss due to XID wraparound.  The basic rules are:
 	 *
 	 * If we're past xidVacLimit, start trying to force autovacuum cycles.
+	 * If we're past xidLogLimit, start issuing logs periodically.
 	 * If we're past xidWarnLimit, start issuing warnings.
 	 * If we're past xidStopLimit, refuse to execute transactions, unless
 	 * we are running in single-user mode (which gives an escape hatch
@@ -129,6 +130,7 @@ GetNewTransactionId(bool isSubXact)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		TransactionId xidLogLimit = TransamVariables->xidLogLimit;
 		TransactionId xidWarnLimit = TransamVariables->xidWarnLimit;
 		TransactionId xidStopLimit = TransamVariables->xidStopLimit;
 		TransactionId xidWrapLimit = TransamVariables->xidWrapLimit;
@@ -165,13 +167,27 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
-		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit) ||
+				 (TransactionIdFollowsOrEquals(xid, xidLogLimit) &&
+				  xid % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M transactions at a time).  Once the warning
+			 * limit is reached, we emit a proper WARNING every time.
+			 */
+			if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
@@ -180,7 +196,7 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
@@ -376,6 +392,7 @@ void
 SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 {
 	TransactionId xidVacLimit;
+	TransactionId xidLogLimit;
 	TransactionId xidWarnLimit;
 	TransactionId xidStopLimit;
 	TransactionId xidWrapLimit;
@@ -424,6 +441,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
+	/*
+	 * We'll start complaining every 1M transactions when we get within 500M
+	 * transactions of data loss.  The idea is to provide an early warning
+	 * system that is less noisy than xidWarnLimit but provides ample time to
+	 * react.
+	 */
+	xidLogLimit = xidWrapLimit - 500000000;
+	if (xidLogLimit < FirstNormalTransactionId)
+		xidLogLimit -= FirstNormalTransactionId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datfrozenxid gets
 	 * to be more than autovacuum_freeze_max_age transactions old.
@@ -447,6 +474,7 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 	TransamVariables->oldestXid = oldest_datfrozenxid;
 	TransamVariables->xidVacLimit = xidVacLimit;
+	TransamVariables->xidLogLimit = xidLogLimit;
 	TransamVariables->xidWarnLimit = xidWarnLimit;
 	TransamVariables->xidStopLimit = xidStopLimit;
 	TransamVariables->xidWrapLimit = xidWrapLimit;
@@ -470,7 +498,11 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		IsUnderPostmaster && !InRecovery)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with xidLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewTransactionId() instead.
+	 */
 	if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit) && !InRecovery)
 	{
 		char	   *oldest_datname;
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index c9e20418275..a1bd4259f86 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -203,8 +203,8 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  * LWLocks.
  *
  * Note: xidWrapLimit and oldestXidDB are not "active" values, but are
- * used just to generate useful messages when xidWarnLimit or xidStopLimit
- * are exceeded.
+ * used just to generate useful messages when xidLogLimit, xidWarnLimit, or
+ * xidStopLimit are exceeded.
  */
 typedef struct TransamVariablesData
 {
@@ -221,6 +221,7 @@ typedef struct TransamVariablesData
 
 	TransactionId oldestXid;	/* cluster-wide minimum datfrozenxid */
 	TransactionId xidVacLimit;	/* start forcing autovacuums here */
+	TransactionId xidLogLimit;	/* start logging periodically here */
 	TransactionId xidWarnLimit; /* start complaining here */
 	TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */
 	TransactionId xidWrapLimit; /* where the world ends */
-- 
2.39.5 (Apple Git-154)

^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
@ 2025-12-11 20:28 ` Nathan Bossart <nathandbossart@gmail.com>
  2025-12-12 02:59   ` Re: enhance wraparound warnings Chao Li <li.evan.chao@gmail.com>
  1 sibling, 1 reply; 25+ messages in thread

From: Nathan Bossart @ 2025-12-11 20:28 UTC (permalink / raw)
  To: pgsql-hackers

rebased

-- 
nathan
From 407a7e91f09b657f048f4af7cff52f1b2f6cc29b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 09:59:15 -0600
Subject: [PATCH v2 1/3] Add percentage of transaction IDs that are available
 to wraparound warnings.

---
 doc/src/sgml/maintenance.sgml          | 1 +
 src/backend/access/transam/multixact.c | 8 ++++++++
 src/backend/access/transam/varsup.c    | 8 ++++++++
 3 files changed, 17 insertions(+)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 08e6489afb8..c8ba94303f1 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,6 +674,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
+DETAIL:  Approximately 1.86% of transactions IDs are available for use.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 8ba2f4529dc..e1ac4bf4c0b 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1040,6 +1040,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / PG_INT32_MAX * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -1049,6 +1051,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / PG_INT32_MAX * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -2166,6 +2170,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / PG_INT32_MAX * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -2175,6 +2181,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / PG_INT32_MAX * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index f8c4dada7c9..962396bae10 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -175,6 +175,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / PG_INT32_MAX * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -182,6 +184,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / PG_INT32_MAX * 100),
 						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -490,6 +494,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / PG_INT32_MAX * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -497,6 +503,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / PG_INT32_MAX * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
-- 
2.39.5 (Apple Git-154)
From 0c87a24dba1730cf5a8e3d6d8bee8446863cff07 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 10:28:52 -0600
Subject: [PATCH v2 2/3] Bump transaction ID limit to warn at 100M.

---
 doc/src/sgml/maintenance.sgml          | 4 ++--
 src/backend/access/transam/multixact.c | 6 +++---
 src/backend/access/transam/varsup.c    | 6 +++---
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index c8ba94303f1..c23e0a6e260 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -670,7 +670,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
    <para>
     If for some reason autovacuum fails to clear old XIDs from a table, the
     system will begin to emit warning messages like this when the database's
-    oldest XIDs reach forty million transactions from the wraparound point:
+    oldest XIDs reach one hundred million transactions from the wraparound point:
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
@@ -824,7 +824,7 @@ HINT:  Execute a database-wide VACUUM in that database.
 
     <para>
      Similar to the XID case, if autovacuum fails to clear old MXIDs from a table, the
-     system will begin to emit warning messages when the database's oldest MXIDs reach forty
+     system will begin to emit warning messages when the database's oldest MXIDs reach one hundred
      million transactions from the wraparound point.  And, just as in the XID case, if these
      warnings are ignored, the system will refuse to generate new MXIDs once there are fewer
      than three million left until wraparound.
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index e1ac4bf4c0b..42bce35c887 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -2072,16 +2072,16 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 		multiStopLimit -= FirstMultiXactId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M multis of data
+	 * We'll start complaining loudly when we get within 100M multis of data
 	 * loss.  This is kind of arbitrary, but if you let your gas gauge get
-	 * down to 2% of full, would you be looking for the next gas station?  We
+	 * down to 5% of full, would you be looking for the next gas station?  We
 	 * need to be fairly liberal about this number because there are lots of
 	 * scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	multiWarnLimit = multiWrapLimit - 40000000;
+	multiWarnLimit = multiWrapLimit - 100000000;
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 962396bae10..5585381bc8c 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -411,16 +411,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		xidStopLimit -= FirstNormalTransactionId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M transactions of
+	 * We'll start complaining loudly when we get within 100M transactions of
 	 * data loss.  This is kind of arbitrary, but if you let your gas gauge
-	 * get down to 2% of full, would you be looking for the next gas station?
+	 * get down to 5% of full, would you be looking for the next gas station?
 	 * We need to be fairly liberal about this number because there are lots
 	 * of scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	xidWarnLimit = xidWrapLimit - 40000000;
+	xidWarnLimit = xidWrapLimit - 100000000;
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
-- 
2.39.5 (Apple Git-154)
From 0a00eb8bd7d45e915aa2accb279e12f3b925bffa Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 10:48:35 -0600
Subject: [PATCH v2 3/3] Perodically emit server logs when fewer than 500M
 remaining transaction IDs.

---
 src/backend/access/transam/multixact.c | 40 +++++++++++++++++++++++---
 src/backend/access/transam/varsup.c    | 40 +++++++++++++++++++++++---
 src/include/access/transam.h           |  5 ++--
 3 files changed, 75 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 42bce35c887..1b53a4b222b 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -147,6 +147,7 @@ typedef struct MultiXactStateData
 
 	/* support for anti-wraparound measures */
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -970,6 +971,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 	 * If we're past multiVacLimit or the safe threshold for member storage
 	 * space, or we don't know what the safe threshold for member storage is,
 	 * start trying to force autovacuum cycles.
+	 * If we're past multiLogLimit, start issuing logs periodically.
 	 * If we're past multiWarnLimit, start issuing warnings.
 	 * If we're past multiStopLimit, refuse to create new MultiXactIds.
 	 *
@@ -985,6 +987,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		MultiXactId multiLogLimit = MultiXactState->multiLogLimit;
 		MultiXactId multiWarnLimit = MultiXactState->multiWarnLimit;
 		MultiXactId multiStopLimit = MultiXactState->multiStopLimit;
 		MultiXactId multiWrapLimit = MultiXactState->multiWrapLimit;
@@ -1028,13 +1031,27 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		if (IsUnderPostmaster && (result % 65536) == 0)
 			SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-		if (!MultiXactIdPrecedes(result, multiWarnLimit))
+		if (!MultiXactIdPrecedes(result, multiWarnLimit) ||
+			(!MultiXactIdPrecedes(result, multiLogLimit) &&
+			 result % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M multis at a time).  Once the warning limit is
+			 * reached, we emit a proper WARNING every time.
+			 */
+			if (!MultiXactIdPrecedes(result, multiWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database \"%s\" must be vacuumed before %u more MultiXactId is used",
 									   "database \"%s\" must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -1045,7 +1062,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database with OID %u must be vacuumed before %u more MultiXactId is used",
 									   "database with OID %u must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -2047,6 +2064,7 @@ void
 SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 {
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -2085,6 +2103,15 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
+	/*
+	 * We'll start complaining every 1M multis when we get within 500M multis
+	 * of data loss.  The idea is to provide an early warning system that is
+	 * less noisy than multiWarnLimit but provides ample time to react.
+	 */
+	multiLogLimit = multiWrapLimit - 500000000;
+	if (multiLogLimit < FirstMultiXactId)
+		multiLogLimit -= FirstMultiXactId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datminmxid gets to
 	 * be more than autovacuum_multixact_freeze_max_age mxids old.
@@ -2102,6 +2129,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	MultiXactState->oldestMultiXactId = oldest_datminmxid;
 	MultiXactState->oldestMultiXactDB = oldest_datoid;
 	MultiXactState->multiVacLimit = multiVacLimit;
+	MultiXactState->multiLogLimit = multiLogLimit;
 	MultiXactState->multiWarnLimit = multiWarnLimit;
 	MultiXactState->multiStopLimit = multiStopLimit;
 	MultiXactState->multiWrapLimit = multiWrapLimit;
@@ -2144,7 +2172,11 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	if (MultiXactIdPrecedes(multiVacLimit, curMulti) && IsUnderPostmaster)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with multiLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewMultiXactId() instead.
+	 */
 	if (MultiXactIdPrecedes(multiWarnLimit, curMulti))
 	{
 		char	   *oldest_datname;
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 5585381bc8c..74ba958eb7a 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -112,6 +112,7 @@ GetNewTransactionId(bool isSubXact)
 	 * catastrophic data loss due to XID wraparound.  The basic rules are:
 	 *
 	 * If we're past xidVacLimit, start trying to force autovacuum cycles.
+	 * If we're past xidLogLimit, start issuing logs periodically.
 	 * If we're past xidWarnLimit, start issuing warnings.
 	 * If we're past xidStopLimit, refuse to execute transactions, unless
 	 * we are running in single-user mode (which gives an escape hatch
@@ -129,6 +130,7 @@ GetNewTransactionId(bool isSubXact)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		TransactionId xidLogLimit = TransamVariables->xidLogLimit;
 		TransactionId xidWarnLimit = TransamVariables->xidWarnLimit;
 		TransactionId xidStopLimit = TransamVariables->xidStopLimit;
 		TransactionId xidWrapLimit = TransamVariables->xidWrapLimit;
@@ -165,13 +167,27 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
-		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit) ||
+				 (TransactionIdFollowsOrEquals(xid, xidLogLimit) &&
+				  xid % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M transactions at a time).  Once the warning
+			 * limit is reached, we emit a proper WARNING every time.
+			 */
+			if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
@@ -180,7 +196,7 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
@@ -376,6 +392,7 @@ void
 SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 {
 	TransactionId xidVacLimit;
+	TransactionId xidLogLimit;
 	TransactionId xidWarnLimit;
 	TransactionId xidStopLimit;
 	TransactionId xidWrapLimit;
@@ -424,6 +441,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
+	/*
+	 * We'll start complaining every 1M transactions when we get within 500M
+	 * transactions of data loss.  The idea is to provide an early warning
+	 * system that is less noisy than xidWarnLimit but provides ample time to
+	 * react.
+	 */
+	xidLogLimit = xidWrapLimit - 500000000;
+	if (xidLogLimit < FirstNormalTransactionId)
+		xidLogLimit -= FirstNormalTransactionId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datfrozenxid gets
 	 * to be more than autovacuum_freeze_max_age transactions old.
@@ -447,6 +474,7 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 	TransamVariables->oldestXid = oldest_datfrozenxid;
 	TransamVariables->xidVacLimit = xidVacLimit;
+	TransamVariables->xidLogLimit = xidLogLimit;
 	TransamVariables->xidWarnLimit = xidWarnLimit;
 	TransamVariables->xidStopLimit = xidStopLimit;
 	TransamVariables->xidWrapLimit = xidWrapLimit;
@@ -470,7 +498,11 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		IsUnderPostmaster && !InRecovery)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with xidLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewTransactionId() instead.
+	 */
 	if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit) && !InRecovery)
 	{
 		char	   *oldest_datname;
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index c9e20418275..a1bd4259f86 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -203,8 +203,8 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  * LWLocks.
  *
  * Note: xidWrapLimit and oldestXidDB are not "active" values, but are
- * used just to generate useful messages when xidWarnLimit or xidStopLimit
- * are exceeded.
+ * used just to generate useful messages when xidLogLimit, xidWarnLimit, or
+ * xidStopLimit are exceeded.
  */
 typedef struct TransamVariablesData
 {
@@ -221,6 +221,7 @@ typedef struct TransamVariablesData
 
 	TransactionId oldestXid;	/* cluster-wide minimum datfrozenxid */
 	TransactionId xidVacLimit;	/* start forcing autovacuums here */
+	TransactionId xidLogLimit;	/* start logging periodically here */
 	TransactionId xidWarnLimit; /* start complaining here */
 	TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */
 	TransactionId xidWrapLimit; /* where the world ends */
-- 
2.39.5 (Apple Git-154)

Attachments:

  [text/plain] v2-0001-Add-percentage-of-transaction-IDs-that-are-availa.patch (5.9K, ../../aTspbukXBQ_o2KE-@nathan/2-v2-0001-Add-percentage-of-transaction-IDs-that-are-availa.patch)
  download | inline diff:
From 407a7e91f09b657f048f4af7cff52f1b2f6cc29b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 09:59:15 -0600
Subject: [PATCH v2 1/3] Add percentage of transaction IDs that are available
 to wraparound warnings.

---
 doc/src/sgml/maintenance.sgml          | 1 +
 src/backend/access/transam/multixact.c | 8 ++++++++
 src/backend/access/transam/varsup.c    | 8 ++++++++
 3 files changed, 17 insertions(+)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 08e6489afb8..c8ba94303f1 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,6 +674,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
+DETAIL:  Approximately 1.86% of transactions IDs are available for use.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 8ba2f4529dc..e1ac4bf4c0b 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1040,6 +1040,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / PG_INT32_MAX * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -1049,6 +1051,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / PG_INT32_MAX * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -2166,6 +2170,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / PG_INT32_MAX * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -2175,6 +2181,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / PG_INT32_MAX * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index f8c4dada7c9..962396bae10 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -175,6 +175,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / PG_INT32_MAX * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -182,6 +184,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / PG_INT32_MAX * 100),
 						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -490,6 +494,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / PG_INT32_MAX * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -497,6 +503,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / PG_INT32_MAX * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
-- 
2.39.5 (Apple Git-154)

  [text/plain] v2-0002-Bump-transaction-ID-limit-to-warn-at-100M.patch (4.2K, ../../aTspbukXBQ_o2KE-@nathan/3-v2-0002-Bump-transaction-ID-limit-to-warn-at-100M.patch)
  download | inline diff:
From 0c87a24dba1730cf5a8e3d6d8bee8446863cff07 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 10:28:52 -0600
Subject: [PATCH v2 2/3] Bump transaction ID limit to warn at 100M.

---
 doc/src/sgml/maintenance.sgml          | 4 ++--
 src/backend/access/transam/multixact.c | 6 +++---
 src/backend/access/transam/varsup.c    | 6 +++---
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index c8ba94303f1..c23e0a6e260 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -670,7 +670,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
    <para>
     If for some reason autovacuum fails to clear old XIDs from a table, the
     system will begin to emit warning messages like this when the database's
-    oldest XIDs reach forty million transactions from the wraparound point:
+    oldest XIDs reach one hundred million transactions from the wraparound point:
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
@@ -824,7 +824,7 @@ HINT:  Execute a database-wide VACUUM in that database.
 
     <para>
      Similar to the XID case, if autovacuum fails to clear old MXIDs from a table, the
-     system will begin to emit warning messages when the database's oldest MXIDs reach forty
+     system will begin to emit warning messages when the database's oldest MXIDs reach one hundred
      million transactions from the wraparound point.  And, just as in the XID case, if these
      warnings are ignored, the system will refuse to generate new MXIDs once there are fewer
      than three million left until wraparound.
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index e1ac4bf4c0b..42bce35c887 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -2072,16 +2072,16 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 		multiStopLimit -= FirstMultiXactId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M multis of data
+	 * We'll start complaining loudly when we get within 100M multis of data
 	 * loss.  This is kind of arbitrary, but if you let your gas gauge get
-	 * down to 2% of full, would you be looking for the next gas station?  We
+	 * down to 5% of full, would you be looking for the next gas station?  We
 	 * need to be fairly liberal about this number because there are lots of
 	 * scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	multiWarnLimit = multiWrapLimit - 40000000;
+	multiWarnLimit = multiWrapLimit - 100000000;
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 962396bae10..5585381bc8c 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -411,16 +411,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		xidStopLimit -= FirstNormalTransactionId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M transactions of
+	 * We'll start complaining loudly when we get within 100M transactions of
 	 * data loss.  This is kind of arbitrary, but if you let your gas gauge
-	 * get down to 2% of full, would you be looking for the next gas station?
+	 * get down to 5% of full, would you be looking for the next gas station?
 	 * We need to be fairly liberal about this number because there are lots
 	 * of scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	xidWarnLimit = xidWrapLimit - 40000000;
+	xidWarnLimit = xidWrapLimit - 100000000;
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
-- 
2.39.5 (Apple Git-154)

  [text/plain] v2-0003-Perodically-emit-server-logs-when-fewer-than-500M.patch (11.2K, ../../aTspbukXBQ_o2KE-@nathan/4-v2-0003-Perodically-emit-server-logs-when-fewer-than-500M.patch)
  download | inline diff:
From 0a00eb8bd7d45e915aa2accb279e12f3b925bffa Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 10:48:35 -0600
Subject: [PATCH v2 3/3] Perodically emit server logs when fewer than 500M
 remaining transaction IDs.

---
 src/backend/access/transam/multixact.c | 40 +++++++++++++++++++++++---
 src/backend/access/transam/varsup.c    | 40 +++++++++++++++++++++++---
 src/include/access/transam.h           |  5 ++--
 3 files changed, 75 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 42bce35c887..1b53a4b222b 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -147,6 +147,7 @@ typedef struct MultiXactStateData
 
 	/* support for anti-wraparound measures */
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -970,6 +971,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 	 * If we're past multiVacLimit or the safe threshold for member storage
 	 * space, or we don't know what the safe threshold for member storage is,
 	 * start trying to force autovacuum cycles.
+	 * If we're past multiLogLimit, start issuing logs periodically.
 	 * If we're past multiWarnLimit, start issuing warnings.
 	 * If we're past multiStopLimit, refuse to create new MultiXactIds.
 	 *
@@ -985,6 +987,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		MultiXactId multiLogLimit = MultiXactState->multiLogLimit;
 		MultiXactId multiWarnLimit = MultiXactState->multiWarnLimit;
 		MultiXactId multiStopLimit = MultiXactState->multiStopLimit;
 		MultiXactId multiWrapLimit = MultiXactState->multiWrapLimit;
@@ -1028,13 +1031,27 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		if (IsUnderPostmaster && (result % 65536) == 0)
 			SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-		if (!MultiXactIdPrecedes(result, multiWarnLimit))
+		if (!MultiXactIdPrecedes(result, multiWarnLimit) ||
+			(!MultiXactIdPrecedes(result, multiLogLimit) &&
+			 result % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M multis at a time).  Once the warning limit is
+			 * reached, we emit a proper WARNING every time.
+			 */
+			if (!MultiXactIdPrecedes(result, multiWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database \"%s\" must be vacuumed before %u more MultiXactId is used",
 									   "database \"%s\" must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -1045,7 +1062,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database with OID %u must be vacuumed before %u more MultiXactId is used",
 									   "database with OID %u must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -2047,6 +2064,7 @@ void
 SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 {
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -2085,6 +2103,15 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
+	/*
+	 * We'll start complaining every 1M multis when we get within 500M multis
+	 * of data loss.  The idea is to provide an early warning system that is
+	 * less noisy than multiWarnLimit but provides ample time to react.
+	 */
+	multiLogLimit = multiWrapLimit - 500000000;
+	if (multiLogLimit < FirstMultiXactId)
+		multiLogLimit -= FirstMultiXactId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datminmxid gets to
 	 * be more than autovacuum_multixact_freeze_max_age mxids old.
@@ -2102,6 +2129,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	MultiXactState->oldestMultiXactId = oldest_datminmxid;
 	MultiXactState->oldestMultiXactDB = oldest_datoid;
 	MultiXactState->multiVacLimit = multiVacLimit;
+	MultiXactState->multiLogLimit = multiLogLimit;
 	MultiXactState->multiWarnLimit = multiWarnLimit;
 	MultiXactState->multiStopLimit = multiStopLimit;
 	MultiXactState->multiWrapLimit = multiWrapLimit;
@@ -2144,7 +2172,11 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	if (MultiXactIdPrecedes(multiVacLimit, curMulti) && IsUnderPostmaster)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with multiLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewMultiXactId() instead.
+	 */
 	if (MultiXactIdPrecedes(multiWarnLimit, curMulti))
 	{
 		char	   *oldest_datname;
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 5585381bc8c..74ba958eb7a 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -112,6 +112,7 @@ GetNewTransactionId(bool isSubXact)
 	 * catastrophic data loss due to XID wraparound.  The basic rules are:
 	 *
 	 * If we're past xidVacLimit, start trying to force autovacuum cycles.
+	 * If we're past xidLogLimit, start issuing logs periodically.
 	 * If we're past xidWarnLimit, start issuing warnings.
 	 * If we're past xidStopLimit, refuse to execute transactions, unless
 	 * we are running in single-user mode (which gives an escape hatch
@@ -129,6 +130,7 @@ GetNewTransactionId(bool isSubXact)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		TransactionId xidLogLimit = TransamVariables->xidLogLimit;
 		TransactionId xidWarnLimit = TransamVariables->xidWarnLimit;
 		TransactionId xidStopLimit = TransamVariables->xidStopLimit;
 		TransactionId xidWrapLimit = TransamVariables->xidWrapLimit;
@@ -165,13 +167,27 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
-		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit) ||
+				 (TransactionIdFollowsOrEquals(xid, xidLogLimit) &&
+				  xid % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M transactions at a time).  Once the warning
+			 * limit is reached, we emit a proper WARNING every time.
+			 */
+			if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
@@ -180,7 +196,7 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
@@ -376,6 +392,7 @@ void
 SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 {
 	TransactionId xidVacLimit;
+	TransactionId xidLogLimit;
 	TransactionId xidWarnLimit;
 	TransactionId xidStopLimit;
 	TransactionId xidWrapLimit;
@@ -424,6 +441,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
+	/*
+	 * We'll start complaining every 1M transactions when we get within 500M
+	 * transactions of data loss.  The idea is to provide an early warning
+	 * system that is less noisy than xidWarnLimit but provides ample time to
+	 * react.
+	 */
+	xidLogLimit = xidWrapLimit - 500000000;
+	if (xidLogLimit < FirstNormalTransactionId)
+		xidLogLimit -= FirstNormalTransactionId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datfrozenxid gets
 	 * to be more than autovacuum_freeze_max_age transactions old.
@@ -447,6 +474,7 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 	TransamVariables->oldestXid = oldest_datfrozenxid;
 	TransamVariables->xidVacLimit = xidVacLimit;
+	TransamVariables->xidLogLimit = xidLogLimit;
 	TransamVariables->xidWarnLimit = xidWarnLimit;
 	TransamVariables->xidStopLimit = xidStopLimit;
 	TransamVariables->xidWrapLimit = xidWrapLimit;
@@ -470,7 +498,11 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		IsUnderPostmaster && !InRecovery)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with xidLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewTransactionId() instead.
+	 */
 	if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit) && !InRecovery)
 	{
 		char	   *oldest_datname;
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index c9e20418275..a1bd4259f86 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -203,8 +203,8 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  * LWLocks.
  *
  * Note: xidWrapLimit and oldestXidDB are not "active" values, but are
- * used just to generate useful messages when xidWarnLimit or xidStopLimit
- * are exceeded.
+ * used just to generate useful messages when xidLogLimit, xidWarnLimit, or
+ * xidStopLimit are exceeded.
  */
 typedef struct TransamVariablesData
 {
@@ -221,6 +221,7 @@ typedef struct TransamVariablesData
 
 	TransactionId oldestXid;	/* cluster-wide minimum datfrozenxid */
 	TransactionId xidVacLimit;	/* start forcing autovacuums here */
+	TransactionId xidLogLimit;	/* start logging periodically here */
 	TransactionId xidWarnLimit; /* start complaining here */
 	TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */
 	TransactionId xidWrapLimit; /* where the world ends */
-- 
2.39.5 (Apple Git-154)

^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2025-12-11 20:28 ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
@ 2025-12-12 02:59   ` Chao Li <li.evan.chao@gmail.com>
  2025-12-12 19:19     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Chao Li @ 2025-12-12 02:59 UTC (permalink / raw)
  To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: pgsql-hackers

Hi Nathan,

I just reviewed the patch. My comments are mainly in 0001, and a few nits on 0003. For 0002, the code change is quite straightforward, I am not sure the value bumping to has been discussed.

> On Dec 12, 2025, at 04:28, Nathan Bossart <nathandbossart@gmail.com> wrote:
> 
> rebased
> 
> -- 
> nathan
> <v2-0001-Add-percentage-of-transaction-IDs-that-are-availa.patch><v2-0002-Bump-transaction-ID-limit-to-warn-at-100M.patch><v2-0003-Perodically-emit-server-logs-when-fewer-than-500M.patch>

1 - 0001
```
+								   (double) (multiWrapLimit - result) / PG_INT32_MAX * 100),
```

I don’t feel good with using PG_INT32_MAX as denominator, though the value is correct.

Looking at the code of how xidWrapLimit is calculated:
```
	/*
	 * The place where we actually get into deep trouble is halfway around
	 * from the oldest potentially-existing XID.  (This calculation is
	 * probably off by one or two counts, because the special XIDs reduce the
	 * size of the loop a little bit.  But we throw in plenty of slop below,
	 * so it doesn't matter.)
	 */
	xidWrapLimit = oldest_datfrozenxid + (MaxTransactionId >> 1);
	if (xidWrapLimit < FirstNormalTransactionId)
		xidWrapLimit += FirstNormalTransactionId;
```

Where "(MaxTransactionId >> 1)” has the same value as PG_INT32_MAX. But if one day xid is changed to 64 bits, that code doesn’t need to updated, while these patched code will need to be updated.

So, can we define a const in transom.h like:
```
#define MaxTransactionId ((TransactionId) 0xFFFFFFFF)
#define WrapAroundWindow (MaxTransactionId>>1)
```

And use WrapAroundWindow in all places.

2 - 0001
```
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
```

“%.2f%%” shows only 2 digits after dot. xidWrapLimit is roughly 2B, when remaining goes down to 107374, it will shows “0.00%”. IMO, when remaining is a large number, percentage makes more sense, while an exact number is clearer when the number is relatively small. So, can we show both percentage and exact number? Or shows the exact number when percentage is 0.00%?

3 - 0001
```
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
+DETAIL:  Approximately 1.86% of transactions IDs are available for use.
```

Typo: " transactions IDs” => " transaction IDs"

4 - 0003
```
Subject: [PATCH v2 3/3] Perodically emit server logs when fewer than 500M
```

Typo: Perodically => Periodically

5 - 0003
```
+	xidLogLimit = xidWrapLimit - 500000000;
```

Instead of hardcode 500M, do we want to consider autovacuum_freeze_max_age? If a deployment sets autovacuum_freeze_max_age > 500M, then vacuum would be triggered first, then this log can get kinda non-intuitive. But if a vacuum cannot freeze anything tuple, then this log will still make sense. I am not sure. Maybe not a real problem.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/









^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2025-12-11 20:28 ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2025-12-12 02:59   ` Re: enhance wraparound warnings Chao Li <li.evan.chao@gmail.com>
@ 2025-12-12 19:19     ` Nathan Bossart <nathandbossart@gmail.com>
  0 siblings, 0 replies; 25+ messages in thread

From: Nathan Bossart @ 2025-12-12 19:19 UTC (permalink / raw)
  To: Chao Li <li.evan.chao@gmail.com>; +Cc: pgsql-hackers

On Fri, Dec 12, 2025 at 10:59:53AM +0800, Chao Li wrote:
> I just reviewed the patch. My comments are mainly in 0001, and a few nits
> on 0003. For 0002, the code change is quite straightforward, I am not
> sure the value bumping to has been discussed.

Thanks!

> Where "(MaxTransactionId >> 1)” has the same value as PG_INT32_MAX. But
> if one day xid is changed to 64 bits, that code doesn’t need to updated,
> while these patched code will need to be updated.
> 
> So, can we define a const in transom.h like:
> ```
> #define MaxTransactionId ((TransactionId) 0xFFFFFFFF)
> #define WrapAroundWindow (MaxTransactionId>>1)
> ```
> 
> And use WrapAroundWindow in all places.

I think I'd rather just open-code the (MaxTransactionId / 2) here.  I'm not
too concerned about 64-bit transaction IDs (there's a lot more than this to
change for that), but it does seem like a good idea to be consistent with
nearby code.

> ```
> +						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
> ```
> 
> “%.2f%%” shows only 2 digits after dot. xidWrapLimit is roughly 2B, when
> remaining goes down to 107374, it will shows “0.00%”. IMO, when remaining
> is a large number, percentage makes more sense, while an exact number is
> clearer when the number is relatively small. So, can we show both
> percentage and exact number? Or shows the exact number when percentage is
> 0.00%?

The errmsg part should already show the exact number of IDs remaining.

> ```
> +	xidLogLimit = xidWrapLimit - 500000000;
> ```
> 
> Instead of hardcode 500M, do we want to consider
> autovacuum_freeze_max_age? If a deployment sets autovacuum_freeze_max_age
> > 500M, then vacuum would be triggered first, then this log can get kinda
> non-intuitive. But if a vacuum cannot freeze anything tuple, then this
> log will still make sense. I am not sure. Maybe not a real problem.

IMHO we should still emit warnings about imminent wraparound even if
autovacuum_freeze_max_age is set to totally-inadvisable values.  I think
the behavior you are describing only happens if users set it to north of
1.6B.

-- 
nathan
From c1cb82c3fe80978c668ecc7c57654f62610e0e07 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 09:59:15 -0600
Subject: [PATCH v3 1/3] Add percentage of transaction IDs that are available
 to wraparound warnings.

---
 doc/src/sgml/maintenance.sgml          | 1 +
 src/backend/access/transam/multixact.c | 8 ++++++++
 src/backend/access/transam/varsup.c    | 8 ++++++++
 3 files changed, 17 insertions(+)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 08e6489afb8..257c6a5435d 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,6 +674,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
+DETAIL:  Approximately 1.86% of transaction IDs are available for use.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index f4fab7edfee..39e5b691573 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1017,6 +1017,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -1026,6 +1028,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -2134,6 +2138,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -2143,6 +2149,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index f8c4dada7c9..32961b9acab 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -175,6 +175,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -182,6 +184,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -490,6 +494,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -497,6 +503,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
-- 
2.39.5 (Apple Git-154)
From 1bbbb093e409323b4e63feac79a0e8a805ab6c37 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 12 Dec 2025 13:10:05 -0600
Subject: [PATCH v3 2/3] Bump transaction ID limit to warn at 100M.

---
 doc/src/sgml/maintenance.sgml          | 4 ++--
 src/backend/access/transam/multixact.c | 6 +++---
 src/backend/access/transam/varsup.c    | 6 +++---
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 257c6a5435d..3960a23ad71 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -670,7 +670,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
    <para>
     If for some reason autovacuum fails to clear old XIDs from a table, the
     system will begin to emit warning messages like this when the database's
-    oldest XIDs reach forty million transactions from the wraparound point:
+    oldest XIDs reach one hundred million transactions from the wraparound point:
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
@@ -824,7 +824,7 @@ HINT:  Execute a database-wide VACUUM in that database.
 
     <para>
      Similar to the XID case, if autovacuum fails to clear old MXIDs from a table, the
-     system will begin to emit warning messages when the database's oldest MXIDs reach forty
+     system will begin to emit warning messages when the database's oldest MXIDs reach one hundred
      million transactions from the wraparound point.  And, just as in the XID case, if these
      warnings are ignored, the system will refuse to generate new MXIDs once there are fewer
      than three million left until wraparound.
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 39e5b691573..27a0baab8c7 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -2040,16 +2040,16 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 		multiStopLimit -= FirstMultiXactId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M multis of data
+	 * We'll start complaining loudly when we get within 100M multis of data
 	 * loss.  This is kind of arbitrary, but if you let your gas gauge get
-	 * down to 2% of full, would you be looking for the next gas station?  We
+	 * down to 5% of full, would you be looking for the next gas station?  We
 	 * need to be fairly liberal about this number because there are lots of
 	 * scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	multiWarnLimit = multiWrapLimit - 40000000;
+	multiWarnLimit = multiWrapLimit - 100000000;
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 32961b9acab..98aeea96e8a 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -411,16 +411,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		xidStopLimit -= FirstNormalTransactionId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M transactions of
+	 * We'll start complaining loudly when we get within 100M transactions of
 	 * data loss.  This is kind of arbitrary, but if you let your gas gauge
-	 * get down to 2% of full, would you be looking for the next gas station?
+	 * get down to 5% of full, would you be looking for the next gas station?
 	 * We need to be fairly liberal about this number because there are lots
 	 * of scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	xidWarnLimit = xidWrapLimit - 40000000;
+	xidWarnLimit = xidWrapLimit - 100000000;
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
-- 
2.39.5 (Apple Git-154)
From 2ca1da92e081f767f756f93b3c091cdf6d084a50 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 12 Dec 2025 13:13:55 -0600
Subject: [PATCH v3 3/3] Periodically emit server logs when fewer than 500M
 remaining transaction IDs.

---
 src/backend/access/transam/multixact.c | 40 +++++++++++++++++++++++---
 src/backend/access/transam/varsup.c    | 40 +++++++++++++++++++++++---
 src/include/access/transam.h           |  5 ++--
 3 files changed, 75 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 27a0baab8c7..abd89c3a73d 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -153,6 +153,7 @@ typedef struct MultiXactStateData
 
 	/* support for anti-wraparound measures */
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -947,6 +948,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 	 * If we're past multiVacLimit or the safe threshold for member storage
 	 * space, or we don't know what the safe threshold for member storage is,
 	 * start trying to force autovacuum cycles.
+	 * If we're past multiLogLimit, start issuing logs periodically.
 	 * If we're past multiWarnLimit, start issuing warnings.
 	 * If we're past multiStopLimit, refuse to create new MultiXactIds.
 	 *
@@ -962,6 +964,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		MultiXactId multiLogLimit = MultiXactState->multiLogLimit;
 		MultiXactId multiWarnLimit = MultiXactState->multiWarnLimit;
 		MultiXactId multiStopLimit = MultiXactState->multiStopLimit;
 		MultiXactId multiWrapLimit = MultiXactState->multiWrapLimit;
@@ -1005,13 +1008,27 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		if (IsUnderPostmaster && ((result % 65536) == 0 || result == FirstMultiXactId))
 			SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-		if (!MultiXactIdPrecedes(result, multiWarnLimit))
+		if (!MultiXactIdPrecedes(result, multiWarnLimit) ||
+			(!MultiXactIdPrecedes(result, multiLogLimit) &&
+			 result % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M multis at a time).  Once the warning limit is
+			 * reached, we emit a proper WARNING every time.
+			 */
+			if (!MultiXactIdPrecedes(result, multiWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database \"%s\" must be vacuumed before %u more MultiXactId is used",
 									   "database \"%s\" must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -1022,7 +1039,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database with OID %u must be vacuumed before %u more MultiXactId is used",
 									   "database with OID %u must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -2015,6 +2032,7 @@ void
 SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 {
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -2053,6 +2071,15 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
+	/*
+	 * We'll start complaining every 1M multis when we get within 500M multis
+	 * of data loss.  The idea is to provide an early warning system that is
+	 * less noisy than multiWarnLimit but provides ample time to react.
+	 */
+	multiLogLimit = multiWrapLimit - 500000000;
+	if (multiLogLimit < FirstMultiXactId)
+		multiLogLimit -= FirstMultiXactId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datminmxid gets to
 	 * be more than autovacuum_multixact_freeze_max_age mxids old.
@@ -2070,6 +2097,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	MultiXactState->oldestMultiXactId = oldest_datminmxid;
 	MultiXactState->oldestMultiXactDB = oldest_datoid;
 	MultiXactState->multiVacLimit = multiVacLimit;
+	MultiXactState->multiLogLimit = multiLogLimit;
 	MultiXactState->multiWarnLimit = multiWarnLimit;
 	MultiXactState->multiStopLimit = multiStopLimit;
 	MultiXactState->multiWrapLimit = multiWrapLimit;
@@ -2112,7 +2140,11 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	if (MultiXactIdPrecedes(multiVacLimit, curMulti) && IsUnderPostmaster)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with multiLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewMultiXactId() instead.
+	 */
 	if (MultiXactIdPrecedes(multiWarnLimit, curMulti))
 	{
 		char	   *oldest_datname;
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 98aeea96e8a..0f633cc0e14 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -112,6 +112,7 @@ GetNewTransactionId(bool isSubXact)
 	 * catastrophic data loss due to XID wraparound.  The basic rules are:
 	 *
 	 * If we're past xidVacLimit, start trying to force autovacuum cycles.
+	 * If we're past xidLogLimit, start issuing logs periodically.
 	 * If we're past xidWarnLimit, start issuing warnings.
 	 * If we're past xidStopLimit, refuse to execute transactions, unless
 	 * we are running in single-user mode (which gives an escape hatch
@@ -129,6 +130,7 @@ GetNewTransactionId(bool isSubXact)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		TransactionId xidLogLimit = TransamVariables->xidLogLimit;
 		TransactionId xidWarnLimit = TransamVariables->xidWarnLimit;
 		TransactionId xidStopLimit = TransamVariables->xidStopLimit;
 		TransactionId xidWrapLimit = TransamVariables->xidWrapLimit;
@@ -165,13 +167,27 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
-		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit) ||
+				 (TransactionIdFollowsOrEquals(xid, xidLogLimit) &&
+				  xid % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M transactions at a time).  Once the warning
+			 * limit is reached, we emit a proper WARNING every time.
+			 */
+			if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
@@ -180,7 +196,7 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
@@ -376,6 +392,7 @@ void
 SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 {
 	TransactionId xidVacLimit;
+	TransactionId xidLogLimit;
 	TransactionId xidWarnLimit;
 	TransactionId xidStopLimit;
 	TransactionId xidWrapLimit;
@@ -424,6 +441,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
+	/*
+	 * We'll start complaining every 1M transactions when we get within 500M
+	 * transactions of data loss.  The idea is to provide an early warning
+	 * system that is less noisy than xidWarnLimit but provides ample time to
+	 * react.
+	 */
+	xidLogLimit = xidWrapLimit - 500000000;
+	if (xidLogLimit < FirstNormalTransactionId)
+		xidLogLimit -= FirstNormalTransactionId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datfrozenxid gets
 	 * to be more than autovacuum_freeze_max_age transactions old.
@@ -447,6 +474,7 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 	TransamVariables->oldestXid = oldest_datfrozenxid;
 	TransamVariables->xidVacLimit = xidVacLimit;
+	TransamVariables->xidLogLimit = xidLogLimit;
 	TransamVariables->xidWarnLimit = xidWarnLimit;
 	TransamVariables->xidStopLimit = xidStopLimit;
 	TransamVariables->xidWrapLimit = xidWrapLimit;
@@ -470,7 +498,11 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		IsUnderPostmaster && !InRecovery)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with xidLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewTransactionId() instead.
+	 */
 	if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit) && !InRecovery)
 	{
 		char	   *oldest_datname;
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index c9e20418275..a1bd4259f86 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -203,8 +203,8 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  * LWLocks.
  *
  * Note: xidWrapLimit and oldestXidDB are not "active" values, but are
- * used just to generate useful messages when xidWarnLimit or xidStopLimit
- * are exceeded.
+ * used just to generate useful messages when xidLogLimit, xidWarnLimit, or
+ * xidStopLimit are exceeded.
  */
 typedef struct TransamVariablesData
 {
@@ -221,6 +221,7 @@ typedef struct TransamVariablesData
 
 	TransactionId oldestXid;	/* cluster-wide minimum datfrozenxid */
 	TransactionId xidVacLimit;	/* start forcing autovacuums here */
+	TransactionId xidLogLimit;	/* start logging periodically here */
 	TransactionId xidWarnLimit; /* start complaining here */
 	TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */
 	TransactionId xidWrapLimit; /* where the world ends */
-- 
2.39.5 (Apple Git-154)

Attachments:

  [text/plain] v3-0001-Add-percentage-of-transaction-IDs-that-are-availa.patch (5.9K, ../../aTxquMXltCxCrXgr@nathan/2-v3-0001-Add-percentage-of-transaction-IDs-that-are-availa.patch)
  download | inline diff:
From c1cb82c3fe80978c668ecc7c57654f62610e0e07 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 09:59:15 -0600
Subject: [PATCH v3 1/3] Add percentage of transaction IDs that are available
 to wraparound warnings.

---
 doc/src/sgml/maintenance.sgml          | 1 +
 src/backend/access/transam/multixact.c | 8 ++++++++
 src/backend/access/transam/varsup.c    | 8 ++++++++
 3 files changed, 17 insertions(+)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 08e6489afb8..257c6a5435d 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,6 +674,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
+DETAIL:  Approximately 1.86% of transaction IDs are available for use.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index f4fab7edfee..39e5b691573 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1017,6 +1017,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -1026,6 +1028,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -2134,6 +2138,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -2143,6 +2149,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index f8c4dada7c9..32961b9acab 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -175,6 +175,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -182,6 +184,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -490,6 +494,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -497,6 +503,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
-- 
2.39.5 (Apple Git-154)

  [text/plain] v3-0002-Bump-transaction-ID-limit-to-warn-at-100M.patch (4.2K, ../../aTxquMXltCxCrXgr@nathan/3-v3-0002-Bump-transaction-ID-limit-to-warn-at-100M.patch)
  download | inline diff:
From 1bbbb093e409323b4e63feac79a0e8a805ab6c37 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 12 Dec 2025 13:10:05 -0600
Subject: [PATCH v3 2/3] Bump transaction ID limit to warn at 100M.

---
 doc/src/sgml/maintenance.sgml          | 4 ++--
 src/backend/access/transam/multixact.c | 6 +++---
 src/backend/access/transam/varsup.c    | 6 +++---
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 257c6a5435d..3960a23ad71 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -670,7 +670,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
    <para>
     If for some reason autovacuum fails to clear old XIDs from a table, the
     system will begin to emit warning messages like this when the database's
-    oldest XIDs reach forty million transactions from the wraparound point:
+    oldest XIDs reach one hundred million transactions from the wraparound point:
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
@@ -824,7 +824,7 @@ HINT:  Execute a database-wide VACUUM in that database.
 
     <para>
      Similar to the XID case, if autovacuum fails to clear old MXIDs from a table, the
-     system will begin to emit warning messages when the database's oldest MXIDs reach forty
+     system will begin to emit warning messages when the database's oldest MXIDs reach one hundred
      million transactions from the wraparound point.  And, just as in the XID case, if these
      warnings are ignored, the system will refuse to generate new MXIDs once there are fewer
      than three million left until wraparound.
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 39e5b691573..27a0baab8c7 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -2040,16 +2040,16 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 		multiStopLimit -= FirstMultiXactId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M multis of data
+	 * We'll start complaining loudly when we get within 100M multis of data
 	 * loss.  This is kind of arbitrary, but if you let your gas gauge get
-	 * down to 2% of full, would you be looking for the next gas station?  We
+	 * down to 5% of full, would you be looking for the next gas station?  We
 	 * need to be fairly liberal about this number because there are lots of
 	 * scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	multiWarnLimit = multiWrapLimit - 40000000;
+	multiWarnLimit = multiWrapLimit - 100000000;
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 32961b9acab..98aeea96e8a 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -411,16 +411,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		xidStopLimit -= FirstNormalTransactionId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M transactions of
+	 * We'll start complaining loudly when we get within 100M transactions of
 	 * data loss.  This is kind of arbitrary, but if you let your gas gauge
-	 * get down to 2% of full, would you be looking for the next gas station?
+	 * get down to 5% of full, would you be looking for the next gas station?
 	 * We need to be fairly liberal about this number because there are lots
 	 * of scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	xidWarnLimit = xidWrapLimit - 40000000;
+	xidWarnLimit = xidWrapLimit - 100000000;
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
-- 
2.39.5 (Apple Git-154)

  [text/plain] v3-0003-Periodically-emit-server-logs-when-fewer-than-500.patch (11.2K, ../../aTxquMXltCxCrXgr@nathan/4-v3-0003-Periodically-emit-server-logs-when-fewer-than-500.patch)
  download | inline diff:
From 2ca1da92e081f767f756f93b3c091cdf6d084a50 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 12 Dec 2025 13:13:55 -0600
Subject: [PATCH v3 3/3] Periodically emit server logs when fewer than 500M
 remaining transaction IDs.

---
 src/backend/access/transam/multixact.c | 40 +++++++++++++++++++++++---
 src/backend/access/transam/varsup.c    | 40 +++++++++++++++++++++++---
 src/include/access/transam.h           |  5 ++--
 3 files changed, 75 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 27a0baab8c7..abd89c3a73d 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -153,6 +153,7 @@ typedef struct MultiXactStateData
 
 	/* support for anti-wraparound measures */
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -947,6 +948,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 	 * If we're past multiVacLimit or the safe threshold for member storage
 	 * space, or we don't know what the safe threshold for member storage is,
 	 * start trying to force autovacuum cycles.
+	 * If we're past multiLogLimit, start issuing logs periodically.
 	 * If we're past multiWarnLimit, start issuing warnings.
 	 * If we're past multiStopLimit, refuse to create new MultiXactIds.
 	 *
@@ -962,6 +964,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		MultiXactId multiLogLimit = MultiXactState->multiLogLimit;
 		MultiXactId multiWarnLimit = MultiXactState->multiWarnLimit;
 		MultiXactId multiStopLimit = MultiXactState->multiStopLimit;
 		MultiXactId multiWrapLimit = MultiXactState->multiWrapLimit;
@@ -1005,13 +1008,27 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 		if (IsUnderPostmaster && ((result % 65536) == 0 || result == FirstMultiXactId))
 			SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-		if (!MultiXactIdPrecedes(result, multiWarnLimit))
+		if (!MultiXactIdPrecedes(result, multiWarnLimit) ||
+			(!MultiXactIdPrecedes(result, multiLogLimit) &&
+			 result % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M multis at a time).  Once the warning limit is
+			 * reached, we emit a proper WARNING every time.
+			 */
+			if (!MultiXactIdPrecedes(result, multiWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database \"%s\" must be vacuumed before %u more MultiXactId is used",
 									   "database \"%s\" must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -1022,7 +1039,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg_plural("database with OID %u must be vacuumed before %u more MultiXactId is used",
 									   "database with OID %u must be vacuumed before %u more MultiXactIds are used",
 									   multiWrapLimit - result,
@@ -2015,6 +2032,7 @@ void
 SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 {
 	MultiXactId multiVacLimit;
+	MultiXactId multiLogLimit;
 	MultiXactId multiWarnLimit;
 	MultiXactId multiStopLimit;
 	MultiXactId multiWrapLimit;
@@ -2053,6 +2071,15 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
+	/*
+	 * We'll start complaining every 1M multis when we get within 500M multis
+	 * of data loss.  The idea is to provide an early warning system that is
+	 * less noisy than multiWarnLimit but provides ample time to react.
+	 */
+	multiLogLimit = multiWrapLimit - 500000000;
+	if (multiLogLimit < FirstMultiXactId)
+		multiLogLimit -= FirstMultiXactId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datminmxid gets to
 	 * be more than autovacuum_multixact_freeze_max_age mxids old.
@@ -2070,6 +2097,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	MultiXactState->oldestMultiXactId = oldest_datminmxid;
 	MultiXactState->oldestMultiXactDB = oldest_datoid;
 	MultiXactState->multiVacLimit = multiVacLimit;
+	MultiXactState->multiLogLimit = multiLogLimit;
 	MultiXactState->multiWarnLimit = multiWarnLimit;
 	MultiXactState->multiStopLimit = multiStopLimit;
 	MultiXactState->multiWrapLimit = multiWrapLimit;
@@ -2112,7 +2140,11 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 	if (MultiXactIdPrecedes(multiVacLimit, curMulti) && IsUnderPostmaster)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with multiLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewMultiXactId() instead.
+	 */
 	if (MultiXactIdPrecedes(multiWarnLimit, curMulti))
 	{
 		char	   *oldest_datname;
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 98aeea96e8a..0f633cc0e14 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -112,6 +112,7 @@ GetNewTransactionId(bool isSubXact)
 	 * catastrophic data loss due to XID wraparound.  The basic rules are:
 	 *
 	 * If we're past xidVacLimit, start trying to force autovacuum cycles.
+	 * If we're past xidLogLimit, start issuing logs periodically.
 	 * If we're past xidWarnLimit, start issuing warnings.
 	 * If we're past xidStopLimit, refuse to execute transactions, unless
 	 * we are running in single-user mode (which gives an escape hatch
@@ -129,6 +130,7 @@ GetNewTransactionId(bool isSubXact)
 		 * possibility of deadlock while doing get_database_name(). First,
 		 * copy all the shared values we'll need in this path.
 		 */
+		TransactionId xidLogLimit = TransamVariables->xidLogLimit;
 		TransactionId xidWarnLimit = TransamVariables->xidWarnLimit;
 		TransactionId xidStopLimit = TransamVariables->xidStopLimit;
 		TransactionId xidWrapLimit = TransamVariables->xidWrapLimit;
@@ -165,13 +167,27 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
-		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+		else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit) ||
+				 (TransactionIdFollowsOrEquals(xid, xidLogLimit) &&
+				  xid % 1000000 == 0))
 		{
 			char	   *oldest_datname = get_database_name(oldest_datoid);
+			int			elevel;
+
+			/*
+			 * We only send the periodic warnings to the server log in an
+			 * attempt to avoid confusion from clients (since the WARNING will
+			 * disappear for 1M transactions at a time).  Once the warning
+			 * limit is reached, we emit a proper WARNING every time.
+			 */
+			if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+				elevel = WARNING;
+			else
+				elevel = LOG_SERVER_ONLY;
 
 			/* complain even if that DB has disappeared */
 			if (oldest_datname)
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
@@ -180,7 +196,7 @@ GetNewTransactionId(bool isSubXact)
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
-				ereport(WARNING,
+				ereport(elevel,
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
@@ -376,6 +392,7 @@ void
 SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 {
 	TransactionId xidVacLimit;
+	TransactionId xidLogLimit;
 	TransactionId xidWarnLimit;
 	TransactionId xidStopLimit;
 	TransactionId xidWrapLimit;
@@ -424,6 +441,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
+	/*
+	 * We'll start complaining every 1M transactions when we get within 500M
+	 * transactions of data loss.  The idea is to provide an early warning
+	 * system that is less noisy than xidWarnLimit but provides ample time to
+	 * react.
+	 */
+	xidLogLimit = xidWrapLimit - 500000000;
+	if (xidLogLimit < FirstNormalTransactionId)
+		xidLogLimit -= FirstNormalTransactionId;
+
 	/*
 	 * We'll start trying to force autovacuums when oldest_datfrozenxid gets
 	 * to be more than autovacuum_freeze_max_age transactions old.
@@ -447,6 +474,7 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 	TransamVariables->oldestXid = oldest_datfrozenxid;
 	TransamVariables->xidVacLimit = xidVacLimit;
+	TransamVariables->xidLogLimit = xidLogLimit;
 	TransamVariables->xidWarnLimit = xidWarnLimit;
 	TransamVariables->xidStopLimit = xidStopLimit;
 	TransamVariables->xidWrapLimit = xidWrapLimit;
@@ -470,7 +498,11 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		IsUnderPostmaster && !InRecovery)
 		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
-	/* Give an immediate warning if past the wrap warn point */
+	/*
+	 * Give an immediate warning if past the wrap warn point.  We don't bother
+	 * with xidLogLimit here, as it's unlikely to apply.  We leave that part
+	 * to GetNewTransactionId() instead.
+	 */
 	if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit) && !InRecovery)
 	{
 		char	   *oldest_datname;
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index c9e20418275..a1bd4259f86 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -203,8 +203,8 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  * LWLocks.
  *
  * Note: xidWrapLimit and oldestXidDB are not "active" values, but are
- * used just to generate useful messages when xidWarnLimit or xidStopLimit
- * are exceeded.
+ * used just to generate useful messages when xidLogLimit, xidWarnLimit, or
+ * xidStopLimit are exceeded.
  */
 typedef struct TransamVariablesData
 {
@@ -221,6 +221,7 @@ typedef struct TransamVariablesData
 
 	TransactionId oldestXid;	/* cluster-wide minimum datfrozenxid */
 	TransactionId xidVacLimit;	/* start forcing autovacuums here */
+	TransactionId xidLogLimit;	/* start logging periodically here */
 	TransactionId xidWarnLimit; /* start complaining here */
 	TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */
 	TransactionId xidWrapLimit; /* where the world ends */
-- 
2.39.5 (Apple Git-154)

^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
@ 2026-02-18 07:16 ` Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  1 sibling, 1 reply; 25+ messages in thread

From: Shinya Kato @ 2026-02-18 07:16 UTC (permalink / raw)
  To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: pgsql-hackers

On Sat, Nov 15, 2025 at 2:05 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
> I don't know about you, but I start getting antsy around a quarter tank.
> In any case, I'm told that even 40M transactions aren't enough time to
> react these days.  Attached are a few patches to enhance the wraparound
> warnings.

Thank you for the patch!

> * 0001 adds a "percent remaining" detail message to the existing WARNING.
> The idea is that "1.86% of transaction IDs" is both easier to understand
> and better indicates urgency than "39985967 transactions".

I like this idea and this is helpful information for DBA. 0001 looks good to me.

> * 0002 bumps the warning limit from 40M to 100M to give folks some more
> time to react.

I don't have a strong opinion on whether 100M is the right value, but
I noticed a documentation issue in 0002.

<programlisting>
WARNING:  database "mydb" must be vacuumed within 39985967 transactions
DETAIL:  Approximately 1.86% of transaction IDs are available for use.
HINT:  To avoid XID assignment failures, execute a database-wide
VACUUM in that database.
</programlisting>

In maintenance.sgml, above "39985967" and "1.86%" should be updated.

> * 0003 adds an early warning system for when fewer than 500M transactions
> remain.  This system sends a LOG only to the server log every 1M
> transactions.  The hope is that this gets someone's attention sooner
> without flooding the application and server log.

I'm not sure 0003 is worth the added complexity. It adds a new field
to TransamVariablesData and a modulo check in GetNewTransactionId(),
which is a hot path. DBAs who need early warning can already monitor
age(datfrozenxid) with more flexible thresholds.


-- 
Best regards,
Shinya Kato
NTT OSS Center





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
@ 2026-03-03 20:31   ` Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Nathan Bossart @ 2026-03-03 20:31 UTC (permalink / raw)
  To: Shinya Kato <shinya11.kato@gmail.com>; +Cc: pgsql-hackers

On Wed, Feb 18, 2026 at 04:16:16PM +0900, Shinya Kato wrote:
> On Sat, Nov 15, 2025 at 2:05 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
>> I don't know about you, but I start getting antsy around a quarter tank.
>> In any case, I'm told that even 40M transactions aren't enough time to
>> react these days.  Attached are a few patches to enhance the wraparound
>> warnings.
> 
> Thank you for the patch!

Thanks for reviewing.

> I don't have a strong opinion on whether 100M is the right value, but
> I noticed a documentation issue in 0002.
> 
> <programlisting>
> WARNING:  database "mydb" must be vacuumed within 39985967 transactions
> DETAIL:  Approximately 1.86% of transaction IDs are available for use.
> HINT:  To avoid XID assignment failures, execute a database-wide
> VACUUM in that database.
> </programlisting>
> 
> In maintenance.sgml, above "39985967" and "1.86%" should be updated.

Fixed.

> I'm not sure 0003 is worth the added complexity. It adds a new field
> to TransamVariablesData and a modulo check in GetNewTransactionId(),
> which is a hot path. DBAs who need early warning can already monitor
> age(datfrozenxid) with more flexible thresholds.

Yeah, looking at this one again, I'm less sure it's worth pursuing.  I've
removed it.

-- 
nathan
From 07cd0d9d323eecb566d30ce2a73cd2f5def4ac18 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 09:59:15 -0600
Subject: [PATCH v4 1/2] Add percentage of transaction IDs that are available
 to wraparound warnings.

---
 doc/src/sgml/maintenance.sgml          | 1 +
 src/backend/access/transam/multixact.c | 8 ++++++++
 src/backend/access/transam/varsup.c    | 8 ++++++++
 3 files changed, 17 insertions(+)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 7c958b06273..f146e14d3d6 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,6 +674,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
+DETAIL:  Approximately 1.86% of transaction IDs are available for use.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 8a5c9818ed6..9f5f8e692b8 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1053,6 +1053,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -1062,6 +1064,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -2186,6 +2190,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -2195,6 +2201,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 3e95d4cfd16..2921148ceba 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -175,6 +175,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -182,6 +184,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -490,6 +494,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -497,6 +503,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
-- 
2.50.1 (Apple Git-155)
From 78b65793dedd5105c23e93a7133b4433db755a79 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 12 Dec 2025 13:10:05 -0600
Subject: [PATCH v4 2/2] Bump transaction ID limit to warn at 100M.

---
 doc/src/sgml/maintenance.sgml          | 8 ++++----
 src/backend/access/transam/multixact.c | 6 +++---
 src/backend/access/transam/varsup.c    | 6 +++---
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index f146e14d3d6..75c22405a09 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -670,11 +670,11 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
    <para>
     If for some reason autovacuum fails to clear old XIDs from a table, the
     system will begin to emit warning messages like this when the database's
-    oldest XIDs reach forty million transactions from the wraparound point:
+    oldest XIDs reach one hundred million transactions from the wraparound point:
 
 <programlisting>
-WARNING:  database "mydb" must be vacuumed within 39985967 transactions
-DETAIL:  Approximately 1.86% of transaction IDs are available for use.
+WARNING:  database "mydb" must be vacuumed within 99985967 transactions
+DETAIL:  Approximately 4.66% of transaction IDs are available for use.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
@@ -853,7 +853,7 @@ HINT:  Execute a database-wide VACUUM in that database.
 
     <para>
      Similar to the XID case, if autovacuum fails to clear old MXIDs from a table, the
-     system will begin to emit warning messages when the database's oldest MXIDs reach forty
+     system will begin to emit warning messages when the database's oldest MXIDs reach one hundred
      million transactions from the wraparound point.  And, just as in the XID case, if these
      warnings are ignored, the system will refuse to generate new MXIDs once there are fewer
      than three million left until wraparound.
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 9f5f8e692b8..978ade705e7 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -2092,16 +2092,16 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 		multiStopLimit -= FirstMultiXactId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M multis of data
+	 * We'll start complaining loudly when we get within 100M multis of data
 	 * loss.  This is kind of arbitrary, but if you let your gas gauge get
-	 * down to 2% of full, would you be looking for the next gas station?  We
+	 * down to 5% of full, would you be looking for the next gas station?  We
 	 * need to be fairly liberal about this number because there are lots of
 	 * scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	multiWarnLimit = multiWrapLimit - 40000000;
+	multiWarnLimit = multiWrapLimit - 100000000;
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 2921148ceba..1441a051773 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -411,16 +411,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		xidStopLimit -= FirstNormalTransactionId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M transactions of
+	 * We'll start complaining loudly when we get within 100M transactions of
 	 * data loss.  This is kind of arbitrary, but if you let your gas gauge
-	 * get down to 2% of full, would you be looking for the next gas station?
+	 * get down to 5% of full, would you be looking for the next gas station?
 	 * We need to be fairly liberal about this number because there are lots
 	 * of scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	xidWarnLimit = xidWrapLimit - 40000000;
+	xidWarnLimit = xidWrapLimit - 100000000;
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
-- 
2.50.1 (Apple Git-155)

Attachments:

  [text/plain] v4-0001-Add-percentage-of-transaction-IDs-that-are-availa.patch (5.9K, ../../aadFHfocomG20wgM@nathan/2-v4-0001-Add-percentage-of-transaction-IDs-that-are-availa.patch)
  download | inline diff:
From 07cd0d9d323eecb566d30ce2a73cd2f5def4ac18 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 14 Nov 2025 09:59:15 -0600
Subject: [PATCH v4 1/2] Add percentage of transaction IDs that are available
 to wraparound warnings.

---
 doc/src/sgml/maintenance.sgml          | 1 +
 src/backend/access/transam/multixact.c | 8 ++++++++
 src/backend/access/transam/varsup.c    | 8 ++++++++
 3 files changed, 17 insertions(+)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 7c958b06273..f146e14d3d6 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,6 +674,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 39985967 transactions
+DETAIL:  Approximately 1.86% of transaction IDs are available for use.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 8a5c9818ed6..9f5f8e692b8 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1053,6 +1053,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -1062,6 +1064,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
+						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -2186,6 +2190,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -2195,6 +2201,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
+					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 3e95d4cfd16..2921148ceba 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -175,6 +175,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 			else
@@ -182,6 +184,8 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
+						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
@@ -490,6 +494,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
@@ -497,6 +503,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
+					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
-- 
2.50.1 (Apple Git-155)

  [text/plain] v4-0002-Bump-transaction-ID-limit-to-warn-at-100M.patch (4.5K, ../../aadFHfocomG20wgM@nathan/3-v4-0002-Bump-transaction-ID-limit-to-warn-at-100M.patch)
  download | inline diff:
From 78b65793dedd5105c23e93a7133b4433db755a79 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Fri, 12 Dec 2025 13:10:05 -0600
Subject: [PATCH v4 2/2] Bump transaction ID limit to warn at 100M.

---
 doc/src/sgml/maintenance.sgml          | 8 ++++----
 src/backend/access/transam/multixact.c | 6 +++---
 src/backend/access/transam/varsup.c    | 6 +++---
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index f146e14d3d6..75c22405a09 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -670,11 +670,11 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
    <para>
     If for some reason autovacuum fails to clear old XIDs from a table, the
     system will begin to emit warning messages like this when the database's
-    oldest XIDs reach forty million transactions from the wraparound point:
+    oldest XIDs reach one hundred million transactions from the wraparound point:
 
 <programlisting>
-WARNING:  database "mydb" must be vacuumed within 39985967 transactions
-DETAIL:  Approximately 1.86% of transaction IDs are available for use.
+WARNING:  database "mydb" must be vacuumed within 99985967 transactions
+DETAIL:  Approximately 4.66% of transaction IDs are available for use.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
@@ -853,7 +853,7 @@ HINT:  Execute a database-wide VACUUM in that database.
 
     <para>
      Similar to the XID case, if autovacuum fails to clear old MXIDs from a table, the
-     system will begin to emit warning messages when the database's oldest MXIDs reach forty
+     system will begin to emit warning messages when the database's oldest MXIDs reach one hundred
      million transactions from the wraparound point.  And, just as in the XID case, if these
      warnings are ignored, the system will refuse to generate new MXIDs once there are fewer
      than three million left until wraparound.
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 9f5f8e692b8..978ade705e7 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -2092,16 +2092,16 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 		multiStopLimit -= FirstMultiXactId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M multis of data
+	 * We'll start complaining loudly when we get within 100M multis of data
 	 * loss.  This is kind of arbitrary, but if you let your gas gauge get
-	 * down to 2% of full, would you be looking for the next gas station?  We
+	 * down to 5% of full, would you be looking for the next gas station?  We
 	 * need to be fairly liberal about this number because there are lots of
 	 * scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	multiWarnLimit = multiWrapLimit - 40000000;
+	multiWarnLimit = multiWrapLimit - 100000000;
 	if (multiWarnLimit < FirstMultiXactId)
 		multiWarnLimit -= FirstMultiXactId;
 
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 2921148ceba..1441a051773 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -411,16 +411,16 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 		xidStopLimit -= FirstNormalTransactionId;
 
 	/*
-	 * We'll start complaining loudly when we get within 40M transactions of
+	 * We'll start complaining loudly when we get within 100M transactions of
 	 * data loss.  This is kind of arbitrary, but if you let your gas gauge
-	 * get down to 2% of full, would you be looking for the next gas station?
+	 * get down to 5% of full, would you be looking for the next gas station?
 	 * We need to be fairly liberal about this number because there are lots
 	 * of scenarios where most transactions are done by automatic clients that
 	 * won't pay attention to warnings.  (No, we're not gonna make this
 	 * configurable.  If you know enough to configure it, you know enough to
 	 * not get in this kind of trouble in the first place.)
 	 */
-	xidWarnLimit = xidWrapLimit - 40000000;
+	xidWarnLimit = xidWrapLimit - 100000000;
 	if (xidWarnLimit < FirstNormalTransactionId)
 		xidWarnLimit -= FirstNormalTransactionId;
 
-- 
2.50.1 (Apple Git-155)

^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
@ 2026-03-06 22:15     ` Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Nathan Bossart @ 2026-03-06 22:15 UTC (permalink / raw)
  To: Shinya Kato <shinya11.kato@gmail.com>; +Cc: pgsql-hackers

Barring additional feedback or objections, I'm planning to commit this in
the next week or two.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
@ 2026-03-09 12:08       ` wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: wenhui qiu @ 2026-03-09 12:08 UTC (permalink / raw)
  To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: Shinya Kato <shinya11.kato@gmail.com>; pgsql-hackers

Hi Nathan Bossart
> Barring additional feedback or objections, I'm planning to commit this in
> the next week or two.
Thank you for working on this. The path LGTM,But I have a small request,There
are many reasons why the table's age can’t be frozen. Now have a path that
can report the reason to users(https://commitfest.postgresql.org/patch/6188/).
Would you be interested in reviewing it? I think we should tell users the
root cause of why the age can’t be reduced, so they can clearly understand
where the issue is.I think we should not only tell users that the XID is
close to wraparound, but also report why this causes the table‘s age to be
unable to freeze.


Thanks

On Sat, Mar 7, 2026 at 6:15 AM Nathan Bossart <nathandbossart@gmail.com>
wrote:

> Barring additional feedback or objections, I'm planning to commit this in
> the next week or two.
>
> --
> nathan
>
>
>

^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
@ 2026-03-20 19:16         ` Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Nathan Bossart @ 2026-03-20 19:16 UTC (permalink / raw)
  To: wenhui qiu <qiuwenhuifx@gmail.com>; +Cc: Shinya Kato <shinya11.kato@gmail.com>; pgsql-hackers

On Mon, Mar 09, 2026 at 08:08:54PM +0800, wenhui qiu wrote:
> Thank you for working on this. The path LGTM,...

Thanks for looking.  Committed.

> ...But I have a small request,There
> are many reasons why the table's age can’t be frozen. Now have a path that
> can report the reason to users(https://commitfest.postgresql.org/patch/6188/).
> Would you be interested in reviewing it? I think we should tell users the
> root cause of why the age can’t be reduced, so they can clearly understand
> where the issue is.I think we should not only tell users that the XID is
> close to wraparound, but also report why this causes the table‘s age to be
> unable to freeze.

Thanks for alerting me to this patch.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
@ 2026-06-19 18:13           ` Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Fujii Masao @ 2026-06-19 18:13 UTC (permalink / raw)
  To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: wenhui qiu <qiuwenhuifx@gmail.com>; Shinya Kato <shinya11.kato@gmail.com>; pgsql-hackers

On Sat, Mar 21, 2026 at 4:16 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
>
> On Mon, Mar 09, 2026 at 08:08:54PM +0800, wenhui qiu wrote:
> > Thank you for working on this. The path LGTM,...
>
> Thanks for looking.  Committed.

Thanks for working on this feature! I have one comment.

ereport(WARNING,
  (errmsg("database \"%s\" must be vacuumed within %u transactions",
  oldest_datname,
  xidWrapLimit - xid),
+ errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+    (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
  errhint("To avoid transaction ID assignment failures, execute a
database-wide VACUUM in that database.\n"
  "You might also need to commit or roll back old prepared
transactions, or drop stale replication slots.")));

The new DETAIL messages for XID and MultiXactId wraparound warnings
seem misleading.

The percentage is calculated as (xidWrapLimit - xid) divided by half
of the ID space. This represents the remaining ID space before wraparound,
not the percentage of IDs that are still available for use. Since
PostgreSQL stops assigning new XIDs/MultiXactIds at the stop limit
before reaching the wraparound limit, the current wording could be
interpreted as overstating how many IDs remain usable.

So, how about changing the wording to match the calculation? For example:

    Approximately XX.XX% of transaction ID space remains before wraparound.

and similarly for MultiXactIds.

Regards,

-- 
Fujii Masao

Attachments:

  [application/octet-stream] v1-0001-Clarify-wraparound-warning-percentage-messages.patch (7.2K, ../../CAHGQGwHWZTsR6bjdfpa+pOkBPjoXXm2LuJ+5nh+CvdyqEASHcQ@mail.gmail.com/2-v1-0001-Clarify-wraparound-warning-percentage-messages.patch)
  download | inline diff:
From 37d758c78186e6848026a659ca1b35f94c49c248 Mon Sep 17 00:00:00 2001
From: "masao.fujii" <masao.fujii@masao.fujii's-MacBook-Pro>
Date: Sat, 20 Jun 2026 02:33:37 +0900
Subject: [PATCH v1] Clarify wraparound warning percentage messages

Commit e646450e609 added DETAIL messages for XID and MultiXactId
wraparound warnings describing the reported percentage as the IDs
available for use. However, the percentage is actually calculated from
the remaining distance to the wraparound limit, not the stop limit
where new IDs are refused. As a result, the wording could be
misinterpreted as meaning that the reported percentage of IDs can still
be allocated.

Update the wording to clarify that the reported percentage represents
the remaining ID space before wraparound.
---
 doc/src/sgml/maintenance.sgml          | 2 +-
 src/backend/access/transam/multixact.c | 8 ++++----
 src/backend/access/transam/varsup.c    | 8 ++++----
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index e341f165efd..a5a779e2e62 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,7 +674,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 99985967 transactions
-DETAIL:  Approximately 4.66% of transaction IDs are available for use.
+DETAIL:  Approximately 4.66% of transaction ID space remains before wraparound.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 10cbc0d76bd..079f5967e74 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1070,7 +1070,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
-						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+						 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -1081,7 +1081,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
-						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+						 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -2208,7 +2208,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
-					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+					 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -2219,7 +2219,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
-					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+					 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index dc5e32d86f3..e0b280665cf 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -166,7 +166,7 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
-						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+						 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -175,7 +175,7 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
-						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+						 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -485,7 +485,7 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
-					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+					 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -494,7 +494,7 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
-					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+					 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
 					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
-- 
2.53.0



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
@ 2026-06-19 18:43             ` Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Nathan Bossart @ 2026-06-19 18:43 UTC (permalink / raw)
  To: Fujii Masao <masao.fujii@gmail.com>; +Cc: wenhui qiu <qiuwenhuifx@gmail.com>; Shinya Kato <shinya11.kato@gmail.com>; pgsql-hackers

On Sat, Jun 20, 2026 at 03:13:23AM +0900, Fujii Masao wrote:
> The percentage is calculated as (xidWrapLimit - xid) divided by half
> of the ID space. This represents the remaining ID space before wraparound,
> not the percentage of IDs that are still available for use. Since
> PostgreSQL stops assigning new XIDs/MultiXactIds at the stop limit
> before reaching the wraparound limit, the current wording could be
> interpreted as overstating how many IDs remain usable.
> 
> So, how about changing the wording to match the calculation? For example:
> 
>     Approximately XX.XX% of transaction ID space remains before wraparound.
> 
> and similarly for MultiXactIds.

That seems reasonable to me, thanks.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
@ 2026-06-20 11:54               ` Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Fujii Masao @ 2026-06-20 11:54 UTC (permalink / raw)
  To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: wenhui qiu <qiuwenhuifx@gmail.com>; Shinya Kato <shinya11.kato@gmail.com>; pgsql-hackers

On Sat, Jun 20, 2026 at 3:43 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
>
> On Sat, Jun 20, 2026 at 03:13:23AM +0900, Fujii Masao wrote:
> > The percentage is calculated as (xidWrapLimit - xid) divided by half
> > of the ID space. This represents the remaining ID space before wraparound,
> > not the percentage of IDs that are still available for use. Since
> > PostgreSQL stops assigning new XIDs/MultiXactIds at the stop limit
> > before reaching the wraparound limit, the current wording could be
> > interpreted as overstating how many IDs remain usable.
> >
> > So, how about changing the wording to match the calculation? For example:
> >
> >     Approximately XX.XX% of transaction ID space remains before wraparound.
> >
> > and similarly for MultiXactIds.
>
> That seems reasonable to me, thanks.

Thanks for the review!

While reading the related log messages again, I noticed that in three of
the four XID wraparound warnings in varsup.c, the HINT still uses "XID"
while the DETAIL message uses "transaction ID". Commit edee0c621de
updated the remaining warning to use "transaction ID",
but seems to have missed the other three. Since using different terms
for the same thing in the DETAIL and HINT messages seems
inconsistent, I included the following changes in the patch. Or would
it be better to do this in a separate patch?

- errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+ errdetail("Approximately %.2f%% of transaction ID space remains
before wraparound.",
     (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
- errhint("To avoid XID assignment failures, execute a database-wide
VACUUM in that database.\n"
+ errhint("To avoid transaction ID assignment failures, execute a
database-wide VACUUM in that database.\n"

I also updated the comments in
src/test/modules/xid_wraparound/t/002_limits.pl, which contain
examples of the XID wraparound warnings.

Attached is the updated patch.


While making these changes, I also noticed that although commit
edee0c621de updated the runtime XID wraparound messages to use
"transaction IDs", the corresponding examples and text in
maintenance.sgml still use the older "XID" terminology. I therefore
created an additional patch (0002) to update the documentation to
match the current messages. I think this should be backpatched to v17.

Thought?

Regards,

-- 
Fujii Masao

Attachments:

  [application/octet-stream] v2-0001-Clarify-wraparound-warning-percentage-messages.patch (8.5K, ../../CAHGQGwHTN-Xc5iDtbzNSjfxuab5Y9qAArw8cB4PrrDJpZ+1fgA@mail.gmail.com/2-v2-0001-Clarify-wraparound-warning-percentage-messages.patch)
  download | inline diff:
From 011fa9d25af818d79c22b7159c3788dd294a1b09 Mon Sep 17 00:00:00 2001
From: "masao.fujii" <masao.fujii@masao.fujii's-MacBook-Pro>
Date: Sat, 20 Jun 2026 19:49:58 +0900
Subject: [PATCH v2 1/2] Clarify wraparound warning percentage messages

Commit e646450e609 added DETAIL messages for XID and MultiXactId
wraparound warnings describing the reported percentage as the IDs
available for use. However, the percentage is actually calculated from
the remaining distance to the wraparound limit, not the stop limit
where new IDs are refused. As a result, the wording could be
misinterpreted as meaning that the reported percentage of IDs can still
be allocated.

Update the wording to clarify that the reported percentage represents
the remaining ID space before wraparound.

Also update the related XID warning hints to use "transaction ID"
terminology consistently.
---
 doc/src/sgml/maintenance.sgml                   |  2 +-
 src/backend/access/transam/multixact.c          |  8 ++++----
 src/backend/access/transam/varsup.c             | 14 +++++++-------
 src/test/modules/xid_wraparound/t/002_limits.pl |  3 ++-
 4 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index e341f165efd..a5a779e2e62 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,7 +674,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 99985967 transactions
-DETAIL:  Approximately 4.66% of transaction IDs are available for use.
+DETAIL:  Approximately 4.66% of transaction ID space remains before wraparound.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 10cbc0d76bd..079f5967e74 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1070,7 +1070,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
-						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+						 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -1081,7 +1081,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
-						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+						 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -2208,7 +2208,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
-					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+					 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -2219,7 +2219,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
-					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+					 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index dc5e32d86f3..cb68d8fa974 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -166,7 +166,7 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
-						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+						 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -175,9 +175,9 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
-						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+						 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
-						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
+						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
 
@@ -485,18 +485,18 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
-					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+					 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
-					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
+					 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
 			ereport(WARNING,
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
-					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+					 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
-					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
+					 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
 }
diff --git a/src/test/modules/xid_wraparound/t/002_limits.pl b/src/test/modules/xid_wraparound/t/002_limits.pl
index 86632a8d510..29d071a677b 100644
--- a/src/test/modules/xid_wraparound/t/002_limits.pl
+++ b/src/test/modules/xid_wraparound/t/002_limits.pl
@@ -70,7 +70,8 @@ $node->safe_psql('postgres',
 # the warning:
 #
 #  WARNING:  database "postgres" must be vacuumed within 3000024 transactions
-#  HINT:  To avoid a database shutdown, execute a database-wide VACUUM in that database.
+#  DETAIL:  Approximately 0.14% of transaction ID space remains before wraparound.
+#  HINT:  To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.
 #  You might also need to commit or roll back old prepared transactions, or drop stale replication slots.
 my $stderr;
 my $warn_limit = 0;
-- 
2.53.0



  [application/octet-stream] v2-0002-doc-Update-wraparound-examples-to-say-transaction.patch (2.2K, ../../CAHGQGwHTN-Xc5iDtbzNSjfxuab5Y9qAArw8cB4PrrDJpZ+1fgA@mail.gmail.com/3-v2-0002-doc-Update-wraparound-examples-to-say-transaction.patch)
  download | inline diff:
From 6d1a0eb207cca2e70516fdffb60be7689ba4b8d7 Mon Sep 17 00:00:00 2001
From: "masao.fujii" <masao.fujii@masao.fujii's-MacBook-Pro>
Date: Sat, 20 Jun 2026 19:50:03 +0900
Subject: [PATCH v2 2/2] doc: Update wraparound examples to say transaction IDs

Commit edee0c621de changed the runtime XID wraparound messages to
use "transaction IDs" terminology, but the corresponding
examples and text in maintenance.sgml still used the older XID wording.

Update those documentation examples into line with the current runtime
messages.

Backpatch to v17, where the runtime messages were changed.
---
 doc/src/sgml/maintenance.sgml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index a5a779e2e62..8043fb36625 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -675,18 +675,18 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 99985967 transactions
 DETAIL:  Approximately 4.66% of transaction ID space remains before wraparound.
-HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
+HINT:  To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
     (A manual <command>VACUUM</command> should fix the problem, as suggested by the
     hint; but note that the <command>VACUUM</command> should be performed by a
     superuser, else it will fail to process system catalogs, which prevent it from
     being able to advance the database's <structfield>datfrozenxid</structfield>.)
-    If these warnings are ignored, the system will refuse to assign new XIDs once
+    If these warnings are ignored, the system will refuse to assign new transaction IDs once
     there are fewer than three million transactions left until wraparound:
 
 <programlisting>
-ERROR:  database is not accepting commands that assign new XIDs to avoid wraparound data loss in database "mydb"
+ERROR:  database is not accepting commands that assign new transaction IDs to avoid wraparound data loss in database "mydb"
 HINT:  Execute a database-wide VACUUM in that database.
 </programlisting>
 
-- 
2.53.0



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
@ 2026-06-24 04:47                 ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Bharath Rupireddy @ 2026-06-24 04:47 UTC (permalink / raw)
  To: Fujii Masao <masao.fujii@gmail.com>; +Cc: Nathan Bossart <nathandbossart@gmail.com>; wenhui qiu <qiuwenhuifx@gmail.com>; Shinya Kato <shinya11.kato@gmail.com>; pgsql-hackers

Hi,

On Sat, Jun 20, 2026 at 4:54 AM Fujii Masao <masao.fujii@gmail.com> wrote:
>
> While making these changes, I also noticed that although commit
> edee0c621de updated the runtime XID wraparound messages to use
> "transaction IDs", the corresponding examples and text in
> maintenance.sgml still use the older "XID" terminology. I therefore
> created an additional patch (0002) to update the documentation to
> match the current messages. I think this should be backpatched to v17.
>
> Thought?

I happened to quickly review these patches. TBH, "transaction ID
space" and "MultiXactId space" seem a bit confusing because of the
word "space" - it reads fine without it. Is there a specific reason
for this wording?

Also, why not use "multixact IDs" instead of "MultiXactId"? The latter
reads like an internal structure name or such that might confuse users
reading the docs or error messages.

How about something like the following?

+ errdetail("Approximately %.2f%% of multixact IDs remain before wraparound.",
+ errdetail("Approximately %.2f%% of transaction IDs remain before wraparound.",

-- 
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
@ 2026-06-24 12:53                   ` Fujii Masao <masao.fujii@gmail.com>
  2026-06-25 17:19                     ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Fujii Masao @ 2026-06-24 12:53 UTC (permalink / raw)
  To: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; +Cc: Nathan Bossart <nathandbossart@gmail.com>; wenhui qiu <qiuwenhuifx@gmail.com>; Shinya Kato <shinya11.kato@gmail.com>; pgsql-hackers

On Wed, Jun 24, 2026 at 1:47 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
> I happened to quickly review these patches.

Thanks for the review!


> TBH, "transaction ID
> space" and "MultiXactId space" seem a bit confusing because of the
> word "space" - it reads fine without it. Is there a specific reason
> for this wording?

I was thinking "space" was appropriate here because the message is intended
to express the percentage remaining before wraparound in the ID range.
The documentation also uses expressions such as "normal XID space is ...".

But I'm not a native English speaker, so this wording might be wrong.


> Also, why not use "multixact IDs" instead of "MultiXactId"? The latter
> reads like an internal structure name or such that might confuse users
> reading the docs or error messages.

I left "MultiXactId" unchanged because several existing log messages
use that term. I don't have any strong objection to changing it to
"multixact ID", which is already used in the documentation. However, if
we do that, I think it's better update all log messages that use
"MultiXactId" for consistency, probably as a separate patch. That seems
more like a v20 item to me, though.

Regards,

-- 
Fujii Masao





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
@ 2026-06-25 17:19                     ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-26 00:51                       ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Bharath Rupireddy @ 2026-06-25 17:19 UTC (permalink / raw)
  To: Fujii Masao <masao.fujii@gmail.com>; +Cc: Nathan Bossart <nathandbossart@gmail.com>; wenhui qiu <qiuwenhuifx@gmail.com>; Shinya Kato <shinya11.kato@gmail.com>; pgsql-hackers

Hi,

On Wed, Jun 24, 2026 at 5:53 AM Fujii Masao <masao.fujii@gmail.com> wrote:
>
> > TBH, "transaction ID
> > space" and "MultiXactId space" seem a bit confusing because of the
> > word "space" - it reads fine without it. Is there a specific reason
> > for this wording?
>
> I was thinking "space" was appropriate here because the message is intended
> to express the percentage remaining before wraparound in the ID range.
> The documentation also uses expressions such as "normal XID space is ...".

The use there is referring to the whole set of transaction IDs and how
one can visualize the use of them - so "XID space" in that context
seems fine to me.

But the wraparound warnings read better when they say how many more
transaction IDs are left:

+ errdetail("Approximately %.2f%% of MultiXactIds remain before wraparound.",
+ errdetail("Approximately %.2f%% of transaction IDs remain before wraparound.",

> > Also, why not use "multixact IDs" instead of "MultiXactId"? The latter
> > reads like an internal structure name or such that might confuse users
> > reading the docs or error messages.
>
> I left "MultiXactId" unchanged because several existing log messages
> use that term. I don't have any strong objection to changing it to
> "multixact ID", which is already used in the documentation. However, if
> we do that, I think it's better update all log messages that use
> "MultiXactId" for consistency, probably as a separate patch. That seems
> more like a v20 item to me, though.

I'm fine to leave "MultiXactIds" as-is - IMHO, it's not worth the cycles.

--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-25 17:19                     ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
@ 2026-06-26 00:51                       ` Fujii Masao <masao.fujii@gmail.com>
  2026-06-26 01:55                         ` Re: enhance wraparound warnings Kyotaro Horiguchi <horikyota.ntt@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Fujii Masao @ 2026-06-26 00:51 UTC (permalink / raw)
  To: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; +Cc: Nathan Bossart <nathandbossart@gmail.com>; wenhui qiu <qiuwenhuifx@gmail.com>; Shinya Kato <shinya11.kato@gmail.com>; pgsql-hackers

On Fri, Jun 26, 2026 at 2:19 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
> The use there is referring to the whole set of transaction IDs and how
> one can visualize the use of them - so "XID space" in that context
> seems fine to me.
>
> But the wraparound warnings read better when they say how many more
> transaction IDs are left:
>
> + errdetail("Approximately %.2f%% of MultiXactIds remain before wraparound.",
> + errdetail("Approximately %.2f%% of transaction IDs remain before wraparound.",

Okay, I've updated the patches as suggested. The updated patches are attached.

Regards,

-- 
Fujii Masao

Attachments:

  [application/octet-stream] v3-0001-Clarify-wraparound-warning-percentage-messages.patch (8.4K, ../../CAHGQGwHHPzYw2qbtwdkmyC+3pLW1aT1p6gUKx4WWhANiJhob2g@mail.gmail.com/2-v3-0001-Clarify-wraparound-warning-percentage-messages.patch)
  download | inline diff:
From 60833ae74d510e6b4f447905da260433f4546569 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Fri, 26 Jun 2026 09:11:03 +0900
Subject: [PATCH v3 1/2] Clarify wraparound warning percentage messages

Commit e646450e609 added DETAIL messages for XID and MultiXactId
wraparound warnings describing the reported percentage as the IDs
available for use. However, the percentage is actually calculated from
the remaining distance to the wraparound limit, not the stop limit
where new IDs are refused. As a result, the wording could be
misinterpreted as meaning that the reported percentage of IDs can still
be allocated.

Update the wording to clarify that the reported percentage represents
the remaining IDs before wraparound.

Also update the related XID warning hints to use "transaction ID"
terminology consistently.
---
 doc/src/sgml/maintenance.sgml                   |  2 +-
 src/backend/access/transam/multixact.c          |  8 ++++----
 src/backend/access/transam/varsup.c             | 14 +++++++-------
 src/test/modules/xid_wraparound/t/002_limits.pl |  3 ++-
 4 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index e341f165efd..2932ba6a30a 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,7 +674,7 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 99985967 transactions
-DETAIL:  Approximately 4.66% of transaction IDs are available for use.
+DETAIL:  Approximately 4.66% of transaction IDs remain before wraparound.
 HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 10cbc0d76bd..7ce615c3eb9 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1070,7 +1070,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
-						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+						 errdetail("Approximately %.2f%% of MultiXactIds remain before wraparound.",
 								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -1081,7 +1081,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
-						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+						 errdetail("Approximately %.2f%% of MultiXactIds remain before wraparound.",
 								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -2208,7 +2208,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
-					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+					 errdetail("Approximately %.2f%% of MultiXactIds remain before wraparound.",
 							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -2219,7 +2219,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
-					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+					 errdetail("Approximately %.2f%% of MultiXactIds remain before wraparound.",
 							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index dc5e32d86f3..d61fe68210e 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -166,7 +166,7 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
-						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+						 errdetail("Approximately %.2f%% of transaction IDs remain before wraparound.",
 								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -175,9 +175,9 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
-						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+						 errdetail("Approximately %.2f%% of transaction IDs remain before wraparound.",
 								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
-						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
+						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
 
@@ -485,18 +485,18 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
-					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+					 errdetail("Approximately %.2f%% of transaction IDs remain before wraparound.",
 							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
-					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
+					 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
 			ereport(WARNING,
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
-					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+					 errdetail("Approximately %.2f%% of transaction IDs remain before wraparound.",
 							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
-					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
+					 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
 }
diff --git a/src/test/modules/xid_wraparound/t/002_limits.pl b/src/test/modules/xid_wraparound/t/002_limits.pl
index 86632a8d510..236bea2588c 100644
--- a/src/test/modules/xid_wraparound/t/002_limits.pl
+++ b/src/test/modules/xid_wraparound/t/002_limits.pl
@@ -70,7 +70,8 @@ $node->safe_psql('postgres',
 # the warning:
 #
 #  WARNING:  database "postgres" must be vacuumed within 3000024 transactions
-#  HINT:  To avoid a database shutdown, execute a database-wide VACUUM in that database.
+#  DETAIL:  Approximately 0.14% of transaction IDs remain before wraparound.
+#  HINT:  To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.
 #  You might also need to commit or roll back old prepared transactions, or drop stale replication slots.
 my $stderr;
 my $warn_limit = 0;
-- 
2.53.0



  [application/octet-stream] v3-0002-doc-Update-wraparound-examples-to-say-transaction.patch (2.2K, ../../CAHGQGwHHPzYw2qbtwdkmyC+3pLW1aT1p6gUKx4WWhANiJhob2g@mail.gmail.com/3-v3-0002-doc-Update-wraparound-examples-to-say-transaction.patch)
  download | inline diff:
From 27d395abaad4e8d9c2220245e7b5101d5e65b4d6 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Fri, 26 Jun 2026 08:33:10 +0900
Subject: [PATCH v3 2/2] doc: Update wraparound examples to say transaction IDs

Commit edee0c621de changed the runtime XID wraparound messages to
use "transaction IDs" terminology, but the corresponding
examples and text in maintenance.sgml still used the older XID wording.

Update those documentation examples into line with the current runtime
messages.

Backpatch to v17, where the runtime messages were changed.
---
 doc/src/sgml/maintenance.sgml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 2932ba6a30a..add170e5737 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -675,18 +675,18 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 99985967 transactions
 DETAIL:  Approximately 4.66% of transaction IDs remain before wraparound.
-HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
+HINT:  To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
     (A manual <command>VACUUM</command> should fix the problem, as suggested by the
     hint; but note that the <command>VACUUM</command> should be performed by a
     superuser, else it will fail to process system catalogs, which prevent it from
     being able to advance the database's <structfield>datfrozenxid</structfield>.)
-    If these warnings are ignored, the system will refuse to assign new XIDs once
+    If these warnings are ignored, the system will refuse to assign new transaction IDs once
     there are fewer than three million transactions left until wraparound:
 
 <programlisting>
-ERROR:  database is not accepting commands that assign new XIDs to avoid wraparound data loss in database "mydb"
+ERROR:  database is not accepting commands that assign new transaction IDs to avoid wraparound data loss in database "mydb"
 HINT:  Execute a database-wide VACUUM in that database.
 </programlisting>
 
-- 
2.53.0



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-25 17:19                     ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-26 00:51                       ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
@ 2026-06-26 01:55                         ` Kyotaro Horiguchi <horikyota.ntt@gmail.com>
  2026-06-26 18:06                           ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Kyotaro Horiguchi @ 2026-06-26 01:55 UTC (permalink / raw)
  To: masao.fujii@gmail.com; +Cc: bharath.rupireddyforpostgres@gmail.com; nathandbossart@gmail.com; qiuwenhuifx@gmail.com; shinya11.kato@gmail.com; pgsql-hackers

Hello,

At Fri, 26 Jun 2026 09:51:16 +0900, Fujii Masao <masao.fujii@gmail.com> wrote in 
> On Fri, Jun 26, 2026 at 2:19 AM Bharath Rupireddy
> <bharath.rupireddyforpostgres@gmail.com> wrote:
> > The use there is referring to the whole set of transaction IDs and how
> > one can visualize the use of them - so "XID space" in that context
> > seems fine to me.
> >
> > But the wraparound warnings read better when they say how many more
> > transaction IDs are left:
> >
> > + errdetail("Approximately %.2f%% of MultiXactIds remain before wraparound.",
> > + errdetail("Approximately %.2f%% of transaction IDs remain before wraparound.",
> 
> Okay, I've updated the patches as suggested. The updated patches are attached.

I'm not sure I'm following this correctly, but the explanation above
sounded to me as if the warning should report the number of IDs
remaining before wraparound, e.g. "Approximately N MultiXactIds remain
before wraparound", so I'm a bit confused by the proposed wording.

> + errdetail("Approximately %.2f%% of MultiXactIds remain before wraparound.",

The attached patch adopts that wording. However, I'm not sure how to
interpret it. The value is still expressed as a percentage, but it is
no longer clear what that percentage is relative to.

Whichever approach we choose, I think the wording should be explicit.
If the value is reported as a percentage, then something like "x.xx%
of MultiXactId space remains" seems clearer to me. If it is reported
as a count, then "N MultiXactIds remain" would make more sense.

Regards,

-- 
Kyotaro Horiguchi
NTT Open Source Software Center


^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-25 17:19                     ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-26 00:51                       ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-26 01:55                         ` Re: enhance wraparound warnings Kyotaro Horiguchi <horikyota.ntt@gmail.com>
@ 2026-06-26 18:06                           ` Nathan Bossart <nathandbossart@gmail.com>
  2026-06-26 22:18                             ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Nathan Bossart @ 2026-06-26 18:06 UTC (permalink / raw)
  To: Kyotaro Horiguchi <horikyota.ntt@gmail.com>; +Cc: masao.fujii@gmail.com; bharath.rupireddyforpostgres@gmail.com; qiuwenhuifx@gmail.com; shinya11.kato@gmail.com; pgsql-hackers

On Fri, Jun 26, 2026 at 10:55:39AM +0900, Kyotaro Horiguchi wrote:
> I'm not sure I'm following this correctly, but the explanation above
> sounded to me as if the warning should report the number of IDs
> remaining before wraparound, e.g. "Approximately N MultiXactIds remain
> before wraparound", so I'm a bit confused by the proposed wording.

We already report the number of IDs remaining in the WARNING message.  In
v19, I've added a DETAIL message that also reports the percentage
remaining, with the hope that it makes the urgency clearer.

>> + errdetail("Approximately %.2f%% of MultiXactIds remain before wraparound.",
> 
> The attached patch adopts that wording. However, I'm not sure how to
> interpret it. The value is still expressed as a percentage, but it is
> no longer clear what that percentage is relative to.
> 
> Whichever approach we choose, I think the wording should be explicit.
> If the value is reported as a percentage, then something like "x.xx%
> of MultiXactId space remains" seems clearer to me. If it is reported
> as a count, then "N MultiXactIds remain" would make more sense.

I find both "percentage of IDs remaining" and "percentage of ID space
remaining" equally clear and have no strong opinion on the matter.

-- 
nathan





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-25 17:19                     ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-26 00:51                       ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-26 01:55                         ` Re: enhance wraparound warnings Kyotaro Horiguchi <horikyota.ntt@gmail.com>
  2026-06-26 18:06                           ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
@ 2026-06-26 22:18                             ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-07-28 09:43                               ` Re: enhance wraparound warnings Yugo Nagata <nagata@sraoss.co.jp>
  0 siblings, 1 reply; 25+ messages in thread

From: Bharath Rupireddy @ 2026-06-26 22:18 UTC (permalink / raw)
  To: Nathan Bossart <nathandbossart@gmail.com>; +Cc: Kyotaro Horiguchi <horikyota.ntt@gmail.com>; masao.fujii@gmail.com; qiuwenhuifx@gmail.com; shinya11.kato@gmail.com; pgsql-hackers

Hi,

On Fri, Jun 26, 2026 at 11:06 AM Nathan Bossart
<nathandbossart@gmail.com> wrote:
>
> On Fri, Jun 26, 2026 at 10:55:39AM +0900, Kyotaro Horiguchi wrote:
> > I'm not sure I'm following this correctly, but the explanation above
> > sounded to me as if the warning should report the number of IDs
> > remaining before wraparound, e.g. "Approximately N MultiXactIds remain
> > before wraparound", so I'm a bit confused by the proposed wording.
>
> We already report the number of IDs remaining in the WARNING message.  In
> v19, I've added a DETAIL message that also reports the percentage
> remaining, with the hope that it makes the urgency clearer.

Yes. For example: WARNING:  database "mydb" must be vacuumed within
99985967 transactions

> >> + errdetail("Approximately %.2f%% of MultiXactIds remain before wraparound.",
> >
> > The attached patch adopts that wording. However, I'm not sure how to
> > interpret it. The value is still expressed as a percentage, but it is
> > no longer clear what that percentage is relative to.
> >
> > Whichever approach we choose, I think the wording should be explicit.
> > If the value is reported as a percentage, then something like "x.xx%
> > of MultiXactId space remains" seems clearer to me. If it is reported
> > as a count, then "N MultiXactIds remain" would make more sense.
>
> I find both "percentage of IDs remaining" and "percentage of ID space
> remaining" equally clear and have no strong opinion on the matter.

My initial comment was that "IDs remaining" reads more naturally to me
than "ID space remaining," but I'm happy to go with the majority here.
IMHO, no need to spend more cycles on this.

--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-25 17:19                     ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-26 00:51                       ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-26 01:55                         ` Re: enhance wraparound warnings Kyotaro Horiguchi <horikyota.ntt@gmail.com>
  2026-06-26 18:06                           ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-26 22:18                             ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
@ 2026-07-28 09:43                               ` Yugo Nagata <nagata@sraoss.co.jp>
  2026-08-04 07:26                                 ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Yugo Nagata @ 2026-07-28 09:43 UTC (permalink / raw)
  To: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; +Cc: Nathan Bossart <nathandbossart@gmail.com>; Kyotaro Horiguchi <horikyota.ntt@gmail.com>; masao.fujii@gmail.com; qiuwenhuifx@gmail.com; shinya11.kato@gmail.com; pgsql-hackers

On Fri, 26 Jun 2026 15:18:25 -0700
Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> wrote:

> Hi,
> 
> On Fri, Jun 26, 2026 at 11:06 AM Nathan Bossart
> <nathandbossart@gmail.com> wrote:
> >
> > On Fri, Jun 26, 2026 at 10:55:39AM +0900, Kyotaro Horiguchi wrote:
> > > I'm not sure I'm following this correctly, but the explanation above
> > > sounded to me as if the warning should report the number of IDs
> > > remaining before wraparound, e.g. "Approximately N MultiXactIds remain
> > > before wraparound", so I'm a bit confused by the proposed wording.
> >
> > We already report the number of IDs remaining in the WARNING message.  In
> > v19, I've added a DETAIL message that also reports the percentage
> > remaining, with the hope that it makes the urgency clearer.
> 
> Yes. For example: WARNING:  database "mydb" must be vacuumed within
> 99985967 transactions
> 
> > >> + errdetail("Approximately %.2f%% of MultiXactIds remain before wraparound.",
> > >
> > > The attached patch adopts that wording. However, I'm not sure how to
> > > interpret it. The value is still expressed as a percentage, but it is
> > > no longer clear what that percentage is relative to.
> > >
> > > Whichever approach we choose, I think the wording should be explicit.
> > > If the value is reported as a percentage, then something like "x.xx%
> > > of MultiXactId space remains" seems clearer to me. If it is reported
> > > as a count, then "N MultiXactIds remain" would make more sense.
> >
> > I find both "percentage of IDs remaining" and "percentage of ID space
> > remaining" equally clear and have no strong opinion on the matter.
> 
> My initial comment was that "IDs remaining" reads more naturally to me
> than "ID space remaining," but I'm happy to go with the majority here.
> IMHO, no need to spend more cycles on this.

I don't have a strong preference, but I slightly lean toward keeping "space".
The explicit denominator would make the percentage easier to interpret, and
"space" itself doesn't seem particularly confusing to me.

I have a comment of 0002 patch.


     being able to advance the database's <structfield>datfrozenxid</structfield>.)
-    If these warnings are ignored, the system will refuse to assign new XIDs once
+    If these warnings are ignored, the system will refuse to assign new transaction IDs once
     there are fewer than three million transactions left until wraparound:

This also replaces "XIDs" with "transaction IDs" in the surrounding explanatory text.
I'm not sure this change is necessary, since that text is not part of the example output,
and "XID" is already used elsewhere in the same paragraph.

Regards,
Yugo Nagata

-- 
Yugo Nagata <nagata@sraoss.co.jp>






^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-25 17:19                     ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-26 00:51                       ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-26 01:55                         ` Re: enhance wraparound warnings Kyotaro Horiguchi <horikyota.ntt@gmail.com>
  2026-06-26 18:06                           ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-26 22:18                             ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-07-28 09:43                               ` Re: enhance wraparound warnings Yugo Nagata <nagata@sraoss.co.jp>
@ 2026-08-04 07:26                                 ` Fujii Masao <masao.fujii@gmail.com>
  2026-08-04 07:50                                   ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  0 siblings, 1 reply; 25+ messages in thread

From: Fujii Masao @ 2026-08-04 07:26 UTC (permalink / raw)
  To: Yugo Nagata <nagata@sraoss.co.jp>; +Cc: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; Nathan Bossart <nathandbossart@gmail.com>; Kyotaro Horiguchi <horikyota.ntt@gmail.com>; qiuwenhuifx@gmail.com; shinya11.kato@gmail.com; pgsql-hackers

On Tue, Jul 28, 2026 at 6:43 PM Yugo Nagata <nagata@sraoss.co.jp> wrote:
> I don't have a strong preference, but I slightly lean toward keeping "space".
> The explicit denominator would make the percentage easier to interpret, and
> "space" itself doesn't seem particularly confusing to me.

So, my understanding of the discussion is:

- Bharath preferred the wording without "space", but said he was fine with
  the majority.
- Kyotaro thought the wording with "space" was clearer, because it makes
  explicit what the percentage is relative to.
- Nathan had no strong preference.
- Yugo also slightly leaned toward keeping "space".

So, taking these opinions together, I think the wording with "space" is the
better choice here.  Unless there are further objections, I plan to commit
the patch with that wording.


> This also replaces "XIDs" with "transaction IDs" in the surrounding explanatory text.
> I'm not sure this change is necessary, since that text is not part of the example output,
> and "XID" is already used elsewhere in the same paragraph.

I've updated 0002 patch as suggested. Thanks for the review!

Regards,

-- 
Fujii Masao

Attachments:

  [application/octet-stream] v4-0001-Clarify-wraparound-warning-percentage-messages.patch (9.0K, ../../CAHGQGwH-WCnA3kohZSz-VUDvtN=N=wQXd82=wRcQceJckgt=xA@mail.gmail.com/2-v4-0001-Clarify-wraparound-warning-percentage-messages.patch)
  download | inline diff:
From 0ab26f97454750c660d964082582243d79f53b17 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Tue, 4 Aug 2026 15:41:46 +0900
Subject: [PATCH v4 1/2] Clarify wraparound warning percentage messages

Commit e646450e609 added DETAIL messages for XID and MultiXactId
wraparound warnings that described the reported percentage as the
percentage of IDs available for use.  However, the percentage is actually
calculated from the remaining distance to the wraparound limit, not the
stop limit where new IDs are refused.  As a result, the wording could be
misinterpreted as meaning that the reported percentage of IDs can still
be allocated.

Update the wording to clarify that the reported percentage represents the
remaining transaction ID space or MultiXactId space before wraparound.

Also update the related XID warning hints to use "transaction ID"
terminology consistently.

Backpatch to v19, where the DETAIL messages were added.

Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Reviewed-by: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-by: Yugo Nagata <nagata@sraoss.co.jp>
Discussion: https://postgr.es/m/CAHGQGwHWZTsR6bjdfpa+pOkBPjoXXm2LuJ+5nh+CvdyqEASHcQ@mail.gmail.com
Backpatch-through: 19
---
 doc/src/sgml/maintenance.sgml                   |  4 ++--
 src/backend/access/transam/multixact.c          |  8 ++++----
 src/backend/access/transam/varsup.c             | 14 +++++++-------
 src/test/modules/xid_wraparound/t/002_limits.pl |  3 ++-
 4 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 0eec6e2e888..75749ea145c 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -674,8 +674,8 @@ SELECT datname, age(datfrozenxid) FROM pg_database;
 
 <programlisting>
 WARNING:  database "mydb" must be vacuumed within 99985967 transactions
-DETAIL:  Approximately 4.66% of transaction IDs are available for use.
-HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.
+DETAIL:  Approximately 4.66% of transaction ID space remains before wraparound.
+HINT:  To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.
 </programlisting>
 
     (A manual <command>VACUUM</command> should fix the problem, as suggested by the
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 8c694658132..424516dc106 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1070,7 +1070,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datname,
 									   multiWrapLimit - result),
-						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+						 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions.")));
@@ -1081,7 +1081,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 									   multiWrapLimit - result,
 									   oldest_datoid,
 									   multiWrapLimit - result),
-						 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+						 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 								   (double) (multiWrapLimit - result) / (MaxMultiXactId / 2) * 100),
 						 errhint("Execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions.")));
@@ -2208,7 +2208,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datname,
 								   multiWrapLimit - curMulti),
-					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+					 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions.")));
@@ -2219,7 +2219,7 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
 								   multiWrapLimit - curMulti,
 								   oldest_datoid,
 								   multiWrapLimit - curMulti),
-					 errdetail("Approximately %.2f%% of MultiXactIds are available for use.",
+					 errdetail("Approximately %.2f%% of MultiXactId space remains before wraparound.",
 							   (double) (multiWrapLimit - curMulti) / (MaxMultiXactId / 2) * 100),
 					 errhint("To avoid MultiXactId assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions.")));
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index dc5e32d86f3..cb68d8fa974 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -166,7 +166,7 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database \"%s\" must be vacuumed within %u transactions",
 								oldest_datname,
 								xidWrapLimit - xid),
-						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+						 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
 						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
@@ -175,9 +175,9 @@ GetNewTransactionId(bool isSubXact)
 						(errmsg("database with OID %u must be vacuumed within %u transactions",
 								oldest_datoid,
 								xidWrapLimit - xid),
-						 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+						 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 								   (double) (xidWrapLimit - xid) / (MaxTransactionId / 2) * 100),
-						 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
+						 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		}
 
@@ -485,18 +485,18 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 					(errmsg("database \"%s\" must be vacuumed within %u transactions",
 							oldest_datname,
 							xidWrapLimit - curXid),
-					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+					 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
-					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
+					 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 		else
 			ereport(WARNING,
 					(errmsg("database with OID %u must be vacuumed within %u transactions",
 							oldest_datoid,
 							xidWrapLimit - curXid),
-					 errdetail("Approximately %.2f%% of transaction IDs are available for use.",
+					 errdetail("Approximately %.2f%% of transaction ID space remains before wraparound.",
 							   (double) (xidWrapLimit - curXid) / (MaxTransactionId / 2) * 100),
-					 errhint("To avoid XID assignment failures, execute a database-wide VACUUM in that database.\n"
+					 errhint("To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.\n"
 							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
 	}
 }
diff --git a/src/test/modules/xid_wraparound/t/002_limits.pl b/src/test/modules/xid_wraparound/t/002_limits.pl
index 86632a8d510..29d071a677b 100644
--- a/src/test/modules/xid_wraparound/t/002_limits.pl
+++ b/src/test/modules/xid_wraparound/t/002_limits.pl
@@ -70,7 +70,8 @@ $node->safe_psql('postgres',
 # the warning:
 #
 #  WARNING:  database "postgres" must be vacuumed within 3000024 transactions
-#  HINT:  To avoid a database shutdown, execute a database-wide VACUUM in that database.
+#  DETAIL:  Approximately 0.14% of transaction ID space remains before wraparound.
+#  HINT:  To avoid transaction ID assignment failures, execute a database-wide VACUUM in that database.
 #  You might also need to commit or roll back old prepared transactions, or drop stale replication slots.
 my $stderr;
 my $warn_limit = 0;
-- 
2.55.0



  [application/octet-stream] v4-0002-doc-Update-XID-wraparound-error-example.patch (1.6K, ../../CAHGQGwH-WCnA3kohZSz-VUDvtN=N=wQXd82=wRcQceJckgt=xA@mail.gmail.com/3-v4-0002-doc-Update-XID-wraparound-error-example.patch)
  download | inline diff:
From 0e8987747f485a40ff63222ea531c7872811db8e Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Tue, 4 Aug 2026 15:42:14 +0900
Subject: [PATCH v4 2/2] doc: Update XID wraparound error example

Commit edee0c621de, and the equivalent v17 commit f2353dd71724,
changed the runtime XID wraparound messages to use "transaction IDs"
terminology, but one corresponding error example in maintenance.sgml
still used the older XID wording.

Update that documentation example in line with the current runtime
message.

Backpatch to v17, where the runtime messages were changed.

Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Yugo Nagata <nagata@sraoss.co.jp>
Discussion: https://postgr.es/m/CAHGQGwHTN-Xc5iDtbzNSjfxuab5Y9qAArw8cB4PrrDJpZ+1fgA@mail.gmail.com
Backpatch-through: 17
---
 doc/src/sgml/maintenance.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 75749ea145c..e351e5e9ca1 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -686,7 +686,7 @@ HINT:  To avoid transaction ID assignment failures, execute a database-wide VACU
     there are fewer than three million transactions left until wraparound:
 
 <programlisting>
-ERROR:  database is not accepting commands that assign new XIDs to avoid wraparound data loss in database "mydb"
+ERROR:  database is not accepting commands that assign new transaction IDs to avoid wraparound data loss in database "mydb"
 HINT:  Execute a database-wide VACUUM in that database.
 </programlisting>
 
-- 
2.55.0



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-25 17:19                     ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-26 00:51                       ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-26 01:55                         ` Re: enhance wraparound warnings Kyotaro Horiguchi <horikyota.ntt@gmail.com>
  2026-06-26 18:06                           ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-26 22:18                             ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-07-28 09:43                               ` Re: enhance wraparound warnings Yugo Nagata <nagata@sraoss.co.jp>
  2026-08-04 07:26                                 ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
@ 2026-08-04 07:50                                   ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-08-04 08:41                                     ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-08-05 02:45                                     ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  0 siblings, 2 replies; 25+ messages in thread

From: Bharath Rupireddy @ 2026-08-04 07:50 UTC (permalink / raw)
  To: Fujii Masao <masao.fujii@gmail.com>; +Cc: Yugo Nagata <nagata@sraoss.co.jp>; Nathan Bossart <nathandbossart@gmail.com>; Kyotaro Horiguchi <horikyota.ntt@gmail.com>; qiuwenhuifx@gmail.com; shinya11.kato@gmail.com; pgsql-hackers

Hi,

On Tue, Aug 4, 2026 at 12:26 AM Fujii Masao <masao.fujii@gmail.com> wrote:
>
> So, my understanding of the discussion is:
>
> - Bharath preferred the wording without "space", but said he was fine with
>   the majority.
> - Kyotaro thought the wording with "space" was clearer, because it makes
>   explicit what the percentage is relative to.
> - Nathan had no strong preference.
> - Yugo also slightly leaned toward keeping "space".
>
> So, taking these opinions together, I think the wording with "space" is the
> better choice here.  Unless there are further objections, I plan to commit
> the patch with that wording.

WFM. Thanks for summarizing.

> > This also replaces "XIDs" with "transaction IDs" in the surrounding explanatory text.
> > I'm not sure this change is necessary, since that text is not part of the example output,
> > and "XID" is already used elsewhere in the same paragraph.
>
> I've updated 0002 patch as suggested. Thanks for the review!

I looked at both patches and they look good to me. pgindent is happy too.

Just a quick question: is the documentation change considered
backpatchable when the source commit exists in older versions? I mean,
does it fall into the bug or inconsistency category that qualifies for
backpatching?

-- 
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-25 17:19                     ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-26 00:51                       ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-26 01:55                         ` Re: enhance wraparound warnings Kyotaro Horiguchi <horikyota.ntt@gmail.com>
  2026-06-26 18:06                           ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-26 22:18                             ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-07-28 09:43                               ` Re: enhance wraparound warnings Yugo Nagata <nagata@sraoss.co.jp>
  2026-08-04 07:26                                 ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-08-04 07:50                                   ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
@ 2026-08-04 08:41                                     ` Fujii Masao <masao.fujii@gmail.com>
  2026-08-04 15:55                                       ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  1 sibling, 1 reply; 25+ messages in thread

From: Fujii Masao @ 2026-08-04 08:41 UTC (permalink / raw)
  To: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; +Cc: Yugo Nagata <nagata@sraoss.co.jp>; Nathan Bossart <nathandbossart@gmail.com>; Kyotaro Horiguchi <horikyota.ntt@gmail.com>; qiuwenhuifx@gmail.com; shinya11.kato@gmail.com; pgsql-hackers

On Tue, Aug 4, 2026 at 4:50 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
> I looked at both patches and they look good to me. pgindent is happy too.

Thanks for the review!


> Just a quick question: is the documentation change considered
> backpatchable when the source commit exists in older versions? I mean,
> does it fall into the bug or inconsistency category that qualifies for
> backpatching?

Yes, I think 0002 is backpatchable.  The issue is minor, but the documentation
example is clearly inconsistent with the actual runtime message in branches
where the source commit was backpatched.  So I see this as a documentation
bug fix rather than a cosmetic wording change.

Regards,

-- 
Fujii Masao






^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-25 17:19                     ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-26 00:51                       ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-26 01:55                         ` Re: enhance wraparound warnings Kyotaro Horiguchi <horikyota.ntt@gmail.com>
  2026-06-26 18:06                           ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-26 22:18                             ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-07-28 09:43                               ` Re: enhance wraparound warnings Yugo Nagata <nagata@sraoss.co.jp>
  2026-08-04 07:26                                 ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-08-04 07:50                                   ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-08-04 08:41                                     ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
@ 2026-08-04 15:55                                       ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  0 siblings, 0 replies; 25+ messages in thread

From: Bharath Rupireddy @ 2026-08-04 15:55 UTC (permalink / raw)
  To: Fujii Masao <masao.fujii@gmail.com>; +Cc: Yugo Nagata <nagata@sraoss.co.jp>; Nathan Bossart <nathandbossart@gmail.com>; Kyotaro Horiguchi <horikyota.ntt@gmail.com>; qiuwenhuifx@gmail.com; shinya11.kato@gmail.com; pgsql-hackers

Hi,

On Tue, Aug 4, 2026 at 1:41 AM Fujii Masao <masao.fujii@gmail.com> wrote:
>
> > Just a quick question: is the documentation change considered
> > backpatchable when the source commit exists in older versions? I mean,
> > does it fall into the bug or inconsistency category that qualifies for
> > backpatching?
>
> Yes, I think 0002 is backpatchable.  The issue is minor, but the documentation
> example is clearly inconsistent with the actual runtime message in branches
> where the source commit was backpatched.  So I see this as a documentation
> bug fix rather than a cosmetic wording change.

Sounds good to me. Thanks for clarifying.

-- 
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: enhance wraparound warnings
  2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-02-18 07:16 ` Re: enhance wraparound warnings Shinya Kato <shinya11.kato@gmail.com>
  2026-03-03 20:31   ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-06 22:15     ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-03-09 12:08       ` Re: enhance wraparound warnings wenhui qiu <qiuwenhuifx@gmail.com>
  2026-03-20 19:16         ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-19 18:13           ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-19 18:43             ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-20 11:54               ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-24 04:47                 ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-24 12:53                   ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-25 17:19                     ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-06-26 00:51                       ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-06-26 01:55                         ` Re: enhance wraparound warnings Kyotaro Horiguchi <horikyota.ntt@gmail.com>
  2026-06-26 18:06                           ` Re: enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
  2026-06-26 22:18                             ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
  2026-07-28 09:43                               ` Re: enhance wraparound warnings Yugo Nagata <nagata@sraoss.co.jp>
  2026-08-04 07:26                                 ` Re: enhance wraparound warnings Fujii Masao <masao.fujii@gmail.com>
  2026-08-04 07:50                                   ` Re: enhance wraparound warnings Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
@ 2026-08-05 02:45                                     ` Fujii Masao <masao.fujii@gmail.com>
  1 sibling, 0 replies; 25+ messages in thread

From: Fujii Masao @ 2026-08-05 02:45 UTC (permalink / raw)
  To: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; +Cc: Yugo Nagata <nagata@sraoss.co.jp>; Nathan Bossart <nathandbossart@gmail.com>; Kyotaro Horiguchi <horikyota.ntt@gmail.com>; qiuwenhuifx@gmail.com; shinya11.kato@gmail.com; pgsql-hackers

On Tue, Aug 4, 2026 at 4:50 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
> I looked at both patches and they look good to me. pgindent is happy too.

I've pushed the patches. Thanks!

Regards,

-- 
Fujii Masao






^ permalink  raw  reply  [nested|flat] 25+ messages in thread


end of thread, other threads:[~2026-08-05 02:45 UTC | newest]

Thread overview: 25+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2025-11-14 17:05 enhance wraparound warnings Nathan Bossart <nathandbossart@gmail.com>
2025-12-11 20:28 ` Nathan Bossart <nathandbossart@gmail.com>
2025-12-12 02:59   ` Chao Li <li.evan.chao@gmail.com>
2025-12-12 19:19     ` Nathan Bossart <nathandbossart@gmail.com>
2026-02-18 07:16 ` Shinya Kato <shinya11.kato@gmail.com>
2026-03-03 20:31   ` Nathan Bossart <nathandbossart@gmail.com>
2026-03-06 22:15     ` Nathan Bossart <nathandbossart@gmail.com>
2026-03-09 12:08       ` wenhui qiu <qiuwenhuifx@gmail.com>
2026-03-20 19:16         ` Nathan Bossart <nathandbossart@gmail.com>
2026-06-19 18:13           ` Fujii Masao <masao.fujii@gmail.com>
2026-06-19 18:43             ` Nathan Bossart <nathandbossart@gmail.com>
2026-06-20 11:54               ` Fujii Masao <masao.fujii@gmail.com>
2026-06-24 04:47                 ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-06-24 12:53                   ` Fujii Masao <masao.fujii@gmail.com>
2026-06-25 17:19                     ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-06-26 00:51                       ` Fujii Masao <masao.fujii@gmail.com>
2026-06-26 01:55                         ` Kyotaro Horiguchi <horikyota.ntt@gmail.com>
2026-06-26 18:06                           ` Nathan Bossart <nathandbossart@gmail.com>
2026-06-26 22:18                             ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-07-28 09:43                               ` Yugo Nagata <nagata@sraoss.co.jp>
2026-08-04 07:26                                 ` Fujii Masao <masao.fujii@gmail.com>
2026-08-04 07:50                                   ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-08-04 08:41                                     ` Fujii Masao <masao.fujii@gmail.com>
2026-08-04 15:55                                       ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-08-05 02:45                                     ` Fujii Masao <masao.fujii@gmail.com>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox