v25-0007-TEST-Add-test-for-64-bit-mxoff-in-pg_resetwal.patch
text/x-patch
Filename: v25-0007-TEST-Add-test-for-64-bit-mxoff-in-pg_resetwal.patch
Type: text/x-patch
Part: 6
Message:
Re: POC: make mxidoff 64 bits
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v25-0007
Subject: TEST: Add test for 64-bit mxoff in pg_resetwal
| File | + | − |
|---|---|---|
| src/bin/pg_resetwal/meson.build | 1 | 0 |
| src/bin/pg_resetwal/t/003_mxoff.pl | 170 | 0 |
From d3fdf8da398df15a6e44d52364b65783f89c360d Mon Sep 17 00:00:00 2001
From: Maxim Orlov <orlovmg@gmail.com>
Date: Tue, 28 Oct 2025 19:08:26 +0300
Subject: [PATCH v25 07/10] TEST: Add test for 64-bit mxoff in pg_resetwal
---
src/bin/pg_resetwal/meson.build | 1 +
src/bin/pg_resetwal/t/003_mxoff.pl | 170 +++++++++++++++++++++++++++++
2 files changed, 171 insertions(+)
create mode 100644 src/bin/pg_resetwal/t/003_mxoff.pl
diff --git a/src/bin/pg_resetwal/meson.build b/src/bin/pg_resetwal/meson.build
index 290832b2299..1e2dfb38a5b 100644
--- a/src/bin/pg_resetwal/meson.build
+++ b/src/bin/pg_resetwal/meson.build
@@ -25,6 +25,7 @@ tests += {
'tests': [
't/001_basic.pl',
't/002_corrupted.pl',
+ 't/003_mxoff.pl',
],
},
}
diff --git a/src/bin/pg_resetwal/t/003_mxoff.pl b/src/bin/pg_resetwal/t/003_mxoff.pl
new file mode 100644
index 00000000000..3c1b7fa1d33
--- /dev/null
+++ b/src/bin/pg_resetwal/t/003_mxoff.pl
@@ -0,0 +1,170 @@
+
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+use strict;
+use warnings FATAL => 'all';
+
+use Math::BigInt;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+sub mxact_eater
+{
+ my $node = shift;
+ my $tbl = shift;
+
+ $node->start;
+ $node->safe_psql('postgres',
+ "CREATE TABLE ${tbl} (I INT PRIMARY KEY, N_UPDATED INT) " .
+ " WITH (AUTOVACUUM_ENABLED=FALSE);" .
+ "INSERT INTO ${tbl} SELECT G, 0 FROM GENERATE_SERIES(1, 50) G;");
+
+ # consume around 10k multixact-offsetfs
+ my $nclients = 10;
+ my $update_every = 75;
+ my @connections = ();
+
+ for (0..$nclients)
+ {
+ my $conn = $node->background_psql('postgres');
+ $conn->query_safe("BEGIN");
+
+ push(@connections, $conn);
+ }
+
+ for (my $i = 0; $i < 1000; $i++)
+ {
+ my $conn = $connections[$i % $nclients];
+
+ $conn->query_safe("COMMIT;");
+ $conn->query_safe("BEGIN");
+
+ if ($i % $update_every == 0)
+ {
+ $conn->query_safe(
+ "UPDATE ${tbl} SET " .
+ "N_UPDATED = N_UPDATED + 1 " .
+ "WHERE I = ${i} % 50");
+ }
+ else
+ {
+ $conn->query_safe(
+ "SELECT * FROM ${tbl} FOR KEY SHARE");
+ }
+ }
+
+ for my $conn (@connections)
+ {
+ $conn->quit();
+ }
+
+ $node->stop;
+}
+
+sub next_mxoff
+{
+ my $node = shift;
+ my ($stdout, $stderr) =
+ run_command([ 'pg_controldata', $node->data_dir ]);
+ my @control_data = split("\n", $stdout);
+ my $next_mxoff = undef;
+
+ foreach (@control_data)
+ {
+ if ($_ =~ /^Latest checkpoint's NextMultiOffset:\s*(.*)$/mg)
+ {
+ $next_mxoff = $1;
+ last;
+ }
+ }
+ die "NextMultiOffset not found in control file\n"
+ unless defined($next_mxoff);
+
+ return $next_mxoff;
+}
+
+sub reset_mxoff
+{
+ my $node = shift;
+ my $offset = shift;
+ $offset = Math::BigInt->new($offset);
+
+ # Get block size
+ my $out = (run_command([ 'pg_resetwal', '--dry-run', $node->data_dir ]))[0];
+ $out =~ /^Database block size: *(\d+)$/m or die;
+ my $blcksz = $1;
+
+ # Reset to new offset
+ my @cmd = ('pg_resetwal', '--pgdata' => $node->data_dir);
+ push @cmd, '--multixact-offset' => $offset->as_hex();
+ command_ok(\@cmd, 'set oldest multixact-offset');
+
+ # Fill empty pg_multixact/members segment
+ my $mult = 32 * int($blcksz / 20) * 4;
+ my $segname = sprintf "%015X", $offset / $mult;
+
+ my @dd = ('dd');
+ push @dd, "if=/dev/zero";
+ push @dd, "of=" . $node->data_dir . "/pg_multixact/members/" . $segname;
+ push @dd, "bs=$blcksz";
+ push @dd, "count=32";
+ command_ok(\@dd, 'fill empty multixact-members');
+}
+
+my ($off1, $off2);
+
+# start from defaults
+my $node1 = PostgreSQL::Test::Cluster->new('node1');
+$node1->init;
+$off1 = next_mxoff($node1);
+mxact_eater($node1, "FOO");
+$off2 = next_mxoff($node1);
+note "> start from $off1, finished at $off2\n";
+
+# start from before 32-bit wraparound
+my $node2 = PostgreSQL::Test::Cluster->new('node2');
+$node2->init;
+reset_mxoff($node2, 0xFFFF0000);
+$off1 = next_mxoff($node2);
+mxact_eater($node2, "FOO");
+$off2 = next_mxoff($node2);
+note "> start from $off1, finished at $off2\n";
+
+# start near 32-bit wraparound
+my $node3 = PostgreSQL::Test::Cluster->new('node3');
+$node3->init;
+reset_mxoff($node3, 0xFFFFEC77);
+$off1 = next_mxoff($node3);
+mxact_eater($node3, "FOO");
+$off2 = next_mxoff($node3);
+note "> start from $off1, finished at $off2\n";
+
+# start over 32-bit wraparound
+my $node4 = PostgreSQL::Test::Cluster->new('node4');
+$node4->init;
+reset_mxoff($node4, '0xFFFFFFFF0000');
+$off1 = next_mxoff($node4);
+mxact_eater($node4, "FOO");
+$off2 = next_mxoff($node3);
+note "> start from $off1, finished at $off2\n";
+
+# check invariant
+$node1->start;
+$node2->start;
+$node3->start;
+$node4->start;
+
+my $var1 = $node1->safe_psql('postgres', 'TABLE FOO');
+my $var2 = $node2->safe_psql('postgres', 'TABLE FOO');
+my $var3 = $node3->safe_psql('postgres', 'TABLE FOO');
+my $var4 = $node4->safe_psql('postgres', 'TABLE FOO');
+ok($var1 eq $var2 eq $var3 eq $var4,
+ 'check table invariant in all nodes');
+
+$node4->stop;
+$node3->stop;
+$node2->stop;
+$node1->stop;
+
+done_testing();
--
2.47.3