From f5fa3605effd624b263ff3e5dbb8b3e5c8992dba Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Sat, 29 Apr 2023 14:23:50 +0700
Subject: [PATCH v7 2/2] Stop telling users to run VACUUM in a single-user mode

Single-user mode is almost always the worst thing to reach for in a
VACUUM emergency:

* Restarting in single user mode requires a shutdown checkpoint
* The user interface is completely different, and awful
* The buffer cache is completely cold
* The checkpointer, background writer and WAL writer are not running
* Without checkpoints WAL segments can not be rotated and reused
* Replication is not running, so after VACUUM is done and database
  is started in normal mode, there is a huge backlog to replicate
* pg_stat_progress_vacuum is not available so there is no indication
  of when the command will complete
* VACUUM VERBOSE doesn't work - there is no output from single-user
  mode vacuum, with or without VERBOSE

If that weren't enough, it's also unsafe because the wraparound
limits are not enforced. It is by no means impossible to corrupt the
database by mistake, such as by a user running VACUUM FULL because it
sounds better.

As mentioned in commit XXXXXXXXX, the system is perfectly capable of
accepting commands when reaching xidStopLimit. Most VACUUM operations
will work normally, with one exception: A new XID is required when
truncating the relation if wal_level is above "minimal". As of v14
the failsafe mechanism disables truncation some time before reaching
xidStopLimit, so this is not an issue in practice.

By remaining in multi-user mode, users still have read-only access to
their database, they can use parallelism, they can use command line
utilities like vacuumdb, and they can remotely access the database
using normal clients.

The only reason to restart in single-user mode is to DROP or TRUNCATE
a table, when it is suspected that doing that would be faster than
vacuuming.

Also add an explicit note warning against using single-user mode.

Backpatch to v14, which is the first version with the VACUUM failsafe.

XXX We should consider v12-13 with "VACUUM (TRUNCATE off, INDEX_CLEANUP
off);", but it's not yet convenient to get to xidStopLimit before v14.

XXX We could consider v11 with careful instructions about redoing
VACUUMs in single-user mode when truncation is necessary.

Aleksander Alekseev, with some adjustments by me

Reported as various times by (at least) Hannu Krosing, Robert Haas,
and Andres Freund

Discussion: https://postgr.es/m/CAJ7c6TM2D277U2wH8X78kg8pH3tdUqebV3_JCJqAkYQFHCFzeg@mail.gmail.com
Discussion: https://www.postgresql.org/message-id/CA%2BTgmob1QCMJrHwRBK8HZtGsr%2B6cJANRQw2mEgJ9e%3DD%2Bz7cOsw%40mail.gmail.com
Discussion: https://www.postgresql.org/message-id/CAMT0RQTmRj_Egtmre6fbiMA9E2hM3BsLULiV8W00stwa3URvzA%40mail.gmail.com
Discussion: https://www.postgresql.org/message-id/CA%2BTgmoYPfofQmRtUan%3DA3aWE9wFsJaOFr%2BW_ys2pPkNPr-2FZw%40mail.gmail.com
---
 doc/src/sgml/maintenance.sgml       | 32 +++++++++++++++++++++--------
 src/backend/access/transam/varsup.c |  4 ++--
 2 files changed, 25 insertions(+), 11 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 105a9900cb..ae9a35fb73 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -669,22 +669,36 @@ HINT:  To prevent entering read-only mode, execute a database-wide VACUUM in tha
 
 <programlisting>
 ERROR:  database is not accepting commands that generate new XIDs to avoid wraparound data loss in database "mydb"
-HINT:  Stop the postmaster and vacuum that database in single-user mode.
+HINT:  Execute a database-wide VACUUM in that database.
 </programlisting>
 
     In this condition any transactions already in progress can continue,
     but only read-only transactions can be started. Operations that
     modify database records or truncate relations will fail.
-
-    The three-million-transaction safety margin exists to let the
-    administrator recover without data loss, by manually executing the
-    required <command>VACUUM</command> commands.  However
-    the only way to do this is to stop the server and start the server in single-user
-    mode to execute <command>VACUUM</command>. See the
-    <xref linkend="app-postgres"/> reference page for details about using
-    single-user mode.
+    The <command>VACUUM</command> command can still be run normally to recover.
+         <!-- v12 and v13 need VACUUM (TRUNCATE off, INDEX_CLEANUP off); >
+         <!-- v11 maybe mention that it will often succed, but will need S-U if truncation happens >
    </para>
 
+   <note>
+    <para>
+     In earlier versions it was required to stop the postmaster and
+     <command>VACUUM</command> the database in a single-user mode. There is no
+     need to use single-user mode anymore, and in fact it's strongly
+     discouraged: It increases downtime, makes monitoring impossible,
+     disables replication, bypasses safeguards against wraparound, etc.
+    </para>
+
+    <para>
+     The only reason to use single-user mode in an emergency is to e.g.
+     <command>TRUNCATE</command> or <command>DROP</command> unneeded tables
+     to avoid needing to <command>VACUUM</command> them.
+     The three-million-transaction safety margin exists to let the
+     administrator do this. See the <xref linkend="app-postgres"/>
+     reference page for details about using single-user mode.
+    </para>
+   </note>
+
    <sect3 id="vacuum-for-multixact-wraparound">
     <title>Multixacts and Wraparound</title>
 
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 485d8ebf81..667da0fd64 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -128,14 +128,14 @@ GetNewTransactionId(bool isSubXact)
 						(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 						 errmsg("database is not accepting commands that generate new XIDs to avoid wraparound data loss in database \"%s\"",
 								oldest_datname),
-						 errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
+						 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(ERROR,
 						(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 						 errmsg("database is not accepting commands that generate new XIDs to avoid wraparound data loss in database with OID %u",
 								oldest_datoid),
-						 errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
+						 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))
-- 
2.39.2

