From bd9df395c639b478df5e62888a01d29eace3e221 Mon Sep 17 00:00:00 2001 From: Shlok Kyal Date: Tue, 18 Nov 2025 15:42:10 +0530 Subject: [PATCH v8 2/2] Add test for new stats for slot sync skip --- src/test/recovery/meson.build | 1 + .../recovery/t/050_slotsync_skip_stats.pl | 201 ++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 src/test/recovery/t/050_slotsync_skip_stats.pl diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build index 523a5cd5b52..17551cf114a 100644 --- a/src/test/recovery/meson.build +++ b/src/test/recovery/meson.build @@ -58,6 +58,7 @@ tests += { 't/047_checkpoint_physical_slot.pl', 't/048_vacuum_horizon_floor.pl', 't/049_wait_for_lsn.pl', + 't/050_slotsync_skip_stats.pl', ], }, } diff --git a/src/test/recovery/t/050_slotsync_skip_stats.pl b/src/test/recovery/t/050_slotsync_skip_stats.pl new file mode 100644 index 00000000000..9313b9cb45b --- /dev/null +++ b/src/test/recovery/t/050_slotsync_skip_stats.pl @@ -0,0 +1,201 @@ +# Copyright (c) 2025, PostgreSQL Global Development Group + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# Skip all tests if injection points are not supported in this build +if ($ENV{enable_injection_points} ne 'yes') +{ + plan skip_all => 'Injection points not supported by this build'; +} + +# Initialize the primary cluster +my $primary = PostgreSQL::Test::Cluster->new('primary'); +$primary->init(allows_streaming => 'logical'); +$primary->append_conf( + 'postgresql.conf', qq{ +autovacuum = off +}); +$primary->start; + +# Check if the extension injection_points is available, as it may be +# possible that this script is run with installcheck, where the module +# would not be installed by default. +if (!$primary->check_extension('injection_points')) +{ + plan skip_all => 'Extension injection_points not installed'; +} + +# Load the injection_points extension +$primary->safe_psql('postgres', q(CREATE EXTENSION injection_points)); + +# Take a backup of the primary for standby initialization +my $backup_name = 'backup'; +$primary->backup($backup_name); + +# Initialize standby from primary backup +my $standby = PostgreSQL::Test::Cluster->new('standby'); +$standby->init_from_backup($primary, $backup_name, has_streaming => 1); + +my $connstr = $primary->connstr; +$standby->append_conf( + 'postgresql.conf', qq( +hot_standby_feedback = on +primary_slot_name = 'sb1_slot' +primary_conninfo = '$connstr dbname=postgres' +)); + +# Create a physical replication slot on primary for standby +$primary->safe_psql('postgres', + q{SELECT pg_create_physical_replication_slot('sb1_slot');}); + +$standby->start; + +# Create a logical replication slot on primary for testing +$primary->safe_psql('postgres', + "SELECT pg_create_logical_replication_slot('slot_sync', 'test_decoding', false, false, true)" +); + +# Wait for standby to catch up +$primary->wait_for_replay_catchup($standby); + +# Initial sync of replication slots +$standby->safe_psql('postgres', "SELECT pg_sync_replication_slots();"); + +# Verify that initially there is no skip reason +my $result = $standby->safe_psql( + 'postgres', + "SELECT slotsync_skip_reason FROM pg_replication_slots + WHERE slot_name = 'slot_sync' AND synced" +); +is($result, 'none', "slot sync reason is none"); + +# Update pg_hba.conf and restart the primary to reject streaming replication +# connections. WAL records won't be replicated to the standby until the +# configuration is restored. +unlink($primary->data_dir . '/pg_hba.conf'); +$primary->append_conf( + 'pg_hba.conf', qq{ +local all all trust +host all all 127.0.0.1/32 trust +host all all ::1/128 trust +}); +$primary->restart; + +# Advance the failover slot so that confirmed flush LSN of remote slot become +# ahead of standby's flushed LSN +$primary->safe_psql( + 'postgres', qq( + CREATE TABLE t1(a int); + INSERT INTO t1 VALUES(1); + SELECT pg_replication_slot_advance('slot_sync', pg_current_wal_lsn()); +)); + +my ($stdout, $stderr); +# Attempt to sync replication slots while standby is behind +($result, $stdout, $stderr) = + $standby->psql('postgres', "SELECT pg_sync_replication_slots();"); + +# Verify pg_sync_replication_slots is failing +ok( $stderr =~ + qr/skipping slot synchronization because the received slot sync.*is ahead of the standby position/, + 'pg_sync_replication_slots failed as expected'); + +# Check skip reason and count when standby is behind +$result = $standby->safe_psql( + 'postgres', + "SELECT slotsync_skip_reason FROM pg_replication_slots + WHERE slot_name = 'slot_sync' AND synced AND NOT temporary" +); +is($result, 'wal_not_flushed', "slot sync skip when standby is behind"); + +$result = $standby->safe_psql('postgres', + "SELECT slotsync_skip_count FROM pg_stat_replication_slots WHERE slot_name = 'slot_sync'" +); +is($result, '1', "check slot sync skip count"); + +# Repeat sync to ensure skip count increments +($result, $stdout, $stderr) = + $standby->psql('postgres', "SELECT pg_sync_replication_slots();"); + +$result = $standby->safe_psql('postgres', + "SELECT slotsync_skip_count FROM pg_stat_replication_slots WHERE slot_name = 'slot_sync'" +); +is($result, '2', "check slot sync skip count"); + +# Restore streaming replication connection +$primary->append_conf( + 'pg_hba.conf', qq{ +local replication all trust +host replication all 127.0.0.1/32 trust +host replication all ::1/128 trust +}); +$primary->restart; + +# Wait for standby to catch up +$primary->wait_for_replay_catchup($standby); + +# Check that skip reason is reset after successful sync +$standby->safe_psql('postgres', "SELECT pg_sync_replication_slots();"); +$result = $standby->safe_psql( + 'postgres', + "SELECT slotsync_skip_reason FROM pg_replication_slots + WHERE slot_name = 'slot_sync' AND synced AND NOT temporary" +); +is($result, 'none', "slotsync_skip_reason is reset after successful sync"); + +# Cleanup: drop the logical slot and ensure standby catches up +$primary->safe_psql('postgres', + "SELECT pg_drop_replication_slot('slot_sync')"); +$primary->wait_for_replay_catchup($standby); + +$standby->safe_psql('postgres', "SELECT pg_sync_replication_slots();"); + +# Test for case when slot sync is skipped when the remote slot is +# behind the local slot. +$primary->safe_psql('postgres', + "SELECT pg_create_logical_replication_slot('slot_sync', 'test_decoding', false, false, true)" +); + +# Attach injection point to simulate wait +my $standby_psql = $standby->background_psql('postgres'); +$standby_psql->query_safe( + q(select injection_points_attach('slot-sync-skip','wait'))); + +# Initiate sync of failover slots +$standby_psql->query_until( + qr/slot_sync/, + q( +\echo slot_sync +select pg_sync_replication_slots(); +)); + +# Wait for backend to reach injection point +$standby->wait_for_event('client backend', 'slot-sync-skip'); + +# Logical slot is temporary and sync will skip because remote is behind +$result = $standby->safe_psql( + 'postgres', + "SELECT slotsync_skip_reason FROM pg_replication_slots + WHERE slot_name = 'slot_sync' AND synced AND temporary" +); +is($result, 'remote_behind', "slot sync skip as remote is behind"); + +$result = $standby->safe_psql('postgres', + "SELECT slotsync_skip_count FROM pg_stat_replication_slots WHERE slot_name = 'slot_sync'" +); +is($result, '1', "check slot sync skip count"); + +# Detach injection point +$standby->safe_psql( + 'postgres', q{ + SELECT injection_points_detach('slot-sync-skip'); + SELECT injection_points_wakeup('slot-sync-skip'); +}); + +$standby_psql->quit; + +done_testing(); -- 2.34.1