Stabilize 035_standby_logical_decoding.pl.
authorAmit Kapila <akapila@postgresql.org>
Tue, 8 Apr 2025 04:08:02 +0000 (09:38 +0530)
committerAmit Kapila <akapila@postgresql.org>
Tue, 8 Apr 2025 04:08:02 +0000 (09:38 +0530)
Some tests try to invalidate logical slots on the standby server by
running VACUUM on the primary. The problem is that xl_running_xacts was
getting generated and replayed before the VACUUM command, leading to the
advancement of the active slot's catalog_xmin. Due to this, active slots
were not getting invalidated, leading to test failures.

We fix it by skipping the generation of xl_running_xacts for the required
tests with the help of injection points. As the required interface for
injection points was not present in back branches, we fixed the failing
tests in them by disallowing the slot to become active for the required
cases (where rows_removed conflict could be generated).

Author: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Reviewed-by: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Backpatch-through: 16, where it was introduced
Discussion: https://postgr.es/m/Z6oQXc8LmiTLfwLA@ip-10-97-1-34.eu-west-3.compute.internal

src/backend/storage/ipc/standby.c
src/test/recovery/t/035_standby_logical_decoding.pl

index 5acb4508f850f163b16c721d4e468bb9472a2d74..7fa8d9247e097b4943a76ec1e9c98623a7899d7d 100644 (file)
@@ -31,6 +31,7 @@
 #include "storage/sinvaladt.h"
 #include "storage/standby.h"
 #include "utils/hsearch.h"
+#include "utils/injection_point.h"
 #include "utils/ps_status.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
@@ -1287,6 +1288,17 @@ LogStandbySnapshot(void)
 
    Assert(XLogStandbyInfoActive());
 
+#ifdef USE_INJECTION_POINTS
+   if (IS_INJECTION_POINT_ATTACHED("skip-log-running-xacts"))
+   {
+       /*
+        * This record could move slot's xmin forward during decoding, leading
+        * to unpredictable results, so skip it when requested by the test.
+        */
+       return GetInsertRecPtr();
+   }
+#endif
+
    /*
     * Get details of any AccessExclusiveLocks being held at the moment.
     */
index ee066626af76cfdd9a9be0e9c1c2b1e69ae53208..b85a4a4eda6644b0dc4c5089b55a35651b3af228 100644 (file)
@@ -10,6 +10,11 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+if ($ENV{enable_injection_points} ne 'yes')
+{
+   plan skip_all => 'Injection points not supported by this build';
+}
+
 my ($stdout, $stderr, $cascading_stdout, $cascading_stderr, $handle);
 
 my $node_primary = PostgreSQL::Test::Cluster->new('primary');
@@ -241,16 +246,19 @@ sub check_for_invalidation
 # VACUUM command, $sql the sql to launch before triggering the vacuum and
 # $to_vac the relation to vacuum.
 #
-# Note that pg_current_snapshot() is used to get the horizon.  It does
-# not generate a Transaction/COMMIT WAL record, decreasing the risk of
-# seeing a xl_running_xacts that would advance an active replication slot's
-# catalog_xmin.  Advancing the active replication slot's catalog_xmin
-# would break some tests that expect the active slot to conflict with
-# the catalog xmin horizon.
+# Note that the injection_point avoids seeing a xl_running_xacts that could
+# advance an active replication slot's catalog_xmin. Advancing the active
+# replication slot's catalog_xmin would break some tests that expect the
+# active slot to conflict with the catalog xmin horizon.
 sub wait_until_vacuum_can_remove
 {
    my ($vac_option, $sql, $to_vac) = @_;
 
+   # Note that from this point the checkpointer and bgwriter will skip writing
+   # xl_running_xacts record.
+   $node_primary->safe_psql('testdb',
+       "SELECT injection_points_attach('skip-log-running-xacts', 'error');");
+
    # Get the current xid horizon,
    my $xid_horizon = $node_primary->safe_psql('testdb',
        qq[select pg_snapshot_xmin(pg_current_snapshot());]);
@@ -268,6 +276,12 @@ sub wait_until_vacuum_can_remove
    $node_primary->safe_psql(
        'testdb', qq[VACUUM $vac_option verbose $to_vac;
                                          INSERT INTO flush_wal DEFAULT VALUES;]);
+
+   $node_primary->wait_for_replay_catchup($node_standby);
+
+   # Resume generating the xl_running_xacts record
+   $node_primary->safe_psql('testdb',
+       "SELECT injection_points_detach('skip-log-running-xacts');");
 }
 
 ########################
@@ -285,6 +299,14 @@ autovacuum = off
 $node_primary->dump_info;
 $node_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 (!$node_primary->check_extension('injection_points'))
+{
+   plan skip_all => 'Extension injection_points not installed';
+}
+
 $node_primary->psql('postgres', q[CREATE DATABASE testdb]);
 
 $node_primary->safe_psql('testdb',
@@ -528,6 +550,9 @@ is($result, qq(10), 'check replicated inserts after subscription on standby');
 $node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub");
 $node_subscriber->stop;
 
+# Create the injection_points extension
+$node_primary->safe_psql('testdb', 'CREATE EXTENSION injection_points;');
+
 ##################################################
 # Recovery conflict: Invalidate conflicting slots, including in-use slots
 # Scenario 1: hot_standby_feedback off and vacuum FULL
@@ -557,8 +582,6 @@ 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');
 
@@ -665,8 +688,6 @@ 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');
 
@@ -699,8 +720,6 @@ 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');
@@ -733,8 +752,6 @@ wait_until_vacuum_can_remove(
                             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
 ok( !$node_standby->log_contains(
        "invalidating obsolete slot \"no_conflict_inactiveslot\"", $logstart),
@@ -782,6 +799,13 @@ $logstart = -s $node_standby->logfile;
 reactive_slots_change_hfs_and_wait_for_xmins('no_conflict_', 'pruning_', 0,
    0);
 
+# Injection_point avoids seeing a xl_running_xacts. This is required because if
+# it is generated between the last two updates, then the catalog_xmin of the
+# active slot could be updated, and hence, the conflict won't occur. See
+# comments atop wait_until_vacuum_can_remove.
+$node_primary->safe_psql('testdb',
+   "SELECT injection_points_attach('skip-log-running-xacts', 'error');");
+
 # This should trigger the conflict
 $node_primary->safe_psql('testdb',
    qq[CREATE TABLE prun(id integer, s char(2000)) WITH (fillfactor = 75, user_catalog_table = true);]
@@ -794,6 +818,10 @@ $node_primary->safe_psql('testdb', qq[UPDATE prun SET s = 'E';]);
 
 $node_primary->wait_for_replay_catchup($node_standby);
 
+# Resume generating the xl_running_xacts record
+$node_primary->safe_psql('testdb',
+   "SELECT injection_points_detach('skip-log-running-xacts');");
+
 # Check invalidation in the logfile and in pg_stat_database_conflicts
 check_for_invalidation('pruning_', $logstart, 'with on-access pruning');