summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/pg_rewind/filemap.c55
-rw-r--r--src/bin/pg_rewind/filemap.h3
-rw-r--r--src/bin/pg_rewind/meson.build1
-rw-r--r--src/bin/pg_rewind/pg_rewind.c9
-rw-r--r--src/bin/pg_rewind/t/011_wal_copy.pl122
5 files changed, 183 insertions, 7 deletions
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 00f5d60d620..467fd97ebcf 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -546,7 +546,9 @@ print_filemap(filemap_t *filemap)
for (i = 0; i < filemap->nentries; i++)
{
entry = filemap->entries[i];
+
if (entry->action != FILE_ACTION_NONE ||
+ entry->content_type == FILE_CONTENT_TYPE_WAL ||
entry->target_pages_to_overwrite.bitmapsize > 0)
{
pg_log_debug("%s (%s)", entry->path,
@@ -707,10 +709,44 @@ final_filemap_cmp(const void *a, const void *b)
}
/*
+ * Decide what to do with a WAL segment file based on its position
+ * relative to the point of divergence.
+ *
+ * Caller is responsible for ensuring that the file exists on both
+ * source and target servers.
+ */
+static file_action_t
+decide_wal_file_action(const char *fname, XLogSegNo last_common_segno,
+ size_t source_size, size_t target_size)
+{
+ TimeLineID file_tli;
+ XLogSegNo file_segno;
+
+ /* Get current WAL segment number given current segment file name */
+ XLogFromFileName(fname, &file_tli, &file_segno, WalSegSz);
+
+ /*
+ * Avoid copying files before the last common segment.
+ *
+ * These files exist on the source and the target servers, so they should
+ * be identical and located strictly before the segment that contains the
+ * LSN where target and source servers have diverged.
+ *
+ * While we are on it, double-check the size of each file and copy the
+ * file if they do not match, in case.
+ */
+ if (file_segno < last_common_segno &&
+ source_size == target_size)
+ return FILE_ACTION_NONE;
+
+ return FILE_ACTION_COPY;
+}
+
+/*
* Decide what action to perform to a file.
*/
static file_action_t
-decide_file_action(file_entry_t *entry)
+decide_file_action(file_entry_t *entry, XLogSegNo last_common_segno)
{
const char *path = entry->path;
@@ -814,8 +850,17 @@ decide_file_action(file_entry_t *entry)
case FILE_TYPE_REGULAR:
if (entry->content_type == FILE_CONTENT_TYPE_WAL)
{
- /* It's a WAL file, copy it. */
- return FILE_ACTION_COPY;
+ /* Handle WAL segment file */
+ const char *filename = last_dir_separator(entry->path);
+
+ if (filename == NULL)
+ filename = entry->path;
+ else
+ filename++; /* Skip the separator */
+
+ return decide_wal_file_action(filename, last_common_segno,
+ entry->source_size,
+ entry->target_size);
}
else if (entry->content_type != FILE_CONTENT_TYPE_RELATION)
{
@@ -876,7 +921,7 @@ decide_file_action(file_entry_t *entry)
* should be executed.
*/
filemap_t *
-decide_file_actions(void)
+decide_file_actions(XLogSegNo last_common_segno)
{
int i;
filehash_iterator it;
@@ -886,7 +931,7 @@ decide_file_actions(void)
filehash_start_iterate(filehash, &it);
while ((entry = filehash_iterate(filehash, &it)) != NULL)
{
- entry->action = decide_file_action(entry);
+ entry->action = decide_file_action(entry, last_common_segno);
}
/*
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index fada420fc23..5145f0b4c46 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -11,6 +11,7 @@
#include "datapagemap.h"
#include "storage/block.h"
#include "storage/relfilelocator.h"
+#include "access/xlogdefs.h"
/* these enum values are sorted in the order we want actions to be processed */
typedef enum
@@ -113,7 +114,7 @@ extern void process_target_wal_block_change(ForkNumber forknum,
RelFileLocator rlocator,
BlockNumber blkno);
-extern filemap_t *decide_file_actions(void);
+extern filemap_t *decide_file_actions(XLogSegNo last_common_segno);
extern void calculate_totals(filemap_t *filemap);
extern void print_filemap(filemap_t *filemap);
diff --git a/src/bin/pg_rewind/meson.build b/src/bin/pg_rewind/meson.build
index 36171600cca..97f001d94a5 100644
--- a/src/bin/pg_rewind/meson.build
+++ b/src/bin/pg_rewind/meson.build
@@ -44,6 +44,7 @@ tests += {
't/008_min_recovery_point.pl',
't/009_growing_files.pl',
't/010_keep_recycled_wals.pl',
+ 't/011_wal_copy.pl',
],
},
}
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 0c68dd4235e..1b953692b17 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -147,6 +147,7 @@ main(int argc, char **argv)
TimeLineID source_tli;
TimeLineID target_tli;
XLogRecPtr target_wal_endrec;
+ XLogSegNo last_common_segno;
size_t size;
char *buffer;
bool no_ensure_shutdown = false;
@@ -398,6 +399,12 @@ main(int argc, char **argv)
targetHistory[lastcommontliIndex].tli);
/*
+ * Convert the divergence LSN to a segment number, that will be used
+ * to decide how WAL segments should be processed.
+ */
+ XLByteToSeg(divergerec, last_common_segno, ControlFile_target.xlog_seg_size);
+
+ /*
* Don't need the source history anymore. The target history is still
* needed by the routines in parsexlog.c, when we read the target WAL.
*/
@@ -492,7 +499,7 @@ main(int argc, char **argv)
* We have collected all information we need from both systems. Decide
* what to do with each file.
*/
- filemap = decide_file_actions();
+ filemap = decide_file_actions(last_common_segno);
if (showprogress)
calculate_totals(filemap);
diff --git a/src/bin/pg_rewind/t/011_wal_copy.pl b/src/bin/pg_rewind/t/011_wal_copy.pl
new file mode 100644
index 00000000000..89ef2590ed9
--- /dev/null
+++ b/src/bin/pg_rewind/t/011_wal_copy.pl
@@ -0,0 +1,122 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+#
+# Check how the copy of WAL segments is handled from the source to
+# the target server.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Utils;
+use Test::More;
+use File::stat qw(stat);
+
+use FindBin;
+use lib $FindBin::RealBin;
+use RewindTest;
+
+RewindTest::setup_cluster();
+RewindTest::start_primary();
+RewindTest::create_standby();
+
+# Advance WAL on primary
+RewindTest::primary_psql("CREATE TABLE t(a int)");
+RewindTest::primary_psql("INSERT INTO t VALUES(0)");
+
+# Segment that is not copied from the source to the target, being
+# generated before the servers have diverged.
+my $wal_seg_skipped = $node_primary->safe_psql('postgres',
+ 'SELECT pg_walfile_name(pg_current_wal_lsn())');
+
+RewindTest::primary_psql("SELECT pg_switch_wal()");
+
+# Follow-up segment, that will include corrupted contents, and will be
+# copied from the source to the target even if generated before the point
+# of divergence.
+RewindTest::primary_psql("INSERT INTO t VALUES(0)");
+my $corrupt_wal_seg = $node_primary->safe_psql('postgres',
+ 'SELECT pg_walfile_name(pg_current_wal_lsn())');
+RewindTest::primary_psql("SELECT pg_switch_wal()");
+
+RewindTest::primary_psql("CHECKPOINT");
+RewindTest::promote_standby;
+
+# New segment on a new timeline, expected to be copied.
+my $new_timeline_wal_seg = $node_standby->safe_psql('postgres',
+ 'SELECT pg_walfile_name(pg_current_wal_lsn())');
+
+# Corrupt a WAL segment on target that has been generated before the
+# divergence point. We will check that it is copied from the source.
+my $corrupt_wal_seg_in_target_path =
+ $node_primary->data_dir . '/pg_wal/' . $corrupt_wal_seg;
+open my $fh, ">>", $corrupt_wal_seg_in_target_path
+ or die "could not open $corrupt_wal_seg_in_target_path";
+
+print $fh 'a';
+close $fh;
+
+my $corrupt_wal_seg_stat_before_rewind =
+ stat($corrupt_wal_seg_in_target_path);
+ok(defined($corrupt_wal_seg_stat_before_rewind),
+ "segment $corrupt_wal_seg exists in target before rewind");
+
+# Verify that the WAL segment on the new timeline does not exist in target
+# before the rewind.
+my $new_timeline_wal_seg_path =
+ $node_primary->data_dir . '/pg_wal/' . $new_timeline_wal_seg;
+my $new_timeline_wal_seg_stat = stat($new_timeline_wal_seg_path);
+ok(!defined($new_timeline_wal_seg_stat),
+ "segment $new_timeline_wal_seg does not exist in target before rewind");
+
+$node_standby->stop();
+$node_primary->stop();
+
+# Cross-check how WAL segments are handled:
+# - The "corrupted" segment generated before the point of divergence is
+# copied.
+# - The "clean" segment generated before the point of divergence is skipped.
+# - The segment of the new timeline is copied.
+command_checks_all(
+ [
+ 'pg_rewind', '--debug',
+ '--source-pgdata' => $node_standby->data_dir,
+ '--target-pgdata' => $node_primary->data_dir,
+ '--no-sync',
+ ],
+ 0,
+ [qr//],
+ [
+ qr/pg_wal\/$wal_seg_skipped \(NONE\)/,
+ qr/pg_wal\/$corrupt_wal_seg \(COPY\)/,
+ qr/pg_wal\/$new_timeline_wal_seg \(COPY\)/,
+ ],
+ 'run pg_rewind');
+
+# Verify that the first WAL segment of the new timeline now exists in
+# target.
+$new_timeline_wal_seg_stat = stat($new_timeline_wal_seg_path);
+ok(defined($new_timeline_wal_seg_stat),
+ "new timeline segment $new_timeline_wal_seg exists in target after rewind"
+);
+
+# Validate that the WAL segment with the same file name as the
+# corrupted WAL segment in target has been copied from source
+# where it was still intact.
+my $corrupt_wal_seg_in_source_path =
+ $node_standby->data_dir . '/pg_wal/' . $corrupt_wal_seg;
+my $corrupt_wal_seg_source_stat = stat($corrupt_wal_seg_in_source_path);
+ok(defined($corrupt_wal_seg_source_stat),
+ "corrupted $corrupt_wal_seg exists in source after rewind");
+
+my $corrupt_wal_seg_stat_after_rewind = stat($corrupt_wal_seg_in_target_path);
+ok(defined($corrupt_wal_seg_stat_after_rewind),
+ "corrupted $corrupt_wal_seg exists in target after rewind");
+isnt(
+ $corrupt_wal_seg_stat_before_rewind->size,
+ $corrupt_wal_seg_source_stat->size,
+ "different size of corrupted $corrupt_wal_seg in source vs target before rewind"
+);
+is( $corrupt_wal_seg_stat_after_rewind->size,
+ $corrupt_wal_seg_source_stat->size,
+ "same size of corrupted $corrupt_wal_seg in source and target after rewind"
+);
+
+done_testing();