Ensure that a standby is able to follow a primary on a newer timeline.
authorFujii Masao
Thu, 14 Jan 2021 03:28:47 +0000 (12:28 +0900)
committerFujii Masao
Thu, 14 Jan 2021 03:28:47 +0000 (12:28 +0900)
Commit 709d003fbd refactored WAL-reading code, but accidentally caused
WalSndSegmentOpen() to fail to follow a timeline switch while reading from
a historic timeline. This issue caused a standby to fail to follow a primary
on a newer timeline when WAL archiving is enabled.

If there is a timeline switch within the segment, WalSndSegmentOpen() should
read from the WAL segment belonging to the new timeline. But previously
since it failed to follow a timeline switch, it tried to read the WAL segment
with old timeline. When WAL archiving is enabled, that WAL segment with
old timeline doesn't exist because it's renamed to .partial. This leads
a primary to have tried to read non-existent WAL segment, and which caused
replication to faill with the error "ERROR:  requested WAL segment ... has
 already been removed".

This commit fixes WalSndSegmentOpen() so that it's able to follow a timeline
switch, to ensure that a standby is able to follow a primary on a newer
timeline even when WAL archiving is enabled.

This commit also adds the regression test to check whether a standby is
able to follow a primary on a newer timeline when WAL archiving is enabled.

Back-patch to v13 where the bug was introduced.

Reported-by: Kyotaro Horiguchi
Author: Kyotaro Horiguchi, tweaked by Fujii Masao
Reviewed-by: Alvaro Herrera, Fujii Masao
Discussion: https://postgr.es/m/20201209.174314.282492377848029776[email protected]

src/backend/replication/walsender.c
src/test/recovery/t/004_timeline_switch.pl

index 7f87eb7f199876081134d434a19b672e75f0133c..04f6c3ebb4ab9f33bd4c2a68bacb733c0e2cb5a9 100644 (file)
@@ -2478,7 +2478,7 @@ WalSndSegmentOpen(XLogReaderState *state, XLogSegNo nextSegNo,
        XLogSegNo   endSegNo;
 
        XLByteToSeg(sendTimeLineValidUpto, endSegNo, state->segcxt.ws_segsize);
-       if (state->seg.ws_segno == endSegNo)
+       if (nextSegNo == endSegNo)
            *tli_p = sendTimeLineNextTLI;
    }
 
index 7e952d366763de3ce2a675ca625611e8f7745547..edadab790f30b9c1a4e343f9ea0cd47075f44b34 100644 (file)
@@ -1,15 +1,16 @@
 # Test for timeline switch
-# Ensure that a cascading standby is able to follow a newly-promoted standby
-# on a new timeline.
 use strict;
 use warnings;
 use File::Path qw(rmtree);
 use PostgresNode;
 use TestLib;
-use Test::More tests => 2;
+use Test::More tests => 3;
 
 $ENV{PGDATABASE} = 'postgres';
 
+# Ensure that a cascading standby is able to follow a newly-promoted standby
+# on a new timeline.
+
 # Initialize master node
 my $node_master = get_new_node('master');
 $node_master->init(allows_streaming => 1);
@@ -66,3 +67,38 @@ $node_standby_1->wait_for_catchup($node_standby_2, 'replay',
 my $result =
   $node_standby_2->safe_psql('postgres', "SELECT count(*) FROM tab_int");
 is($result, qq(2000), 'check content of standby 2');
+
+
+# Ensure that a standby is able to follow a master on a newer timeline
+# when WAL archiving is enabled.
+
+# Initialize master node
+my $node_master_2 = get_new_node('master_2');
+$node_master_2->init(allows_streaming => 1, has_archiving => 1);
+$node_master_2->start;
+
+# Take backup
+$node_master_2->backup($backup_name);
+
+# Create standby node
+my $node_standby_3 = get_new_node('standby_3');
+$node_standby_3->init_from_backup($node_master_2, $backup_name,
+   has_streaming => 1);
+
+# Restart master node in standby mode and promote it, switching it
+# to a new timeline.
+$node_master_2->set_standby_mode;
+$node_master_2->restart;
+$node_master_2->promote;
+
+# Start standby node, create some content on master and check its presence
+# in standby, to ensure that the timeline switch has been done.
+$node_standby_3->start;
+$node_master_2->safe_psql('postgres',
+   "CREATE TABLE tab_int AS SELECT 1 AS a");
+$node_master_2->wait_for_catchup($node_standby_3, 'replay',
+   $node_master_2->lsn('write'));
+
+my $result_2 =
+  $node_standby_3->safe_psql('postgres', "SELECT count(*) FROM tab_int");
+is($result_2, qq(1), 'check content of standby 3');