Add --system-identifier / -s option to pg_resetwal

Started by Nikolay Samokhvalov8 months ago7 messages
#1Nikolay Samokhvalov
nik@postgres.ai
1 attachment(s)

hi hackers,

I was just involved in a DR case, where we needed to change system ID in
control data, and noticed that there is no such capability, officially – so
I thought, it would be good to have it

the attached patch adds a new -s / --system-identifier option to
pg_resetwal that allows users to change the database cluster's system
identifier; it can be useful when you need to make a restored cluster
distinct from the original, or when cloning for testing

some aspects about the patch:
- accepts positive 64-bit integers only (zero not allowed)
- requires interactive confirmation or --force flag for safety
- detects non-TTY environments and requires --force in scripts
- included input validation and error handling
- updated docs with clear warnings about compatibility
- added tests, including some edge cases

looking forward to hearing feedback!

Nik

Attachments:

0001-pg_resetwal--system-identifier.patchapplication/octet-stream; name=0001-pg_resetwal--system-identifier.patchDownload
From 4b41965b84470a33254c3aaf9b2b458027a9a176 Mon Sep 17 00:00:00 2001
From: Nikolay Samokhvalov <nik@postgres.ai>
Date: Sat, 31 May 2025 11:36:41 -0700
Subject: [PATCH] Add --system-identifier option to pg_resetwal

This patch adds a new --system-identifier option to pg_resetwal that allows
users to change the database cluster's system identifier. This feature is
useful in recovery scenarios where a restored cluster needs to be made
distinct from the original.

Key features:
- Accepts positive 64-bit integers only (zero not allowed)
- Requires interactive confirmation or --force flag for safety
- Detects non-TTY environments and requires --force in scripts
- Comprehensive input validation and error handling
- Updated documentation with clear warnings about compatibility
- Extensive test coverage including edge cases and automation scenarios

The system identifier change makes the cluster incompatible with existing
backups, standby servers, and replication setups, so the feature includes
appropriate safety checks and user warnings.

Author: Nikolay Samokhvalov <nik@postgres.ai>
---
 doc/src/sgml/ref/pg_resetwal.sgml  |  32 ++++++
 src/bin/pg_resetwal/pg_resetwal.c  |  69 ++++++++++-
 src/bin/pg_resetwal/t/001_basic.pl | 179 +++++++++++++++++++++++++++++
 3 files changed, 279 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/pg_resetwal.sgml b/doc/src/sgml/ref/pg_resetwal.sgml
index 2c019c2aac6..e58befd5b09 100644
--- a/doc/src/sgml/ref/pg_resetwal.sgml
+++ b/doc/src/sgml/ref/pg_resetwal.sgml
@@ -356,6 +356,38 @@ PostgreSQL documentation
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><option>-s <replaceable class="parameter">system_identifier</replaceable></option></term>
+    <term><option>--system-identifier=<replaceable class="parameter">system_identifier</replaceable></option></term>
+    <listitem>
+     <para>
+      Manually set the database system identifier.
+     </para>
+
+     <para>
+      The system identifier is a unique 64-bit number that identifies the
+      database cluster. It is used by replication systems and backup tools
+      to ensure they are working with the correct cluster. Changing the
+      system identifier makes the cluster incompatible with existing
+      backups, standby servers, and replication setups.
+     </para>
+
+     <para>
+      This option should only be used in recovery scenarios where you need
+      to make a restored cluster distinct from the original, or when cloning
+      a cluster for testing purposes. The value must be a positive 64-bit
+      integer and cannot be zero.
+     </para>
+
+     <warning>
+      <para>
+       Changing the system identifier will break compatibility with existing
+       backups and standby servers. Use this option with extreme caution.
+      </para>
+     </warning>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><option>--char-signedness=<replaceable class="parameter">option</replaceable></option></term>
     <listitem>
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index e876f35f38e..eecd9fd3733 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -76,6 +76,7 @@ static XLogSegNo minXlogSegNo = 0;
 static int	WalSegSz;
 static int	set_wal_segsize;
 static int	set_char_signedness = -1;
+static uint64 set_sysid = 0;
 
 static void CheckDataVersion(void);
 static bool read_controlfile(void);
@@ -105,6 +106,7 @@ main(int argc, char *argv[])
 		{"next-oid", required_argument, NULL, 'o'},
 		{"multixact-offset", required_argument, NULL, 'O'},
 		{"oldest-transaction-id", required_argument, NULL, 'u'},
+		{"system-identifier", required_argument, NULL, 's'},
 		{"next-transaction-id", required_argument, NULL, 'x'},
 		{"wal-segsize", required_argument, NULL, 1},
 		{"char-signedness", required_argument, NULL, 2},
@@ -140,7 +142,7 @@ main(int argc, char *argv[])
 	}
 
 
-	while ((c = getopt_long(argc, argv, "c:D:e:fl:m:no:O:u:x:", long_options, NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "c:D:e:fl:m:no:O:s:u:x:", long_options, NULL)) != -1)
 	{
 		switch (c)
 		{
@@ -321,6 +323,29 @@ main(int argc, char *argv[])
 					break;
 				}
 
+			case 's':
+				/* Check for negative sign first */
+				if (optarg[0] == '-')
+				{
+					pg_log_error("system identifier must be greater than 0");
+					pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+					exit(1);
+				}
+				errno = 0;
+				set_sysid = strtou64(optarg, &endptr, 0);
+				if (endptr == optarg || *endptr != '\0' || errno != 0 || set_sysid == 0)
+				{
+					if (errno == ERANGE)
+						pg_log_error("system identifier value is out of range");
+					else if (set_sysid == 0)
+						pg_log_error("system identifier must be greater than 0");
+					else
+						pg_log_error("invalid argument for option %s", "-s");
+					pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+					exit(1);
+				}
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -478,6 +503,41 @@ main(int argc, char *argv[])
 	if (set_char_signedness != -1)
 		ControlFile.default_char_signedness = (set_char_signedness == 1);
 
+	if (set_sysid != 0)
+	{
+		/* Safety check: prompt for confirmation when changing system identifier unless force flag is used */
+		if (!force)
+		{
+			char response[10];
+			
+			/* Check if stdin is a TTY for interactive confirmation */
+			if (!isatty(fileno(stdin)))
+			{
+				pg_log_error("standard input is not a TTY and --force was not specified");
+				pg_log_error_hint("Cannot prompt for system identifier change confirmation. "
+								  "Use --force to proceed without confirmation in non-interactive mode.");
+				exit(1);
+			}
+			
+			printf(_("WARNING: Changing the system identifier will make this cluster incompatible with existing backups and standby servers.\n"));
+			printf(_("Current system identifier: " UINT64_FORMAT "\n"), ControlFile.system_identifier);
+			printf(_("New system identifier: " UINT64_FORMAT "\n"), set_sysid);
+			printf(_("Continue? (y/n) "));
+			fflush(stdout);
+			
+			if (fgets(response, sizeof(response), stdin) == NULL ||
+				(response[0] != 'y' && response[0] != 'Y'))
+			{
+				printf(_("System identifier change cancelled.\n"));
+				exit(1);
+			}
+		}
+		
+		printf(_("Changing system identifier from " UINT64_FORMAT " to " UINT64_FORMAT "\n"), 
+			   ControlFile.system_identifier, set_sysid);
+		ControlFile.system_identifier = set_sysid;
+	}
+
 	if (minXlogSegNo > newXlogSegNo)
 		newXlogSegNo = minXlogSegNo;
 
@@ -875,6 +935,12 @@ PrintNewControlValues(void)
 		printf(_("Bytes per WAL segment:                %u\n"),
 			   ControlFile.xlog_seg_size);
 	}
+
+	if (set_sysid != 0)
+	{
+		printf(_("System identifier:                    " UINT64_FORMAT "\n"),
+			   ControlFile.system_identifier);
+	}
 }
 
 
@@ -1212,6 +1278,7 @@ usage(void)
 	printf(_("  -O, --multixact-offset=OFFSET    set next multitransaction offset\n"));
 	printf(_("  -u, --oldest-transaction-id=XID  set oldest transaction ID\n"));
 	printf(_("  -x, --next-transaction-id=XID    set next transaction ID\n"));
+	printf(_("  -s, --system-identifier=SYSID    set system identifier (requires confirmation or --force)\n"));
 	printf(_("      --char-signedness=OPTION     set char signedness to \"signed\" or \"unsigned\"\n"));
 	printf(_("      --wal-segsize=SIZE           size of WAL segments, in megabytes\n"));
 
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index d6bbbd0ceda..760722e744d 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -7,6 +7,7 @@ use warnings FATAL => 'all';
 use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
+use IPC::Run;
 
 program_help_ok('pg_resetwal');
 program_version_ok('pg_resetwal');
@@ -179,6 +180,184 @@ command_fails_like(
 	qr/error: invalid argument for option --char-signedness/,
 	'fails with incorrect --char-signedness option');
 
+# -s / --system-identifier
+command_fails_like(
+	[ 'pg_resetwal', '-s' => 'foo', $node->data_dir ],
+	qr/error: invalid argument for option -s/,
+	'fails with incorrect -s option');
+command_fails_like(
+	[ 'pg_resetwal', '--system-identifier' => 'bar', $node->data_dir ],
+	qr/error: invalid argument for option -s/,
+	'fails with incorrect --system-identifier option');
+command_fails_like(
+	[ 'pg_resetwal', '-s' => '0', $node->data_dir ],
+	qr/error: system identifier must be greater than 0/,
+	'fails with zero system identifier');
+command_fails_like(
+	[ 'pg_resetwal', '-s' => '-123', $node->data_dir ],
+	qr/error: system identifier must be greater than 0/,
+	'fails with negative system identifier');
+
+# Test system identifier change with dry-run
+command_like(
+	[ 'pg_resetwal', '-s' => '1234567890123456789', '--dry-run', $node->data_dir ],
+	qr/System identifier:\s+1234567890123456789/,
+	'system identifier change shows in dry-run output');
+
+# Test actual system identifier change with force flag
+$node->stop;
+my $new_sysid = '9876543210987654321';
+command_ok(
+	[ 'pg_resetwal', '-f', '-s' => $new_sysid, $node->data_dir ],
+	'pg_resetwal -s with force flag succeeds');
+
+# Verify the change was applied by checking pg_control
+$node->start;
+my $controldata_output = $node->safe_psql('postgres', 
+	"SELECT system_identifier FROM pg_control_system()");
+is($controldata_output, $new_sysid, 'system identifier was changed correctly');
+
+# Test that the server works normally after system identifier change
+is($node->safe_psql("postgres", "SELECT 1;"),
+	1, 'server running and working after system identifier change');
+
+$node->stop;
+
+# Test that system identifier change requires force flag when control values are guessed
+# Note: Interactive prompt testing is challenging due to stdin handling limitations
+command_fails_like(
+	[ 'pg_resetwal', '-s' => '1111111111111111111', $node->data_dir ],
+	qr/not proceeding because control file values were guessed/,
+	'system identifier change fails without force flag when control values are guessed');
+
+# Test non-TTY stdin handling (when stdin is not interactive)
+my $non_tty_test_node = PostgreSQL::Test::Cluster->new('non_tty_test');
+$non_tty_test_node->init;
+$non_tty_test_node->stop;
+
+# Test with stdin redirected from /dev/null (non-TTY)
+my ($stdin_null, $stdout_null, $stderr_null) = ('', '', '');
+my $null_harness = IPC::Run::start(
+	[ 'pg_resetwal', '-s', '3333333333333333333', $non_tty_test_node->data_dir ],
+	'<', '/dev/null', '>', \$stdout_null, '2>', \$stderr_null
+);
+$null_harness->finish();
+
+like($stderr_null, qr/standard input is not a TTY and --force was not specified/, 
+	'non-TTY stdin properly detected and rejected without --force');
+
+# Test that --force works with non-TTY stdin
+command_ok(
+	[ 'pg_resetwal', '-f', '-s', '4444444444444444444', $non_tty_test_node->data_dir ],
+	'system identifier change with --force works in non-TTY environment');
+
+# Verify the change was applied in non-TTY test
+$non_tty_test_node->start;
+my $non_tty_sysid = $non_tty_test_node->safe_psql('postgres', 
+	"SELECT system_identifier FROM pg_control_system()");
+is($non_tty_sysid, '4444444444444444444', 
+	'system identifier changed correctly with --force in non-TTY environment');
+$non_tty_test_node->stop;
+
+# Test interactive confirmation with 'n' response (cancellation)
+# We can test this by providing 'n' as input to stdin
+my $interactive_test_node = PostgreSQL::Test::Cluster->new('interactive_test');
+$interactive_test_node->init;
+$interactive_test_node->stop;
+
+# Create a test that simulates user saying 'n' to the confirmation prompt
+my ($stdin, $stdout, $stderr);
+my $harness = IPC::Run::start(
+	[ 'pg_resetwal', '-s', '7777777777777777777', $interactive_test_node->data_dir ],
+	'<', \$stdin, '>', \$stdout, '2>', \$stderr
+);
+
+# Send 'n' to decline the confirmation
+$stdin = "n\n";
+$harness->finish();
+
+like($stderr, qr/System identifier change cancelled/, 
+	'interactive confirmation properly cancels on n response');
+
+# Test interactive confirmation with 'y' response (acceptance)
+($stdin, $stdout, $stderr) = ('', '', '');
+$harness = IPC::Run::start(
+	[ 'pg_resetwal', '-s', '8888888888888888888', $interactive_test_node->data_dir ],
+	'<', \$stdin, '>', \$stdout, '2>', \$stderr
+);
+
+# Send 'y' to accept the confirmation
+$stdin = "y\n";
+$harness->finish();
+
+like($stdout, qr/Changing system identifier/, 
+	'interactive confirmation proceeds on y response');
+
+# Verify the change was applied
+$interactive_test_node->start;
+my $interactive_sysid = $interactive_test_node->safe_psql('postgres', 
+	"SELECT system_identifier FROM pg_control_system()");
+is($interactive_sysid, '8888888888888888888', 
+	'system identifier changed via interactive confirmation');
+$interactive_test_node->stop;
+
+# Test maximum valid 64-bit value
+my $max_sysid = '18446744073709551615';  # 2^64 - 1
+command_like(
+	[ 'pg_resetwal', '-s' => $max_sysid, '-f', '--dry-run', $node->data_dir ],
+	qr/System identifier:\s+18446744073709551615/,
+	'maximum 64-bit system identifier value accepted');
+
+# Test overflow detection
+command_fails_like(
+	[ 'pg_resetwal', '-s' => '99999999999999999999999999999', $node->data_dir ],
+	qr/error: system identifier value is out of range/,
+	'overflow system identifier value rejected');
+
+# Test hexadecimal input (should fail - only decimal accepted)
+command_fails_like(
+	[ 'pg_resetwal', '-s' => '0x123456789ABCDEF0', $node->data_dir ],
+	qr/error: invalid argument for option -s/,
+	'hexadecimal system identifier input rejected');
+
+# Test leading/trailing whitespace (should fail)
+command_fails_like(
+	[ 'pg_resetwal', '-s' => ' 123456789 ', $node->data_dir ],
+	qr/error: invalid argument for option -s/,
+	'system identifier with whitespace rejected');
+
+# Test empty string
+command_fails_like(
+	[ 'pg_resetwal', '-s' => '', $node->data_dir ],
+	qr/error: invalid argument for option -s/,
+	'empty system identifier rejected');
+
+# Test boundary values
+command_like(
+	[ 'pg_resetwal', '-s' => '1', '-f', '--dry-run', $node->data_dir ],
+	qr/System identifier:\s+1/,
+	'minimum valid system identifier (1) accepted');
+
+# Test very large but valid value
+command_like(
+	[ 'pg_resetwal', '-s' => '9223372036854775807', '-f', '--dry-run', $node->data_dir ],
+	qr/System identifier:\s+9223372036854775807/,
+	'large valid system identifier accepted');
+
+# Test another system identifier change to verify functionality
+my $another_sysid = '5555555555555555555';
+command_ok(
+	[ 'pg_resetwal', '-f', '-s' => $another_sysid, $node->data_dir ],
+	'second system identifier change succeeds');
+
+# Verify the second change
+$node->start;
+my $second_controldata = $node->safe_psql('postgres', 
+	"SELECT system_identifier FROM pg_control_system()");
+is($second_controldata, $another_sysid, 'second system identifier change verified');
+
+$node->stop;
+
 # run with control override options
 
 my $out = (run_command([ 'pg_resetwal', '--dry-run', $node->data_dir ]))[0];
-- 
GitLab

#2Kirk Wolak
wolakk@gmail.com
In reply to: Nikolay Samokhvalov (#1)
Re: Add --system-identifier / -s option to pg_resetwal

On Sat, May 31, 2025 at 2:52 PM Nikolay Samokhvalov <nik@postgres.ai> wrote:

I was just involved in a DR case, where we needed to change system ID in
control data, and noticed that there is no such capability, officially – so
I thought, it would be good to have it

the attached patch adds a new -s / --system-identifier option to
pg_resetwal that allows users to change the database cluster's system
identifier; it can be useful when you need to make a restored cluster
distinct from the original, or when cloning for testing

some aspects about the patch:
- accepts positive 64-bit integers only (zero not allowed)
- requires interactive confirmation or --force flag for safety
- detects non-TTY environments and requires --force in scripts
- included input validation and error handling
- updated docs with clear warnings about compatibility
- added tests, including some edge cases

looking forward to hearing feedback!

+1

Like the idea. I did a manual code review. It's a small patch, very
focused, and includes the proper docs.
It's very well thought out (I Iike the --force requirement for scripts).

The only thing I saw was that the code basically accepts Y or y, and all
else is considered "N". for that reason,
I suggest that this line:
printf(_("Continue? (y/n) "));

Would be more clear as:
printf(_("Continue? (y/n) [n]"));

indicating the N is the default answer. if you put anything other than Y
or y... N will be picked.

But this is almost trivial, but I wanted to be thorough.

Regards!

#3Peter Eisentraut
peter@eisentraut.org
In reply to: Nikolay Samokhvalov (#1)
Re: Add --system-identifier / -s option to pg_resetwal

On 31.05.25 20:52, Nikolay Samokhvalov wrote:

the attached patch adds a new -s / --system-identifier option to
pg_resetwal that allows users to change the database cluster's system
identifier; it can be useful when you need to make a restored cluster
distinct from the original, or when cloning for testing

Maybe we can stop eating up short options? A long option seems
sufficient for this.

- requires interactive confirmation or --force flag for safety

I don't see the need for interactive confirmation here. The user would
have explicitly chosen the option, so they get what they asked for.

#4Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#3)
Re: Add --system-identifier / -s option to pg_resetwal

On Wed, Jun 04, 2025 at 06:49:31AM +0200, Peter Eisentraut wrote:

Maybe we can stop eating up short options? A long option seems sufficient
for this.

Agreed.

I don't see the need for interactive confirmation here. The user would have
explicitly chosen the option, so they get what they asked for.

Yes, let's remove that. The patch is making --force hold a different
meaning that it currently has. And the tests will have less bloat
with their TTY parts gone.

Honestly, I don't think that we need that many tests in the scope of
this patch. Let's drop the hexa test, the empty test, the test with a
value of 1, the very-large-but-valid-value, and the second update.
These overlap with other tests or just rely on internals of getopt()
or of strtou64(), which eat cycles without really any added value.

+   The system identifier is a unique 64-bit number that identifies the
+   database cluster. It is used by replication systems and backup tools
+   to ensure they are working with the correct cluster. Changing the
+   system identifier makes the cluster incompatible with existing
+   backups, standby servers, and replication setups.

The first sentence of this paragraph is a close copy-paste of what's
in protocol.sgml. It would be nice to avoid these duplications.

Note the report generated by `git diff --check`, while on it, with a
couple of whitespaces reported.

FWIW, I've wanted that in the past when err.. Performing some
chirurgy on clusters.
--
Michael

#5Nikolay Samokhvalov
nik@postgres.ai
In reply to: Michael Paquier (#4)
1 attachment(s)
Re: Add --system-identifier / -s option to pg_resetwal

Thank you, Peter and Michael, for the reviews

Attached is v2, simplified as suggested:
- Removed short option -s
- Removed interactive confirmation and --force
- Simplified tests leaving only essential ones

Additionally, there was an off-list review done by Andrey Borodin, so his
comments also addressed:
- Simplified logic, getting rid of '-' check (negative numbers) -- decided
to accept negative input values (they wrap to valid positive uint64)

Nik

Attachments:

0002-pg_resetwal--system-identifier.patchapplication/octet-stream; name=0002-pg_resetwal--system-identifier.patchDownload
From e19ab85f17cc74bfa8df57d249b85a83fd859ac7 Mon Sep 17 00:00:00 2001
From: Nikolay Samokhvalov <nik@postgres.ai>
Date: Wed, 4 Jun 2025 11:37:43 -0700
Subject: [PATCH] [PATCH] Add --system-identifier option to pg_resetwal

This patch adds a new --system-identifier option to pg_resetwal that allows
users to change the database cluster's system identifier. This feature is
useful in recovery scenarios where a restored cluster needs to be made
distinct from the original.

The system identifier change makes the cluster incompatible with existing
backups, standby servers, and replication setups, so the feature includes
appropriate safety checks and user warnings.
---
 doc/src/sgml/ref/pg_resetwal.sgml  | 23 +++++++++++++++++++++++
 src/bin/pg_resetwal/pg_resetwal.c  | 25 +++++++++++++++++++++++++
 src/bin/pg_resetwal/t/001_basic.pl | 29 +++++++++++++++++++++++++++++
 3 files changed, 77 insertions(+)

diff --git a/doc/src/sgml/ref/pg_resetwal.sgml b/doc/src/sgml/ref/pg_resetwal.sgml
index 2c019c2aac6..7ee1c2a08b2 100644
--- a/doc/src/sgml/ref/pg_resetwal.sgml
+++ b/doc/src/sgml/ref/pg_resetwal.sgml
@@ -356,6 +356,29 @@ PostgreSQL documentation
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><option>--system-identifier=<replaceable class="parameter">system_identifier</replaceable></option></term>
+    <listitem>
+     <para>
+      Manually set the database system identifier.
+     </para>
+
+     <para>
+      This option should only be used in recovery scenarios where you need
+      to make a restored cluster distinct from the original, or when cloning
+      a cluster for testing purposes. The value must be a positive 64-bit
+      integer and cannot be zero.
+     </para>
+
+     <warning>
+      <para>
+       Changing the system identifier will break compatibility with existing
+       backups and standby servers. Use this option with extreme caution.
+      </para>
+     </warning>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><option>--char-signedness=<replaceable class="parameter">option</replaceable></option></term>
     <listitem>
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index e876f35f38e..995f4b166b9 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -76,6 +76,7 @@ static XLogSegNo minXlogSegNo = 0;
 static int	WalSegSz;
 static int	set_wal_segsize;
 static int	set_char_signedness = -1;
+static uint64 set_sysid = 0;
 
 static void CheckDataVersion(void);
 static bool read_controlfile(void);
@@ -105,6 +106,7 @@ main(int argc, char *argv[])
 		{"next-oid", required_argument, NULL, 'o'},
 		{"multixact-offset", required_argument, NULL, 'O'},
 		{"oldest-transaction-id", required_argument, NULL, 'u'},
+		{"system-identifier", required_argument, NULL, 3},
 		{"next-transaction-id", required_argument, NULL, 'x'},
 		{"wal-segsize", required_argument, NULL, 1},
 		{"char-signedness", required_argument, NULL, 2},
@@ -321,6 +323,19 @@ main(int argc, char *argv[])
 					break;
 				}
 
+			case 3:
+				errno = 0;
+				set_sysid = strtou64(optarg, &endptr, 0);
+				if (endptr == optarg || *endptr != '\0' || errno != 0)
+				{
+					pg_log_error("invalid argument for option %s", "--system-identifier");
+					pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+					exit(1);
+				}
+				if (set_sysid == 0)
+					pg_fatal("system identifier must not be 0");
+				break;
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -478,6 +493,9 @@ main(int argc, char *argv[])
 	if (set_char_signedness != -1)
 		ControlFile.default_char_signedness = (set_char_signedness == 1);
 
+	if (set_sysid != 0)
+		ControlFile.system_identifier = set_sysid;
+
 	if (minXlogSegNo > newXlogSegNo)
 		newXlogSegNo = minXlogSegNo;
 
@@ -875,6 +893,12 @@ PrintNewControlValues(void)
 		printf(_("Bytes per WAL segment:                %u\n"),
 			   ControlFile.xlog_seg_size);
 	}
+
+	if (set_sysid != 0)
+	{
+		printf(_("System identifier:                    " UINT64_FORMAT "\n"),
+			   ControlFile.system_identifier);
+	}
 }
 
 
@@ -1212,6 +1236,7 @@ usage(void)
 	printf(_("  -O, --multixact-offset=OFFSET    set next multitransaction offset\n"));
 	printf(_("  -u, --oldest-transaction-id=XID  set oldest transaction ID\n"));
 	printf(_("  -x, --next-transaction-id=XID    set next transaction ID\n"));
+	printf(_("      --system-identifier=SYSID    set system identifier\n"));
 	printf(_("      --char-signedness=OPTION     set char signedness to \"signed\" or \"unsigned\"\n"));
 	printf(_("      --wal-segsize=SIZE           size of WAL segments, in megabytes\n"));
 
diff --git a/src/bin/pg_resetwal/t/001_basic.pl b/src/bin/pg_resetwal/t/001_basic.pl
index d6bbbd0ceda..c740759882c 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -179,6 +179,35 @@ command_fails_like(
 	qr/error: invalid argument for option --char-signedness/,
 	'fails with incorrect --char-signedness option');
 
+# --system-identifier
+command_fails_like(
+	[ 'pg_resetwal', '--system-identifier' => 'foo', $node->data_dir ],
+	qr/error: invalid argument for option --system-identifier/,
+	'fails with incorrect --system-identifier option');
+command_fails_like(
+	[ 'pg_resetwal', '--system-identifier' => '0', $node->data_dir ],
+	qr/system identifier must not be 0/,
+	'fails with zero system identifier');
+
+# Test actual system identifier change with force flag
+$node->stop;
+my $new_sysid = '9876543210987654321';
+command_ok(
+	[ 'pg_resetwal', '-f', '--system-identifier' => $new_sysid, $node->data_dir ],
+	'pg_resetwal --system-identifier with force flag succeeds');
+
+# Verify the change was applied by checking pg_control
+$node->start;
+my $controldata_output = $node->safe_psql('postgres', 
+	"SELECT system_identifier FROM pg_control_system()");
+is($controldata_output, $new_sysid, 'system identifier was changed correctly');
+
+# Test that the server works normally after system identifier change
+is($node->safe_psql("postgres", "SELECT 1;"),
+	1, 'server running and working after system identifier change');
+
+$node->stop;
+
 # run with control override options
 
 my $out = (run_command([ 'pg_resetwal', '--dry-run', $node->data_dir ]))[0];
-- 
2.39.5 (Apple Git-154)

#6Peter Eisentraut
peter@eisentraut.org
In reply to: Nikolay Samokhvalov (#5)
Re: Add --system-identifier / -s option to pg_resetwal

On 04.06.25 20:46, Nikolay Samokhvalov wrote:

- Simplified logic, getting rid of '-' check (negative numbers) --
decided to accept negative input values (they wrap to valid positive uint64)

That doesn't seem like a good idea to me.

#7Fujii Masao
masao.fujii@oss.nttdata.com
In reply to: Nikolay Samokhvalov (#5)
Re: Add --system-identifier / -s option to pg_resetwal

On 2025/06/05 3:46, Nikolay Samokhvalov wrote:

Thank you, Peter and Michael, for the reviews

Attached is v2, simplified as suggested:
- Removed short option -s
- Removed interactive confirmation and --force
- Simplified tests leaving only essential ones

In my environment, the test failed with the following output:

# Failed test 'system identifier was changed correctly'
# at t/001_basic.pl line 203.
# got: '-8570200862721897295'
# expected: '9876543210987654321'
# Looks like you failed 1 test of 84.
t/001_basic.pl ......
Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/84 subtests

I think this happens because the system_identifier returned
by pg_control_system() is a bigint, not an unsigned one.
So either we need to use a different method to retrieve
the value correctly, or we should fix pg_control_system() to
report the system identifier properly (though that would be
a separate issue).

+	if (set_sysid != 0)
+	{
+		printf(_("System identifier:                    " UINT64_FORMAT "\n"),
+			   ControlFile.system_identifier);
+	}

For consistency with PrintControlValues() and pg_controldata,
the label should be "Database system identifier", and we should
use PRIu64 instead of UINT64_FORMAT for gettext()?

+ {"system-identifier", required_argument, NULL, 3},
{"next-transaction-id", required_argument, NULL, 'x'},
{"wal-segsize", required_argument, NULL, 1},
{"char-signedness", required_argument, NULL, 2},

It would be more consistent to place the "system-identifier"
entry just after "char-signedness".

One question about this feature: why do we need to allow
explicitly setting the system identifier? If the goal is
simply to ensure it's different from the original,
wouldn't it be sufficient to let pg_resetwal generate
a new (hopefully unique) value using existing logic,
like what's done in GuessControlValues() or BootStrapXLOG()?

Regards,

--
Fujii Masao
NTT DATA Japan Corporation