1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

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
This commit is contained in:
Heikki Linnakangas
2026-09-19 00:26:14 +03:00
parent e995b2cb45
commit 5cd84ed627
3 changed files with 99 additions and 31 deletions

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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);