diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fcbde10529..083fe760f3 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -7936,10 +7936,9 @@ xlog_redo(XLogReaderState *record) memcpy(&xlrec, XLogRecGetData(record), sizeof(xl_end_of_recovery)); /* - * For Hot Standby, we could treat this like a Shutdown Checkpoint, - * but this case is rarer and harder to test, so the benefit doesn't - * outweigh the potential extra cost of maintenance. + * For Hot Standby, we could treat this like an end-of-recovery checkpoint */ + RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT); /* * We should've already switched to the new TLI before replaying this diff --git a/src/bin/pg_rewind/t/010_standby_source_following_new_primary.pl b/src/bin/pg_rewind/t/010_standby_source_following_new_primary.pl new file mode 100644 index 0000000000..bbd6fff536 --- /dev/null +++ b/src/bin/pg_rewind/t/010_standby_source_following_new_primary.pl @@ -0,0 +1,157 @@ + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +# +# Test using a standby server that follows new primary as the source. +# +# This sets up three nodes: A, B and C. First, A is the primary, +# B follows A, and C follows B: +# +# A (primary) <--- B (standby) <--- C (standby) +# +# +# Then we promote B, and insert some divergent rows in A and B: +# +# A (primary) B (primary) <--- C (standby) +# +# +# Finally, we run pg_rewind on A, to point it at C: +# +# B (primary) <--- C (standby) <--- A (standby) +# +# We was not able to rewind until checkpointing in this scenario due to +# the bug that cascade standby (i.e. C) does not follow the new +# primary's (i.e. B's) minRecoveryPoint and minRecoveryPointTLI. +# +# Since we're dealing with three nodes, we cannot use most of the +# RewindTest functions as is. + +use strict; +use warnings; +use PostgreSQL::Test::Utils; +use Test::More; + +use FindBin; +use lib $FindBin::RealBin; +use File::Copy; +use PostgreSQL::Test::Cluster; +use RewindTest; + +my $tmp_folder = PostgreSQL::Test::Utils::tempdir; + +my $node_a; +my $node_b; +my $node_c; + +# Set up node A, as primary +# +# A (primary) + +setup_cluster('a'); +start_primary(); +$node_a = $node_primary; + +# Create a test table and insert a row in primary. +$node_a->safe_psql('postgres', "CREATE TABLE tbl1 (d text)"); +$node_a->safe_psql('postgres', "INSERT INTO tbl1 VALUES ('in A')"); +primary_psql("CHECKPOINT"); + +# Set up node B and C, as cascaded standbys +# +# A (primary) <--- B (standby) <--- C (standby) +$node_a->backup('my_backup'); +$node_b = PostgreSQL::Test::Cluster->new('node_b'); +$node_b->init_from_backup($node_a, 'my_backup', has_streaming => 1); +$node_b->set_standby_mode(); +$node_b->start; + +$node_b->backup('my_backup'); +$node_c = PostgreSQL::Test::Cluster->new('node_c'); +$node_c->init_from_backup($node_b, 'my_backup', has_streaming => 1); +$node_c->set_standby_mode(); +$node_c->start; + +# Insert additional data on A, and wait for both standbys to catch up. +$node_a->safe_psql('postgres', + "INSERT INTO tbl1 values ('in A, before promotion')"); +$node_a->safe_psql('postgres', 'CHECKPOINT'); + +my $lsn = $node_a->lsn('write'); +$node_a->wait_for_catchup('node_b', 'write', $lsn); +$node_b->wait_for_catchup('node_c', 'write', $lsn); + +# Promote B +# +# A (primary) B (primary) <--- C (standby) + +$node_b->promote; + +# make sure end-of-recovery record is replicated to C before we continue +$node_b->wait_for_catchup('node_c'); + +# Insert a row in A. This causes A/B and C to have "diverged", so that it's +# no longer possible to just apply the standy's logs over primary directory +# - you need to rewind. +$node_a->safe_psql('postgres', + "INSERT INTO tbl1 VALUES ('rewind this')"); + +# +# All set up. We're ready to run pg_rewind. +# +my $node_a_pgdata = $node_a->data_dir; + +# Stop the old primary node and be ready to perform the rewind. +$node_a->stop('fast'); + +# Keep a temporary postgresql.conf or it would be overwritten during the rewind. +copy( + "$node_a_pgdata/postgresql.conf", + "$tmp_folder/node_a-postgresql.conf.tmp"); + +{ + # Temporarily unset PGAPPNAME so that the server doesn't + # inherit it. Otherwise this could affect libpqwalreceiver + # connections in confusing ways. + local %ENV = %ENV; + delete $ENV{PGAPPNAME}; + + # Do rewind using a remote connection as source, generating + # recovery configuration automatically. + command_ok( + [ + 'pg_rewind', "--debug", + "--source-server", $node_c->connstr('postgres'), + "--target-pgdata=$node_a_pgdata", "--no-sync", + ], + 'pg_rewind remote'); +} + +# Now move back postgresql.conf with old settings +move( + "$tmp_folder/node_a-postgresql.conf.tmp", + "$node_a_pgdata/postgresql.conf"); + +# Restart the node. +$node_a->start; + +# set RewindTest::node_primary to point to the rewound node, so that we can +# use check_query() +$node_primary = $node_a; + +# Run some checks to verify that C has been successfully rewound, +# and connected back to follow B. + +check_query( + 'SELECT * FROM tbl1', + qq(in A +in A, before promotion +), + 'table content after rewind'); + +# clean up +$node_a->teardown_node; +$node_b->teardown_node; +$node_c->teardown_node; + +done_testing(); +