pg_resetwal --next-transaction-id may cause database failed to restart.

Started by movead.li@highgo.caover 5 years ago11 messages
#1movead.li@highgo.ca
movead.li@highgo.ca
1 attachment(s)

hello hackers,

When I try to use pg_resetwal tool to skip some transaction ID, I get a problem that is
the tool can accept all transaction id I offered with '-x' option, however, the database
may failed to restart because of can not read file under $PGDATA/pg_xact. For
example, the 'NextXID' in a database is 1000, if you offer '-x 32769' then the database
failed to restart.

I read the document of pg_resetwal tool, it told me to write a 'safe value', but I think
pg_resetwal tool should report it and refuse to exec walreset work when using an unsafe
value, rather than remaining it until the user restarts the database.

I do a initial patch to limit the input, now it accepts transaction in two ways:
1. The transaction ID is on the same CLOG page with the 'NextXID' in pg_control.
2. The transaction ID is right at the end of a CLOG page.
The input limited above can ensure the database restart successfully.

The same situation with multixact and multixact-offset option and I make
the same change in the patch.

Do you think it is an issue?

Regards,
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca
EMAIL: mailto:movead(dot)li(at)highgo(dot)ca

Attachments:

pg_resetwal_transaction_limit.patchapplication/octet-stream; name=pg_resetwal_transaction_limit.patchDownload
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 233441837f..d9ba2425df 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -59,6 +59,20 @@
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
+#define CLOG_XACTS_PER_BYTE 4
+#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
+
+#define TransactionIdToPage(xid)	((xid) / (TransactionId) CLOG_XACTS_PER_PAGE)
+#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
+
+#define MULTIXACT_OFFSETS_PER_PAGE (BLCKSZ / sizeof(MultiXactOffset))
+
+#define MultiXactIdToOffsetPage(xid) \
+	((xid) / (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
+#define MultiXactIdToOffsetEntry(xid) \
+	((xid) % (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
+
+
 static ControlFileData ControlFile; /* pg_control values */
 static XLogSegNo newXlogSegNo;	/* new XLOG segment # */
 static bool guessed = false;	/* T if we had to guess at any values */
@@ -430,6 +444,20 @@ main(int argc, char *argv[])
 
 	if (set_xid != 0)
 	{
+		/*
+		 * Only transactions on the same clog page or the last transaction ID of a page with
+		 * the nextFullXid transaction ID record in pg_control are allowed.
+		 */
+		if(TransactionIdToPgIndex(set_xid) != 0)
+		{
+			TransactionId	xid_in_control = XidFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid);
+
+			if(TransactionIdToPage(set_xid) != TransactionIdToPage(xid_in_control))
+			{
+				pg_log_error("unsafe xid number %u pointed by -x", set_xid);
+				exit(1);
+			}
+		}
 		ControlFile.checkPointCopy.nextFullXid =
 			FullTransactionIdFromEpochAndXid(EpochFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid),
 											 set_xid);
@@ -457,6 +485,20 @@ main(int argc, char *argv[])
 
 	if (set_mxid != 0)
 	{
+		/*
+		 * Only transactions on the same mutixact offset page or the last transaction ID
+		 * of a page with the nextMulti transaction ID record in pg_control are allowed.
+		 */
+		if(MultiXactIdToOffsetEntry(set_mxid) != 0)
+		{
+			MultiXactId	mxid_in_control = ControlFile.checkPointCopy.nextMulti;
+
+			if(MultiXactIdToOffsetPage(set_mxid) != MultiXactIdToOffsetPage(mxid_in_control))
+			{
+				pg_log_error("unsafe mxid number %u pointed by -m", set_mxid);
+				exit(1);
+			}
+		}
 		ControlFile.checkPointCopy.nextMulti = set_mxid;
 
 		ControlFile.checkPointCopy.oldestMulti = set_oldestmxid;
@@ -466,7 +508,23 @@ main(int argc, char *argv[])
 	}
 
 	if (set_mxoff != -1)
+	{
+		/*
+		 * Only offset point on the same mutixact member page with the nextMulti
+		 * transaction or the last offset point of a page are allowed.
+		 */
+		if(set_mxoff % BLCKSZ != 0)
+		{
+			MultiXactOffset mxoff_in_control = ControlFile.checkPointCopy.nextMultiOffset;
+
+			if(set_mxoff / BLCKSZ != mxoff_in_control / BLCKSZ)
+			{
+				pg_log_error("unsafe mxoff number %u pointed by -m", set_mxoff);
+				exit(1);
+			}
+		}
 		ControlFile.checkPointCopy.nextMultiOffset = set_mxoff;
+	}
 
 	if (minXlogTli > ControlFile.checkPointCopy.ThisTimeLineID)
 	{
#2Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: movead.li@highgo.ca (#1)
Re: pg_resetwal --next-transaction-id may cause database failed to restart.

On 2020-Jun-22, movead.li@highgo.ca wrote:

hello hackers,

When I try to use pg_resetwal tool to skip some transaction ID, I get a problem that is
the tool can accept all transaction id I offered with '-x' option, however, the database
may failed to restart because of can not read file under $PGDATA/pg_xact. For
example, the 'NextXID' in a database is 1000, if you offer '-x 32769' then the database
failed to restart.

Yeah, the normal workaround is to create the necessary file manually in
order to let the system start after such an operation; they are
sometimes necessary to enable testing weird cases with wraparound and
such. So a total rejection to work for these cases would be unhelpful
precisely for the scenario that those switches were intended to serve.

Maybe a better answer is to have a new switch in postmaster that creates
any needed files (incl. producing associated WAL etc); so you'd run
pg_resetwal -x some-value
postmaster --create-special-stuff
then start your server and off you go.

Now maybe this is too much complication for a mechanism that really
isn't for general consumption anyway. I mean, if you're using
pg_resetwal, you're already playing with fire.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#3movead.li@highgo.ca
movead.li@highgo.ca
In reply to: Alvaro Herrera (#2)
Re: pg_resetwal --next-transaction-id may cause database failed to restart.

Yeah, the normal workaround is to create the necessary file manually in
order to let the system start after such an operation; they are
sometimes necessary to enable testing weird cases with wraparound and
such. So a total rejection to work for these cases would be unhelpful
precisely for the scenario that those switches were intended to serve.

I think these words should appear in pg_resetwal document if we decide
to do nothing for this issue.

Maybe a better answer is to have a new switch in postmaster that creates
any needed files (incl. producing associated WAL etc); so you'd run
pg_resetwal -x some-value
postmaster --create-special-stuff
then start your server and off you go.

As shown in the document, it looks like to rule a safe input, so I think it's better
to rule it and add an option to focus write an unsafe value if necessary.

Now maybe this is too much complication for a mechanism that really
isn't for general consumption anyway. I mean, if you're using
pg_resetwal, you're already playing with fire.

Yes, that's true, I always heard the word "You'd better not use pg_walreset".
But the tool appear in PG code, it's better to improve it than do nothing.

Regards,
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca
EMAIL: mailto:movead(dot)li(at)highgo(dot)ca

#4Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: movead.li@highgo.ca (#3)
Re: pg_resetwal --next-transaction-id may cause database failed to restart.

On 2020-Jun-24, movead.li@highgo.ca wrote:

Maybe a better answer is to have a new switch in postmaster that creates
any needed files (incl. producing associated WAL etc); so you'd run
pg_resetwal -x some-value
postmaster --create-special-stuff
then start your server and off you go.

As shown in the document, it looks like to rule a safe input, so I think it's better
to rule it and add an option to focus write an unsafe value if necessary.

ISTM that a reasonable compromise is that if you use -x (or -c, -m, -O)
and the input value is outside the range supported by existing files,
then it's a fatal error; unless you use --force, which turns it into
just a warning.

Now maybe this is too much complication for a mechanism that really
isn't for general consumption anyway. I mean, if you're using
pg_resetwal, you're already playing with fire.

Yes, that's true, I always heard the word "You'd better not use pg_walreset".
But the tool appear in PG code, it's better to improve it than do nothing.

Sure.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#5movead.li@highgo.ca
movead.li@highgo.ca
In reply to: Alvaro Herrera (#4)
1 attachment(s)
Re: pg_resetwal --next-transaction-id may cause database failed to restart.

ISTM that a reasonable compromise is that if you use -x (or -c, -m, -O)
and the input value is outside the range supported by existing files,
then it's a fatal error; unless you use --force, which turns it into
just a warning.

I do not think '--force' is a good choice, so I add a '--test, -t' option to
force to write a unsafe value to pg_control.
Do you think it is an acceptable method?

Regards,
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca
EMAIL: mailto:movead(dot)li(at)highgo(dot)ca

Attachments:

pg_resetwal_transaction_limit_v2.patchapplication/octet-stream; name=pg_resetwal_transaction_limit_v2.patchDownload
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 233441837f..0de931e387 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -59,6 +59,20 @@
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
+#define CLOG_XACTS_PER_BYTE 4
+#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
+
+#define TransactionIdToPage(xid)	((xid) / (TransactionId) CLOG_XACTS_PER_PAGE)
+#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
+
+#define MULTIXACT_OFFSETS_PER_PAGE (BLCKSZ / sizeof(MultiXactOffset))
+
+#define MultiXactIdToOffsetPage(xid) \
+	((xid) / (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
+#define MultiXactIdToOffsetEntry(xid) \
+	((xid) % (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
+
+
 static ControlFileData ControlFile; /* pg_control values */
 static XLogSegNo newXlogSegNo;	/* new XLOG segment # */
 static bool guessed = false;	/* T if we had to guess at any values */
@@ -96,6 +110,7 @@ main(int argc, char *argv[])
 		{"pgdata", required_argument, NULL, 'D'},
 		{"epoch", required_argument, NULL, 'e'},
 		{"force", no_argument, NULL, 'f'},
+		{"test", no_argument, NULL, 't'},
 		{"next-wal-file", required_argument, NULL, 'l'},
 		{"multixact-ids", required_argument, NULL, 'm'},
 		{"dry-run", no_argument, NULL, 'n'},
@@ -108,6 +123,7 @@ main(int argc, char *argv[])
 
 	int			c;
 	bool		force = false;
+	bool		testmode = false;
 	bool		noupdate = false;
 	MultiXactId set_oldestmxid = 0;
 	char	   *endptr;
@@ -135,7 +151,7 @@ main(int argc, char *argv[])
 	}
 
 
-	while ((c = getopt_long(argc, argv, "c:D:e:fl:m:no:O:x:", long_options, NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "c:D:e:fl:m:no:O:tx:", long_options, NULL)) != -1)
 	{
 		switch (c)
 		{
@@ -146,6 +162,10 @@ main(int argc, char *argv[])
 			case 'f':
 				force = true;
 				break;
+
+			case 't':
+				testmode = true;
+				break;
 
 			case 'n':
 				noupdate = true;
@@ -430,6 +450,20 @@ main(int argc, char *argv[])
 
 	if (set_xid != 0)
 	{
+		/*
+		 * Only transactions on the same clog page or the last transaction ID of a page with
+		 * the nextFullXid transaction ID record in pg_control are allowed.
+		 */
+		if(TransactionIdToPgIndex(set_xid) != 0)
+		{
+			TransactionId	xid_in_control = XidFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid);
+
+			if(TransactionIdToPage(set_xid) != TransactionIdToPage(xid_in_control) && !testmode)
+			{
+				pg_log_error("unsafe xid number %u pointed by -x", set_xid);
+				exit(1);
+			}
+		}
 		ControlFile.checkPointCopy.nextFullXid =
 			FullTransactionIdFromEpochAndXid(EpochFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid),
 											 set_xid);
@@ -457,6 +491,21 @@ main(int argc, char *argv[])
 
 	if (set_mxid != 0)
 	{
+		/*
+		 * Only transactions on the same mutixact offset page or the last transaction ID
+		 * of a page with the nextMulti transaction ID record in pg_control are allowed.
+		 */
+		if(MultiXactIdToOffsetEntry(set_mxid) != 0)
+		{
+			MultiXactId	mxid_in_control = ControlFile.checkPointCopy.nextMulti;
+
+			if(MultiXactIdToOffsetPage(set_mxid) != MultiXactIdToOffsetPage(mxid_in_control)
+				&& !testmode)
+			{
+				pg_log_error("unsafe mxid number %u pointed by -m", set_mxid);
+				exit(1);
+			}
+		}
 		ControlFile.checkPointCopy.nextMulti = set_mxid;
 
 		ControlFile.checkPointCopy.oldestMulti = set_oldestmxid;
@@ -466,7 +515,23 @@ main(int argc, char *argv[])
 	}
 
 	if (set_mxoff != -1)
+	{
+		/*
+		 * Only offset point on the same mutixact member page with the nextMulti
+		 * transaction or the last offset point of a page are allowed.
+		 */
+		if(set_mxoff % BLCKSZ != 0)
+		{
+			MultiXactOffset mxoff_in_control = ControlFile.checkPointCopy.nextMultiOffset;
+
+			if(set_mxoff / BLCKSZ != mxoff_in_control / BLCKSZ && !testmode)
+			{
+				pg_log_error("unsafe mxoff number %u pointed by -m", set_mxoff);
+				exit(1);
+			}
+		}
 		ControlFile.checkPointCopy.nextMultiOffset = set_mxoff;
+	}
 
 	if (minXlogTli > ControlFile.checkPointCopy.ThisTimeLineID)
 	{
@@ -1214,6 +1279,7 @@ usage(void)
 	printf(_(" [-D, --pgdata=]DATADIR          data directory\n"));
 	printf(_("  -e, --epoch=XIDEPOCH           set next transaction ID epoch\n"));
 	printf(_("  -f, --force                    force update to be done\n"));
+	printf(_("  -t, --test                     test mode to force -O,-x,-m to be done\n"));
 	printf(_("  -l, --next-wal-file=WALFILE    set minimum starting location for new WAL\n"));
 	printf(_("  -m, --multixact-ids=MXID,MXID  set next and oldest multitransaction ID\n"));
 	printf(_("  -n, --dry-run                  no update, just show what would be done\n"));
#6Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: movead.li@highgo.ca (#5)
Re: pg_resetwal --next-transaction-id may cause database failed to restart.

On 2020-Jul-07, movead.li@highgo.ca wrote:

ISTM that a reasonable compromise is that if you use -x (or -c, -m, -O)
and the input value is outside the range supported by existing files,
then it's a fatal error; unless you use --force, which turns it into
just a warning.

I do not think '--force' is a good choice, so I add a '--test, -t' option to
force to write a unsafe value to pg_control.
Do you think it is an acceptable method?

The rationale for this interface is unclear to me. Please explain what
happens in each case?

In my proposal, we'd have:

* Bad value, no --force:
- program raises error, no work done.
* Bad value with --force:
- program raises warning but changes anyway.
* Good value, no --force:
- program changes value without saying anything
* Good value with --force:
- same

The rationale for this interface is convenient knowledgeable access: the
DBA runs the program with value X, and if the value is good, then
they're done. If the program raises an error, DBA has a choice: either
run with --force because they know what they're doing, or don't do
anything because they know that they would make a mess.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#7movead.li@highgo.ca
movead.li@highgo.ca
In reply to: Alvaro Herrera (#6)
Re: pg_resetwal --next-transaction-id may cause database failed to restart.

The rationale for this interface is unclear to me. Please explain what
happens in each case?
In my proposal, we'd have:
* Bad value, no --force:
- program raises error, no work done.
* Bad value with --force:
- program raises warning but changes anyway.
* Good value, no --force:
- program changes value without saying anything
* Good value with --force:
- same

You have list all cases, maybe you are right it needs to raise a warning
when force a Bad value write which missed in the patch.
And I use '--test' in the patch, not '--force' temporary, maybe it needs
a deep research and discuss.

The rationale for this interface is convenient knowledgeable access: the
DBA runs the program with value X, and if the value is good, then
they're done. If the program raises an error, DBA has a choice: either
run with --force because they know what they're doing, or don't do
anything because they know that they would make a mess.

Yes that's it, in addition the raised error, can tell the DBA to input a good
value.

Regards,
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca
EMAIL: mailto:movead(dot)li(at)highgo(dot)ca

#8Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#4)
Re: pg_resetwal --next-transaction-id may cause database failed to restart.

On Wed, Jun 24, 2020 at 11:04 AM Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

ISTM that a reasonable compromise is that if you use -x (or -c, -m, -O)
and the input value is outside the range supported by existing files,
then it's a fatal error; unless you use --force, which turns it into
just a warning.

One potential problem is that you might be using --force for some
other reason and end up forcing this, too. But maybe that's OK.

Perhaps we should consider the idea of having pg_resetwal create the
relevant clog file and zero-fill it, if it doesn't exist already,
rather than leaving that to to the DBA or the postmaster binary to do
it. It seems like that is what people would want to happen in this
situation.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#9movead.li@highgo.ca
movead.li@highgo.ca
In reply to: movead.li@highgo.ca (#3)
Re: pg_resetwal --next-transaction-id may cause database failed to restart.

ISTM that a reasonable compromise is that if you use -x (or -c, -m, -O)
and the input value is outside the range supported by existing files,
then it's a fatal error; unless you use --force, which turns it into
just a warning.

One potential problem is that you might be using --force for some
other reason and end up forcing this, too. But maybe that's OK.

Yes it's true, so I try to add a new option to control this behavior, you
can see it in the last mail with attach.

Perhaps we should consider the idea of having pg_resetwal create the
relevant clog file and zero-fill it, if it doesn't exist already,
rather than leaving that to to the DBA or the postmaster binary to do
it. It seems like that is what people would want to happen in this
situation.

I have considered this idea, but I think it produces files uncontrolled
by postmaster, so I think it may be unacceptable and give up.

In the case we force to write an unsafe value, we can create or extend
related files I think. Do you have any further idea, I can work out a new
patch.

Regards,
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca
EMAIL: mailto:movead(dot)li(at)highgo(dot)ca

#10Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: movead.li@highgo.ca (#9)
Re: pg_resetwal --next-transaction-id may cause database failed to restart.

On 2020-Jul-09, movead.li@highgo.ca wrote:

ISTM that a reasonable compromise is that if you use -x (or -c, -m, -O)
and the input value is outside the range supported by existing files,
then it's a fatal error; unless you use --force, which turns it into
just a warning.

One potential problem is that you might be using --force for some
other reason and end up forcing this, too. But maybe that's OK.

Yes it's true, so I try to add a new option to control this behavior, you
can see it in the last mail with attach.

It may be OK actually; if you're doing multiple dangerous changes, you'd
use --dry-run beforehand ... No? (It's what *I* would do, for sure.)
Which in turns suggests that it would good to ensure that --dry-run
*also* emits a warning (not an error, so that any other warnings can
also be thrown and the user gets the full picture).

I think adding multiple different --force switches makes the UI more
complex for little added value.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#11movead.li@highgo.ca
movead.li@highgo.ca
In reply to: Alvaro Herrera (#10)
1 attachment(s)
Re: pg_resetwal --next-transaction-id may cause database failed to restart.

It may be OK actually; if you're doing multiple dangerous changes, you'd
use --dry-run beforehand ... No? (It's what *I* would do, for sure.)
Which in turns suggests that it would good to ensure that --dry-run
*also* emits a warning (not an error, so that any other warnings can
also be thrown and the user gets the full picture).

Yes that's true, I have chaged the patch and will get a warning rather than
error when we point a --dry-run option.
And I remake the code which looks more clearly.

I think adding multiple different --force switches makes the UI more
complex for little added value.

Yes I also feel about that, but I can't convince myself to use --force
to finish the mission, because --force is used when something wrong with
pg_control file and we can listen to hackers' proposals.

Regards,
Highgo Software (Canada/China/Pakistan)
URL : www.highgo.ca
EMAIL: mailto:movead(dot)li(at)highgo(dot)ca

Attachments:

pg_resetwal_transaction_limit_v3.patchapplication/octet-stream; name=pg_resetwal_transaction_limit_v3.patchDownload
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 233441837f..7ec06761c3 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -59,6 +59,20 @@
 #include "pg_getopt.h"
 #include "storage/large_object.h"
 
+#define CLOG_XACTS_PER_BYTE 4
+#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
+
+#define TransactionIdToPage(xid)	((xid) / (TransactionId) CLOG_XACTS_PER_PAGE)
+#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
+
+#define MULTIXACT_OFFSETS_PER_PAGE (BLCKSZ / sizeof(MultiXactOffset))
+
+#define MultiXactIdToOffsetPage(xid) \
+	((xid) / (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
+#define MultiXactIdToOffsetEntry(xid) \
+	((xid) % (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
+
+
 static ControlFileData ControlFile; /* pg_control values */
 static XLogSegNo newXlogSegNo;	/* new XLOG segment # */
 static bool guessed = false;	/* T if we had to guess at any values */
@@ -74,6 +88,9 @@ static uint32 minXlogTli = 0;
 static XLogSegNo minXlogSegNo = 0;
 static int	WalSegSz;
 static int	set_wal_segsize;
+static bool get_unsafe = false;
+static bool testmode = false;
+static bool noupdate = false;
 
 static void CheckDataVersion(void);
 static bool read_controlfile(void);
@@ -86,6 +103,9 @@ static void KillExistingXLOG(void);
 static void KillExistingArchiveStatus(void);
 static void WriteEmptyXLOG(void);
 static void usage(void);
+static void CheckInputXid(TransactionId set_xid);
+static void CheckInputMid(MultiXactId set_mxid);
+static void CheckInputMxoff(MultiXactOffset set_mxoff);
 
 
 int
@@ -96,6 +116,7 @@ main(int argc, char *argv[])
 		{"pgdata", required_argument, NULL, 'D'},
 		{"epoch", required_argument, NULL, 'e'},
 		{"force", no_argument, NULL, 'f'},
+		{"test", no_argument, NULL, 't'},
 		{"next-wal-file", required_argument, NULL, 'l'},
 		{"multixact-ids", required_argument, NULL, 'm'},
 		{"dry-run", no_argument, NULL, 'n'},
@@ -108,7 +129,6 @@ main(int argc, char *argv[])
 
 	int			c;
 	bool		force = false;
-	bool		noupdate = false;
 	MultiXactId set_oldestmxid = 0;
 	char	   *endptr;
 	char	   *endptr2;
@@ -135,7 +155,7 @@ main(int argc, char *argv[])
 	}
 
 
-	while ((c = getopt_long(argc, argv, "c:D:e:fl:m:no:O:x:", long_options, NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "c:D:e:fl:m:no:O:tx:", long_options, NULL)) != -1)
 	{
 		switch (c)
 		{
@@ -147,6 +167,10 @@ main(int argc, char *argv[])
 				force = true;
 				break;
 
+			case 't':
+				testmode = true;
+				break;
+
 			case 'n':
 				noupdate = true;
 				break;
@@ -430,6 +454,7 @@ main(int argc, char *argv[])
 
 	if (set_xid != 0)
 	{
+		CheckInputXid(set_xid);
 		ControlFile.checkPointCopy.nextFullXid =
 			FullTransactionIdFromEpochAndXid(EpochFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid),
 											 set_xid);
@@ -457,6 +482,7 @@ main(int argc, char *argv[])
 
 	if (set_mxid != 0)
 	{
+		CheckInputMid(set_mxid);
 		ControlFile.checkPointCopy.nextMulti = set_mxid;
 
 		ControlFile.checkPointCopy.oldestMulti = set_oldestmxid;
@@ -466,7 +492,10 @@ main(int argc, char *argv[])
 	}
 
 	if (set_mxoff != -1)
+	{
+		CheckInputMxoff(set_mxoff);
 		ControlFile.checkPointCopy.nextMultiOffset = set_mxoff;
+	}
 
 	if (minXlogTli > ControlFile.checkPointCopy.ThisTimeLineID)
 	{
@@ -878,6 +907,136 @@ PrintNewControlValues(void)
 	}
 }
 
+/*
+ * Only transactions on the same clog page with the nextFullXid transaction
+ * ID record in pg_control or the first transaction ID of a page are allowed.
+ */
+static void
+CheckInputXid(TransactionId set_xid)
+{
+	TransactionId	xid_in_control = InvalidTransactionId;
+
+	/*
+	 * if it's first transaction ID, then nothing wrong and we skip check in a
+	 * testmode.
+	 */
+	if(0 == TransactionIdToPgIndex(set_xid) || testmode)
+	{
+		return;
+	}
+
+	xid_in_control = XidFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid);
+
+	/* It's ok if set a xid in same CLOG page */
+	if(TransactionIdToPage(set_xid) == TransactionIdToPage(xid_in_control))
+	{
+		return;
+	}
+
+	if(noupdate)
+	{
+		/* For UI output */
+		if(!get_unsafe)
+		{
+			printf("\n");
+			get_unsafe = true;
+		}
+		pg_log_warning("unsafe xid number %u pointed by -x", set_xid);
+	}
+	else
+	{
+		pg_log_error("unsafe xid number %u pointed by -x", set_xid);
+		exit(1);
+	}
+}
+
+/*
+ * Only transactions on the same mutixact offset page with the nextMulti
+ * transaction ID record in pg_control or the first transaction ID of a
+ * page are allowed.
+ */
+static void
+CheckInputMid(MultiXactId set_mxid)
+{
+	MultiXactId	mxid_in_control = InvalidMultiXactId;
+
+	/*
+	 * if it's first MultiXact ID in a page, then nothing wrong and we
+	 * skip check in a testmode.
+	 */
+	if(0 == MultiXactIdToOffsetEntry(set_mxid) || testmode)
+	{
+		return;
+	}
+
+	mxid_in_control = ControlFile.checkPointCopy.nextMulti;
+
+	/* It's ok if the mxid in same Mutixact page */
+	if(MultiXactIdToOffsetPage(set_mxid) == MultiXactIdToOffsetPage(mxid_in_control))
+	{
+		return;
+	}
+
+	if(noupdate)
+	{
+		/* For UI output */
+		if(!get_unsafe)
+		{
+			printf("\n");
+			get_unsafe = true;
+		}
+		pg_log_warning("unsafe mxid number %u pointed by -m", set_mxid);
+	}
+	else
+	{
+		pg_log_error("unsafe mxid number %u pointed by -m", set_mxid);
+		exit(1);
+	}
+}
+
+/*
+ * Only offset point on the same mutixact member page with the nextMulti
+ * transaction or the first offset point of a page are allowed.
+ */
+static void
+CheckInputMxoff(MultiXactOffset set_mxoff)
+{
+	MultiXactOffset mxoff_in_control;
+
+	/*
+	 * if it's first place in a mutixact offset page, then nothing wrong and we
+	 * skip check in a testmode.
+	 */
+	if(0 == set_mxoff % BLCKSZ || testmode)
+	{
+		return;
+	}
+
+	mxoff_in_control = ControlFile.checkPointCopy.nextMultiOffset;
+
+	/* It's ok if the mxoff in same Mutixact offset page */
+	if(set_mxoff / BLCKSZ == mxoff_in_control / BLCKSZ)
+	{
+		return;
+	}
+
+	if(noupdate)
+	{
+		/* For UI output */
+		if(!get_unsafe)
+		{
+			printf("\n");
+			get_unsafe = true;
+		}
+		pg_log_warning("unsafe mxoff number %u pointed by -m", set_mxoff);
+	}
+	else
+	{
+		pg_log_error("unsafe mxoff number %u pointed by -m", set_mxoff);
+		exit(1);
+	}
+}
+
 
 /*
  * Write out the new pg_control file.
@@ -1214,6 +1373,7 @@ usage(void)
 	printf(_(" [-D, --pgdata=]DATADIR          data directory\n"));
 	printf(_("  -e, --epoch=XIDEPOCH           set next transaction ID epoch\n"));
 	printf(_("  -f, --force                    force update to be done\n"));
+	printf(_("  -t, --test                     test mode to force -O,-x,-m to be done\n"));
 	printf(_("  -l, --next-wal-file=WALFILE    set minimum starting location for new WAL\n"));
 	printf(_("  -m, --multixact-ids=MXID,MXID  set next and oldest multitransaction ID\n"));
 	printf(_("  -n, --dry-run                  no update, just show what would be done\n"));