postgres.git / summary / log / commit / refs
commit 5cd84ed6274ecd552f2991c54710608c0ef4cdc7
Author: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri Sep 18 21:26:14 2026 +0000
Check that oldestXID and oldestMulti are consistent at pg_upgrade
Now that pg_upgrade will rewrite multixid members, starting from
oldestMulti, it's important that oldestMulti is valid. Add a sanity
check that oldestMulti is not newer than the oldest datminmxid value
in pg_database.
One case where this could happen is if the cluster was previously
upgraded to version 9.3 with a buggy pg_upgrade version that didn't
have commit a61daa14d5. This new pg_upgrade check is similar to the
defence that was added in commit 78db307bb2 to VACUUM to avoid
truncating away multixids if oldestMulti is too new. This pg_upgrade
check differs in that we don't try to soldier on with the upgrade if
the oldestMultiXID is inconsistent, but rather just abort the upgrade.
Reported-by: Noah Misch <noah@leadboat.com>
Discussion: https://www.postgresql.org/message-id/20260827231757.78.noahmisch@microsoft.com
Backpatch-through: 19
src/backend/access/transam/multixact.c | 28 --------------
src/bin/pg_upgrade/check.c | 71 ++++++++++++++++++++++++++++++++++
src/include/access/multixact.h | 31 +++++++++++++--
3 files changed, 99 insertions(+), 31 deletions(-)
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 0dbb072ce44..e780522e444 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -2860,34 +2860,6 @@ MultiXactMemberIoErrorDetail(const void *opaque_data)
context->offset);
}
-/*
- * Decide which of two MultiXactIds is earlier.
- *
- * XXX do we need to do something special for InvalidMultiXactId?
- * (Doesn't look like it.)
- */
-bool
-MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2)
-{
- int32 diff = (int32) (multi1 - multi2);
-
- return (diff < 0);
-}
-
-/*
- * MultiXactIdPrecedesOrEquals -- is multi1 logically <= multi2?
- *
- * XXX do we need to do something special for InvalidMultiXactId?
- * (Doesn't look like it.)
- */
-bool
-MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2)
-{
- int32 diff = (int32) (multi1 - multi2);
-
- return (diff <= 0);
-}
-
/*
* Write a TRUNCATE xlog record
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index e298e5658da..5b8c6ee145f 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -9,6 +9,8 @@
#include "postgres_fe.h"
+#include "access/multixact.h"
+#include "access/transam.h"
#include "catalog/pg_am_d.h"
#include "catalog/pg_authid_d.h"
#include "catalog/pg_class_d.h"
@@ -37,6 +39,7 @@ static void check_new_cluster_subscription_configuration(void);
static void check_old_cluster_for_valid_slots(void);
static void check_old_cluster_subscription_state(void);
static void check_old_cluster_global_names(ClusterInfo *cluster);
+static void check_for_oldestxid_consistency(ClusterInfo *cluster);
/*
* DataTypesUsageChecks - definitions of data type checks for the old cluster
@@ -632,6 +635,7 @@ check_and_dump_old_cluster(void)
*/
check_is_install_user(&old_cluster);
check_for_prepared_transactions(&old_cluster);
+ check_for_oldestxid_consistency(&old_cluster);
check_for_isn_and_int8_passing_mismatch(&old_cluster);
if (GET_MAJOR_VERSION(old_cluster.major_version) >= 1700)
@@ -2723,3 +2727,70 @@ check_old_cluster_global_names(ClusterInfo *cluster)
else
check_ok();
}
+
+/*
+ * check_for_oldestxid_consistency()
+ *
+ * Check that the oldestXid and oldestMultiXID values in the control file are
+ * consistent with the 'datfrozenxid' and 'datminmxid' values in pg_database.
+ *
+ * The invariant is that all 'datfrozenxid' and 'datminmxid' values must be
+ * greater than or equal to the values in the control file. Otherwise you
+ * might already have truncated away clog or multixids that are still needed.
+ * If that has happened, we refuse the upgrade and require the administrator
+ * to deal with the situation first.
+ *
+ * One scenario where that is known to happen is if the cluster was upgraded
+ * in the past to version 9.3 with a buggy pg_upgrade version that didn't copy
+ * the oldestMulti value from the old cluster. See commit a61daa14d5.
+ * That was a long time ago, though, so you're not very likely to encounter
+ * that bug in the wild anymore. Therefore we don't assume that's the cause
+ * or try to do anything clever here. In any case, it's still good to check
+ * to prevent further damage.
+ */
+static void
+check_for_oldestxid_consistency(ClusterInfo *cluster)
+{
+ PGconn *conn_template1;
+ PGresult *dbres;
+ int ntups;
+ int i_datname;
+ int i_datfrozenxid;
+ int i_datminmxid;
+
+ prep_status("Checking oldestXID and oldestMultiXid consistency");
+
+ conn_template1 = connectToServer(cluster, "template1");
+
+ dbres = executeQueryOrDie(conn_template1,
+ "SELECT datname, datfrozenxid, datminmxid "
+ "FROM pg_catalog.pg_database");
+
+ i_datname = PQfnumber(dbres, "datname");
+ i_datfrozenxid = PQfnumber(dbres, "datfrozenxid");
+ i_datminmxid = PQfnumber(dbres, "datminmxid");
+
+ ntups = PQntuples(dbres);
+ for (int dbnum = 0; dbnum < ntups; dbnum++)
+ {
+ char *datname = PQgetvalue(dbres, dbnum, i_datname);
+ TransactionId datfrozenxid = (TransactionId) str2uint(PQgetvalue(dbres, dbnum, i_datfrozenxid));
+ MultiXactId datminmxid = (MultiXactId) str2uint(PQgetvalue(dbres, dbnum, i_datminmxid));
+
+ if (TransactionIdPrecedes(datfrozenxid, cluster->controldata.chkpnt_oldstxid))
+ {
+ pg_fatal("oldestXID (%u) in the control file is newer than the datfrozenxid (%u) of database \"%s\"",
+ cluster->controldata.chkpnt_oldstxid, datfrozenxid, datname);
+ }
+ if (MultiXactIdPrecedes(datminmxid, cluster->controldata.chkpnt_oldstMulti))
+ {
+ pg_fatal("oldestMultiXid (%u) in control file is newer than the datminmxid (%u) of database \"%s\"",
+ cluster->controldata.chkpnt_oldstMulti, datminmxid, datname);
+ }
+ }
+
+ PQclear(dbres);
+ PQfinish(conn_template1);
+
+ check_ok();
+}
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 6be5299ab68..97df1d805af 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -28,6 +28,34 @@
#define MultiXactIdIsValid(multi) ((multi) != InvalidMultiXactId)
+/*
+ * Decide which of two MultiXactIds is earlier.
+ *
+ * XXX do we need to do something special for InvalidMultiXactId?
+ * (Doesn't look like it.)
+ */
+static inline bool
+MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2)
+{
+ int32 diff = (int32) (multi1 - multi2);
+
+ return (diff < 0);
+}
+
+/*
+ * MultiXactIdPrecedesOrEquals -- is multi1 logically <= multi2?
+ *
+ * XXX do we need to do something special for InvalidMultiXactId?
+ * (Doesn't look like it.)
+ */
+static inline bool
+MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2)
+{
+ int32 diff = (int32) (multi1 - multi2);
+
+ return (diff <= 0);
+}
+
/*
* Possible multixact lock modes ("status"). The first four modes are for
* tuple locks (FOR KEY SHARE, FOR SHARE, FOR NO KEY UPDATE, FOR UPDATE); the
@@ -110,9 +138,6 @@ extern int GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
extern void GetMultiXactInfo(uint32 *multixacts, MultiXactOffset *nextOffset,
MultiXactId *oldestMultiXactId,
MultiXactOffset *oldestOffset);
-extern bool MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2);
-extern bool MultiXactIdPrecedesOrEquals(MultiXactId multi1,
- MultiXactId multi2);
extern int multixactoffsetssyncfiletag(const FileTag *ftag, char *path);
extern int multixactmemberssyncfiletag(const FileTag *ftag, char *path);
[parent: e995b2cb456c]