Hi, On Fri, Jan 12, 2024 at 07:01:55AM +0900, Michael Paquier wrote: > On Thu, Jan 11, 2024 at 11:00:01PM +0300, Alexander Lakhin wrote: > > Bertrand, I've relaunched tests in the same slowed down VM with both > > patches applied (but with no other modifications) and got a failure > > with pg_class, similar to what we had seen before: > > 9 # Failed test 'activeslot slot invalidation is logged with vacuum > > on pg_class' > > 9 # at t/035_standby_logical_decoding.pl line 230. > > > > Please look at the logs attached (I see there Standby/RUNNING_XACTS near > > 'invalidating obsolete replication slot "row_removal_inactiveslot"').
Thanks! For this one, the "good" news is that it looks like that we don’t see the "terminating" message not followed by an "obsolete" message (so the engine behaves correctly) anymore. There is simply nothing related to the row_removal_activeslot at all (the catalog_xmin advanced and there is no conflict). And I agree that this is due to the Standby/RUNNING_XACTS that is "advancing" the catalog_xmin of the active slot. > Standby/RUNNING_XACTS is exactly why 039_end_of_wal.pl uses wal_level > = minimal, because these lead to unpredictible records inserted, > impacting the reliability of the tests. We cannot do that here, > obviously. That may be a long shot, but could it be possible to tweak > the test with a retry logic, retrying things if such a standby > snapshot is found because we know that the invalidation is not going > to work anyway? I think it all depends what the xl_running_xacts does contain (means does it "advance" or not the catalog_xmin in our case). In our case it does advance it (should it occurs) due to the "select txid_current()" that is done in wait_until_vacuum_can_remove() in 035_standby_logical_decoding.pl. I suggest to make use of txid_current_snapshot() instead (that does not produce a Transaction/COMMIT wal record, as opposed to txid_current()). I think that it could be "enough" for our case here, and it's what v5 attached is now doing. Let's give v5 a try? (please apply v1-0001-Fix-race-condition-in-InvalidatePossiblyObsoleteS.patch too). Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
>From b1c316a85a2dd43a5ab33adbda1ee82020d84bb1 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot <bertranddrouvot...@gmail.com> Date: Thu, 11 Jan 2024 13:36:57 +0000 Subject: [PATCH v5] Fix 035_standby_logical_decoding.pl race conditions We want to ensure that vacuum was able to remove dead rows (aka no other transactions holding back global xmin) before testing for slots invalidation on the standby. Also, get rid of the active slot invalidation check during the on-access pruning check. This test is racy for active slots and active slots invalidations are well covered in other tests. --- .../t/035_standby_logical_decoding.pl | 114 +++++++++--------- 1 file changed, 59 insertions(+), 55 deletions(-) 100.0% src/test/recovery/t/ diff --git a/src/test/recovery/t/035_standby_logical_decoding.pl b/src/test/recovery/t/035_standby_logical_decoding.pl index 8bc39a5f03..dd4149c8bc 100644 --- a/src/test/recovery/t/035_standby_logical_decoding.pl +++ b/src/test/recovery/t/035_standby_logical_decoding.pl @@ -191,9 +191,9 @@ sub check_slots_conflict_reason # Drop the slots, re-create them, change hot_standby_feedback, # check xmin and catalog_xmin values, make slot active and reset stat. -sub reactive_slots_change_hfs_and_wait_for_xmins +sub recreate_slots_change_hfs_and_wait_for_xmins { - my ($previous_slot_prefix, $slot_prefix, $hsf, $invalidated) = @_; + my ($previous_slot_prefix, $slot_prefix, $hsf, $invalidated, $activate) = @_; # drop the logical slots drop_logical_slots($previous_slot_prefix); @@ -203,8 +203,11 @@ sub reactive_slots_change_hfs_and_wait_for_xmins change_hot_standby_feedback_and_wait_for_xmins($hsf, $invalidated); - $handle = - make_slot_active($node_standby, $slot_prefix, 1, \$stdout, \$stderr); + if ($activate) + { + $handle = + make_slot_active($node_standby, $slot_prefix, 1, \$stdout, \$stderr); + } # reset stat: easier to check for confl_active_logicalslot in pg_stat_database_conflicts $node_standby->psql('testdb', q[select pg_stat_reset();]); @@ -213,7 +216,7 @@ sub reactive_slots_change_hfs_and_wait_for_xmins # Check invalidation in the logfile and in pg_stat_database_conflicts sub check_for_invalidation { - my ($slot_prefix, $log_start, $test_name) = @_; + my ($slot_prefix, $log_start, $test_name, $activated) = @_; my $active_slot = $slot_prefix . 'activeslot'; my $inactive_slot = $slot_prefix . 'inactiveslot'; @@ -230,14 +233,33 @@ sub check_for_invalidation "activeslot slot invalidation is logged $test_name"); # Verify that pg_stat_database_conflicts.confl_active_logicalslot has been updated - ok( $node_standby->poll_query_until( - 'postgres', - "select (confl_active_logicalslot = 1) from pg_stat_database_conflicts where datname = 'testdb'", - 't'), - 'confl_active_logicalslot updated' - ) or die "Timed out waiting confl_active_logicalslot to be updated"; + if ($activated) + { + ok( $node_standby->poll_query_until( + 'postgres', + "select (confl_active_logicalslot = 1) from pg_stat_database_conflicts where datname = 'testdb'", + 't'), + 'confl_active_logicalslot updated' + ) or die "Timed out waiting confl_active_logicalslot to be updated"; + } } +# Launch $sql and wait for a new snapshot that has a newer horizon before +# doing the vacuum with $vac_option on $to_vac. +sub wait_until_vacuum_can_remove +{ + my ($vac_option, $sql, $to_vac) = @_; + + my $xid = $node_primary->safe_psql('testdb', qq[$sql + select txid_snapshot_xmax(txid_current_snapshot()) - 1;]); + + $node_primary->poll_query_until('testdb', + "SELECT (select txid_snapshot_xmin(txid_current_snapshot()) - $xid) > 0") + or die "new snapshot does not have a newer horizon"; + + $node_primary->safe_psql('testdb', qq[VACUUM $vac_option verbose $to_vac; + INSERT INTO flush_wal DEFAULT VALUES; -- see create table flush_wal;]); +} ######################## # Initialize primary node ######################## @@ -248,6 +270,7 @@ $node_primary->append_conf( wal_level = 'logical' max_replication_slots = 4 max_wal_senders = 4 +autovacuum = off }); $node_primary->dump_info; $node_primary->start; @@ -464,22 +487,17 @@ $node_subscriber->stop; # One way to produce recovery conflict is to create/drop a relation and # launch a vacuum full on pg_class with hot_standby_feedback turned off on # the standby. -reactive_slots_change_hfs_and_wait_for_xmins('behaves_ok_', 'vacuum_full_', - 0, 1); +recreate_slots_change_hfs_and_wait_for_xmins('behaves_ok_', 'vacuum_full_', + 0, 1, 1); # This should trigger the conflict -$node_primary->safe_psql( - 'testdb', qq[ - CREATE TABLE conflict_test(x integer, y text); - DROP TABLE conflict_test; - VACUUM full pg_class; - INSERT INTO flush_wal DEFAULT VALUES; -- see create table flush_wal -]); +wait_until_vacuum_can_remove('full', 'CREATE TABLE conflict_test(x integer, y text); + DROP TABLE conflict_test;', 'pg_class'); $node_primary->wait_for_replay_catchup($node_standby); # Check invalidation in the logfile and in pg_stat_database_conflicts -check_for_invalidation('vacuum_full_', 1, 'with vacuum FULL on pg_class'); +check_for_invalidation('vacuum_full_', 1, 'with vacuum FULL on pg_class', 1); # Verify conflict_reason is 'rows_removed' in pg_replication_slots check_slots_conflict_reason('vacuum_full_', 'rows_removed'); @@ -546,22 +564,17 @@ my $logstart = -s $node_standby->logfile; # One way to produce recovery conflict is to create/drop a relation and # launch a vacuum on pg_class with hot_standby_feedback turned off on the standby. -reactive_slots_change_hfs_and_wait_for_xmins('vacuum_full_', 'row_removal_', - 0, 1); +recreate_slots_change_hfs_and_wait_for_xmins('vacuum_full_', 'row_removal_', + 0, 1, 1); # This should trigger the conflict -$node_primary->safe_psql( - 'testdb', qq[ - CREATE TABLE conflict_test(x integer, y text); - DROP TABLE conflict_test; - VACUUM pg_class; - INSERT INTO flush_wal DEFAULT VALUES; -- see create table flush_wal -]); +wait_until_vacuum_can_remove('', 'CREATE TABLE conflict_test(x integer, y text); + DROP TABLE conflict_test;', 'pg_class'); $node_primary->wait_for_replay_catchup($node_standby); # Check invalidation in the logfile and in pg_stat_database_conflicts -check_for_invalidation('row_removal_', $logstart, 'with vacuum on pg_class'); +check_for_invalidation('row_removal_', $logstart, 'with vacuum on pg_class', 1); # Verify conflict_reason is 'rows_removed' in pg_replication_slots check_slots_conflict_reason('row_removal_', 'rows_removed'); @@ -584,23 +597,18 @@ $logstart = -s $node_standby->logfile; # One way to produce recovery conflict is to create/drop a relation and # launch a vacuum on pg_class with hot_standby_feedback turned off on the standby. -reactive_slots_change_hfs_and_wait_for_xmins('row_removal_', - 'shared_row_removal_', 0, 1); +recreate_slots_change_hfs_and_wait_for_xmins('row_removal_', + 'shared_row_removal_', 0, 1, 1); # Trigger the conflict -$node_primary->safe_psql( - 'testdb', qq[ - CREATE ROLE create_trash; - DROP ROLE create_trash; - VACUUM pg_authid; - INSERT INTO flush_wal DEFAULT VALUES; -- see create table flush_wal -]); +wait_until_vacuum_can_remove('', 'CREATE ROLE create_trash; + DROP ROLE create_trash;', 'pg_authid'); $node_primary->wait_for_replay_catchup($node_standby); # Check invalidation in the logfile and in pg_stat_database_conflicts check_for_invalidation('shared_row_removal_', $logstart, - 'with vacuum on pg_authid'); + 'with vacuum on pg_authid', 1); # Verify conflict_reason is 'rows_removed' in pg_replication_slots check_slots_conflict_reason('shared_row_removal_', 'rows_removed'); @@ -621,18 +629,14 @@ check_pg_recvlogical_stderr($handle, # get the position to search from in the standby logfile $logstart = -s $node_standby->logfile; -reactive_slots_change_hfs_and_wait_for_xmins('shared_row_removal_', - 'no_conflict_', 0, 1); +recreate_slots_change_hfs_and_wait_for_xmins('shared_row_removal_', + 'no_conflict_', 0, 1, 1); # This should not trigger a conflict -$node_primary->safe_psql( - 'testdb', qq[ - CREATE TABLE conflict_test(x integer, y text); - INSERT INTO conflict_test(x,y) SELECT s, s::text FROM generate_series(1,4) s; - UPDATE conflict_test set x=1, y=1; - VACUUM conflict_test; - INSERT INTO flush_wal DEFAULT VALUES; -- see create table flush_wal -]); +wait_until_vacuum_can_remove('', 'CREATE TABLE conflict_test(x integer, y text); + INSERT INTO conflict_test(x,y) SELECT s, s::text FROM generate_series(1,4) s; + UPDATE conflict_test set x=1, y=1;', 'conflict_test'); + $node_primary->wait_for_replay_catchup($node_standby); # message should not be issued @@ -679,8 +683,8 @@ $logstart = -s $node_standby->logfile; # One way to produce recovery conflict is to trigger an on-access pruning # on a relation marked as user_catalog_table. -reactive_slots_change_hfs_and_wait_for_xmins('no_conflict_', 'pruning_', 0, - 0); +recreate_slots_change_hfs_and_wait_for_xmins('no_conflict_', 'pruning_', 0, + 0, 0); # This should trigger the conflict $node_primary->safe_psql('testdb', @@ -695,7 +699,7 @@ $node_primary->safe_psql('testdb', qq[UPDATE prun SET s = 'E';]); $node_primary->wait_for_replay_catchup($node_standby); # Check invalidation in the logfile and in pg_stat_database_conflicts -check_for_invalidation('pruning_', $logstart, 'with on-access pruning'); +check_for_invalidation('pruning_', $logstart, 'with on-access pruning', 0); # Verify conflict_reason is 'rows_removed' in pg_replication_slots check_slots_conflict_reason('pruning_', 'rows_removed'); @@ -739,7 +743,7 @@ $node_primary->restart; $node_primary->wait_for_replay_catchup($node_standby); # Check invalidation in the logfile and in pg_stat_database_conflicts -check_for_invalidation('wal_level_', $logstart, 'due to wal_level'); +check_for_invalidation('wal_level_', $logstart, 'due to wal_level', 1); # Verify conflict_reason is 'wal_level_insufficient' in pg_replication_slots check_slots_conflict_reason('wal_level_', 'wal_level_insufficient'); -- 2.34.1