From 2ad57fce46de1b053039c91248fd127888de09ad Mon Sep 17 00:00:00 2001
From: Nazir Bilal Yavuz <byavuz81@gmail.com>
Date: Tue, 15 Sep 2026 16:28:28 +0300
Subject: [PATCH v3 1/2] Avoid idle WAL waits in checksum tests

Use wait_for_replay_catchup() in checksum tests that explicitly wait for
the upstream insert LSN. That position can include unrelated WAL not yet
flushed and available for streaming on an idle primary. Completed
transactions, backups, checkpoints, and the explicit flushes of checksum
state changes already cover the WAL these waits need to replay.

In the 013_rewind test, also switch WAL on the source before starting
the target. This flushes through the minimum recovery point chosen by
pg_rewind, avoiding a startup delay while an unflushed full_page_writes
change waits for the background writer to log its next snapshot.

This reduces test time of 013_rewind test from ~45s to ~3s.

Discussion: https://postgr.es/m/CAN55FZ1Yak_xBqMaDQsD7atpBkGLEkF-DKXcs3nLHM1Uq4YRew%40mail.gmail.com
---
 .../test_checksums/t/003_standby_restarts.pl      | 12 ++++--------
 .../test_checksums/t/011_standby_straddle.pl      | 15 +++++----------
 src/test/modules/test_checksums/t/013_rewind.pl   | 12 +++++++++---
 .../t/021_rewind_divergent_transitions.pl         |  8 ++++----
 4 files changed, 22 insertions(+), 25 deletions(-)

diff --git a/src/test/modules/test_checksums/t/003_standby_restarts.pl b/src/test/modules/test_checksums/t/003_standby_restarts.pl
index b05ffd8643a..d70b90598da 100644
--- a/src/test/modules/test_checksums/t/003_standby_restarts.pl
+++ b/src/test/modules/test_checksums/t/003_standby_restarts.pl
@@ -42,8 +42,7 @@ $node_primary->safe_psql('postgres',
 	"CREATE TABLE t AS SELECT generate_series(1,10000) AS a;");
 
 # Wait for standby to catch up
-$node_primary->wait_for_catchup($node_standby, 'replay',
-	$node_primary->lsn('insert'));
+$node_primary->wait_for_replay_catchup($node_standby);
 
 # Check that checksums are turned off on all nodes
 test_checksum_state($node_primary, 'off');
@@ -140,8 +139,7 @@ $node_primary->safe_psql(
 	  SELECT g, repeat('x', 100) FROM generate_series(1, 1000) g;
 	CREATE INDEX unlogged_promo_payload_idx ON unlogged_promo (payload);
 ]);
-$node_primary->wait_for_catchup($node_standby, 'replay',
-	$node_primary->lsn('insert'));
+$node_primary->wait_for_replay_catchup($node_standby);
 
 # Get the relfilenode and database OID so we can inspect the filesystem
 my $unlogged_rfn = $node_primary->safe_psql('postgres',
@@ -161,8 +159,7 @@ wait_for_checksum_state($node_standby, 'on');
 
 # After standby replays, the unlogged main file must still not exist.
 # If the bug were present, FPI replay would materialize the full table.
-$node_primary->wait_for_catchup($node_standby, 'replay',
-	$node_primary->lsn('insert'));
+$node_primary->wait_for_replay_catchup($node_standby);
 ok( !-f "$standby_datadir/base/$db_oid/$unlogged_rfn",
 	'standby has no main fork for unlogged table after enable');
 
@@ -181,8 +178,7 @@ is($result, '1000',
 # Alter persistence to logged, and make sure we can read it on both the primary
 # and standby without any page verification errors in the logfiles.
 $node_primary->safe_psql('postgres', 'ALTER TABLE unlogged_tbl SET logged;');
-$node_primary->wait_for_catchup($node_standby, 'replay',
-	$node_primary->lsn('insert'));
+$node_primary->wait_for_replay_catchup($node_standby);
 
 $result =
   $node_primary->safe_psql('postgres', 'SELECT sum(a) FROM unlogged_tbl;');
diff --git a/src/test/modules/test_checksums/t/011_standby_straddle.pl b/src/test/modules/test_checksums/t/011_standby_straddle.pl
index e50fb65fcaf..4d3aa6f1249 100644
--- a/src/test/modules/test_checksums/t/011_standby_straddle.pl
+++ b/src/test/modules/test_checksums/t/011_standby_straddle.pl
@@ -92,8 +92,7 @@ bgwriter_lru_maxpages = 0
 ]);
 $node_standby->start;
 
-$node_primary->wait_for_catchup($node_standby, 'replay',
-	$node_primary->lsn('insert'));
+$node_primary->wait_for_replay_catchup($node_standby);
 test_checksum_state($node_primary, 'off');
 test_checksum_state($node_standby, 'off');
 
@@ -102,8 +101,7 @@ test_checksum_state($node_standby, 'off');
 # record reaches the standby until the enabling is released, so this remains
 # the standby's backup starting checkpoint throughout.
 $node_primary->safe_psql('postgres', 'CHECKPOINT;');
-$node_primary->wait_for_catchup($node_standby, 'replay',
-	$node_primary->lsn('insert'));
+$node_primary->wait_for_replay_catchup($node_standby);
 $node_standby->safe_psql('postgres', 'CHECKPOINT;');
 
 # Put everything the enabling writes into fresh WAL segments, so that the
@@ -143,8 +141,7 @@ $node_primary->safe_psql('postgres',
 
 # The standby has now replayed the state change: its pg_control says "on"
 # while the rewritten pages are only dirty in its shared buffers.
-$node_primary->wait_for_catchup($node_standby, 'replay',
-	$node_primary->lsn('insert'));
+$node_primary->wait_for_replay_catchup($node_standby);
 wait_for_checksum_state($node_standby, 'on');
 
 # Crash the standby, losing the dirty rewritten pages.
@@ -197,8 +194,7 @@ isnt($ret, 0, 'standby refuses connections while below the state change');
 $node_standby->enable_streaming($node_primary);
 $node_standby->reload;
 $node_standby->poll_query_until('postgres', 'SELECT true;');
-$node_primary->wait_for_catchup($node_standby, 'replay',
-	$node_primary->lsn('insert'));
+$node_primary->wait_for_replay_catchup($node_standby);
 
 # The rewritten pages are again only dirty in shared buffers, so the on-disk
 # pages still lack checksums.  A base backup must skip verification entirely
@@ -228,8 +224,7 @@ $node_primary->poll_query_until('postgres',
 
 # A restartpoint on the final checkpoint lets verification resume, and a
 # backup started from it must again pass.
-$node_primary->wait_for_catchup($node_standby, 'replay',
-	$node_primary->lsn('insert'));
+$node_primary->wait_for_replay_catchup($node_standby);
 $node_standby->safe_psql('postgres', 'CHECKPOINT;');
 
 $node_standby->command_checks_all(
diff --git a/src/test/modules/test_checksums/t/013_rewind.pl b/src/test/modules/test_checksums/t/013_rewind.pl
index a791e24317d..21715ace9f3 100644
--- a/src/test/modules/test_checksums/t/013_rewind.pl
+++ b/src/test/modules/test_checksums/t/013_rewind.pl
@@ -71,7 +71,8 @@ my $node_b = PostgreSQL::Test::Cluster->new('node_b');
 $node_b->init_from_backup($node_a, 'backup', has_streaming => 1);
 $node_b->start;
 
-$node_a->wait_for_catchup($node_b, 'replay', $node_a->lsn('insert'));
+# Backup completion has flushed the required WAL.
+$node_a->wait_for_replay_catchup($node_b);
 test_checksum_state($node_a, 'off');
 test_checksum_state($node_b, 'off');
 
@@ -83,7 +84,7 @@ $node_b->safe_psql('postgres',
 # in a background session; it will block on the injection point with
 # the checkpointer busy until released.
 $node_a->safe_psql('postgres', "CHECKPOINT;");
-$node_a->wait_for_catchup($node_b, 'replay', $node_a->lsn('insert'));
+$node_a->wait_for_replay_catchup($node_b);
 
 my $bg_psql = $node_b->background_psql('postgres', on_error_stop => 0);
 $bg_psql->query_until(
@@ -182,9 +183,14 @@ port = @{[$node_a->port]}
 primary_conninfo = '$connstr application_name=@{[$node_a->name]}'
 ]);
 $node_a->set_standby_mode;
+
+# Flush WAL through the minimum recovery point chosen by pg_rewind.  The
+# full_page_writes change can leave an unflushed record on the idle source,
+# delaying startup until the background writer logs its next snapshot.
+$node_b->safe_psql('postgres', 'SELECT pg_switch_wal();');
 $node_a->start;
 
-$node_b->wait_for_catchup($node_a, 'replay', $node_b->lsn('insert'));
+$node_b->wait_for_replay_catchup($node_a);
 test_checksum_state($node_a, 'on');
 
 is($node_a->safe_psql('postgres', "SELECT count(*) FROM t;"),
diff --git a/src/test/modules/test_checksums/t/021_rewind_divergent_transitions.pl b/src/test/modules/test_checksums/t/021_rewind_divergent_transitions.pl
index 6f95384f76a..e52fcc56ee5 100644
--- a/src/test/modules/test_checksums/t/021_rewind_divergent_transitions.pl
+++ b/src/test/modules/test_checksums/t/021_rewind_divergent_transitions.pl
@@ -87,7 +87,7 @@ $node_a->backup('backup');
 my $node_b = PostgreSQL::Test::Cluster->new('node_b');
 $node_b->init_from_backup($node_a, 'backup', has_streaming => 1);
 $node_b->start;
-$node_a->wait_for_catchup($node_b, 'replay', $node_a->lsn('insert'));
+$node_a->wait_for_replay_catchup($node_b);
 
 # Clean switchover to B; enable checksums online on it.
 $node_a->stop('fast');
@@ -137,7 +137,7 @@ primary_conninfo = '$connstr_b application_name=@{[$node_a->name]}'
 $node_a->set_standby_mode;
 $node_a->start;
 
-$node_b->wait_for_catchup($node_a, 'replay', $node_b->lsn('insert'));
+$node_b->wait_for_replay_catchup($node_a);
 test_checksum_state($node_a, 'on');
 
 is($node_a->safe_psql('postgres', "SELECT count(*) FROM t_div;"),
@@ -147,7 +147,7 @@ is($node_a->safe_psql('postgres', "SELECT count(*) FROM t_div;"),
 # checksums online so the next divergence point carries "off", and let
 # A replay the change.
 disable_data_checksums($node_b, wait => 'off');
-$node_b->wait_for_catchup($node_a, 'replay', $node_b->lsn('insert'));
+$node_b->wait_for_replay_catchup($node_a);
 test_checksum_state($node_a, 'off');
 
 # Clean switchover back to A; enable checksums online on it.
@@ -184,7 +184,7 @@ primary_conninfo = '$connstr_a application_name=@{[$node_b->name]}'
 $node_b->set_standby_mode;
 $node_b->start;
 
-$node_a->wait_for_catchup($node_b, 'replay', $node_a->lsn('insert'));
+$node_a->wait_for_replay_catchup($node_b);
 test_checksum_state($node_b, 'on');
 
 is($node_b->safe_psql('postgres', "SELECT count(*) FROM t;"),
-- 
2.47.3

