Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  023_twophase_stream.pl

  Sprache: Shell
 


# Copyright (c) 2021-2025, PostgreSQL Global Development Group

# Test logical replication of 2PC with streaming.
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;

# Check that the parallel apply worker has finished applying the streaming
# transaction.
sub check_parallel_log
{
 my ($node_subscriber, $offset, $is_parallel, $type) = @_;

 if ($is_parallel)
 {
  $node_subscriber->wait_for_log(
   qr/DEBUG: ( [A-Z0-9]+:)? finished processing the STREAM $type command/,
   $offset);
 }
}

# Common test steps for both the streaming=on and streaming=parallel cases.
sub test_streaming
{
 my ($node_publisher, $node_subscriber, $appname, $is_parallel) = @_;

 my $offset = 0;

 ###############################
 # Test 2PC PREPARE / COMMIT PREPARED.
 # 1. Data is streamed as a 2PC transaction.
 # 2. Then do commit prepared.
 #
 # Expect all data is replicated on subscriber side after the commit.
 ###############################

 # Check the subscriber log from now on.
 $offset = -s $node_subscriber->logfile;

 # check that 2PC gets replicated to subscriber
 # Insert, update and delete some rows.
 $node_publisher->safe_psql(
  'postgres', q{
  BEGIN;
  INSERT INTO test_tab SELECT i, sha256(i::text::bytea) FROM generate_series(35) s(i);
  UPDATE test_tab SET b = sha256(b) WHERE mod(a,2) = 0;
  DELETE FROM test_tab WHERE mod(a,3) = 0;
  PREPARE TRANSACTION 'test_prepared_tab';});

 $node_publisher->wait_for_catchup($appname);

 check_parallel_log($node_subscriber, $offset, $is_parallel, 'PREPARE');

 # check that transaction is in prepared state on subscriber
 my $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*) FROM pg_prepared_xacts;");
 is($result, qq(1), 'transaction is prepared on subscriber');

 # 2PC transaction gets committed
 $node_publisher->safe_psql('postgres',
  "COMMIT PREPARED 'test_prepared_tab';");

 $node_publisher->wait_for_catchup($appname);

 # check that transaction is committed on subscriber
 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*), count(c), count(d = 999) FROM test_tab");
 is($result, qq(4|4|4),
  'Rows inserted by 2PC have committed on subscriber, and extra columns contain local defaults'
 );
 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*) FROM pg_prepared_xacts;");
 is($result, qq(0), 'transaction is committed on subscriber');

 ###############################
 # Test 2PC PREPARE / ROLLBACK PREPARED.
 # 1. Table is deleted back to 2 rows which are replicated on subscriber.
 # 2. Data is streamed using 2PC.
 # 3. Do rollback prepared.
 #
 # Expect data rolls back leaving only the original 2 rows.
 ###############################

 # First, delete the data except for 2 rows (will be replicated)
 $node_publisher->safe_psql('postgres',
  "DELETE FROM test_tab WHERE a > 2;");

 # Check the subscriber log from now on.
 $offset = -s $node_subscriber->logfile;

 # Then insert, update and delete some rows.
 $node_publisher->safe_psql(
  'postgres', q{
  BEGIN;
  INSERT INTO test_tab SELECT i, sha256(i::text::bytea) FROM generate_series(35) s(i);
  UPDATE test_tab SET b = sha256(b) WHERE mod(a,2) = 0;
  DELETE FROM test_tab WHERE mod(a,3) = 0;
  PREPARE TRANSACTION 'test_prepared_tab';});

 $node_publisher->wait_for_catchup($appname);

 check_parallel_log($node_subscriber, $offset, $is_parallel, 'PREPARE');

 # check that transaction is in prepared state on subscriber
 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*) FROM pg_prepared_xacts;");
 is($result, qq(1), 'transaction is prepared on subscriber');

 # 2PC transaction gets aborted
 $node_publisher->safe_psql('postgres',
  "ROLLBACK PREPARED 'test_prepared_tab';");

 $node_publisher->wait_for_catchup($appname);

 # check that transaction is aborted on subscriber
 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*), count(c), count(d = 999) FROM test_tab");
 is($result, qq(2|2|2),
  'Rows inserted by 2PC are rolled back, leaving only the original 2 rows'
 );

 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*) FROM pg_prepared_xacts;");
 is($result, qq(0), 'transaction is aborted on subscriber');

 ###############################
 # Check that 2PC COMMIT PREPARED is decoded properly on crash restart.
 # 1. insert, update and delete some rows.
 # 2. Then server crashes before the 2PC transaction is committed.
 # 3. After servers are restarted the pending transaction is committed.
 #
 # Expect all data is replicated on subscriber side after the commit.
 # Note: both publisher and subscriber do crash/restart.
 ###############################

 # Check the subscriber log from now on.
 $offset = -s $node_subscriber->logfile;

 $node_publisher->safe_psql(
  'postgres', q{
  BEGIN;
  INSERT INTO test_tab SELECT i, sha256(i::text::bytea) FROM generate_series(35) s(i);
  UPDATE test_tab SET b = sha256(b) WHERE mod(a,2) = 0;
  DELETE FROM test_tab WHERE mod(a,3) = 0;
  PREPARE TRANSACTION 'test_prepared_tab';});

 $node_subscriber->stop('immediate');
 $node_publisher->stop('immediate');

 $node_publisher->start;
 $node_subscriber->start;

 # We don't try to check the log for parallel option here as the subscriber
 # may have stopped after finishing the prepare and before logging the
 # appropriate message.

 # commit post the restart
 $node_publisher->safe_psql('postgres',
  "COMMIT PREPARED 'test_prepared_tab';");
 $node_publisher->wait_for_catchup($appname);

 # check inserts are visible
 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*), count(c), count(d = 999) FROM test_tab");
 is($result, qq(4|4|4),
  'Rows inserted by 2PC have committed on subscriber, and extra columns contain local defaults'
 );

 ###############################
 # Do INSERT after the PREPARE but before ROLLBACK PREPARED.
 # 1. Table is deleted back to 2 rows which are replicated on subscriber.
 # 2. Data is streamed using 2PC.
 # 3. A single row INSERT is done which is after the PREPARE.
 # 4. Then do a ROLLBACK PREPARED.
 #
 # Expect the 2PC data rolls back leaving only 3 rows on the subscriber
 # (the original 2 + inserted 1).
 ###############################

 # First, delete the data except for 2 rows (will be replicated)
 $node_publisher->safe_psql('postgres',
  "DELETE FROM test_tab WHERE a > 2;");

 # Check the subscriber log from now on.
 $offset = -s $node_subscriber->logfile;

 # Then insert, update and delete some rows.
 $node_publisher->safe_psql(
  'postgres', q{
  BEGIN;
  INSERT INTO test_tab SELECT i, sha256(i::text::bytea) FROM generate_series(35) s(i);
  UPDATE test_tab SET b = sha256(b) WHERE mod(a,2) = 0;
  DELETE FROM test_tab WHERE mod(a,3) = 0;
  PREPARE TRANSACTION 'test_prepared_tab';});

 $node_publisher->wait_for_catchup($appname);

 check_parallel_log($node_subscriber, $offset, $is_parallel, 'PREPARE');

 # check that transaction is in prepared state on subscriber
 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*) FROM pg_prepared_xacts;");
 is($result, qq(1), 'transaction is prepared on subscriber');

 # Insert a different record (now we are outside of the 2PC transaction)
 # Note: the 2PC transaction still holds row locks so make sure this insert is for a separate primary key
 $node_publisher->safe_psql('postgres',
  "INSERT INTO test_tab VALUES (99999, 'foobar')");

 # 2PC transaction gets aborted
 $node_publisher->safe_psql('postgres',
  "ROLLBACK PREPARED 'test_prepared_tab';");

 $node_publisher->wait_for_catchup($appname);

 # check that transaction is aborted on subscriber,
 # but the extra INSERT outside of the 2PC still was replicated
 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*), count(c), count(d = 999) FROM test_tab");
 is($result, qq(3|3|3),
  'check the outside insert was copied to subscriber');

 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*) FROM pg_prepared_xacts;");
 is($result, qq(0), 'transaction is aborted on subscriber');

 ###############################
 # Do INSERT after the PREPARE but before COMMIT PREPARED.
 # 1. Table is deleted back to 2 rows which are replicated on subscriber.
 # 2. Data is streamed using 2PC.
 # 3. A single row INSERT is done which is after the PREPARE.
 # 4. Then do a COMMIT PREPARED.
 #
 # Expect 2PC data + the extra row are on the subscriber
 # (the 3334 + inserted 1 = 3335).
 ###############################

 # First, delete the data except for 2 rows (will be replicated)
 $node_publisher->safe_psql('postgres',
  "DELETE FROM test_tab WHERE a > 2;");

 # Check the subscriber log from now on.
 $offset = -s $node_subscriber->logfile;

 # Then insert, update and delete some rows.
 $node_publisher->safe_psql(
  'postgres', q{
  BEGIN;
  INSERT INTO test_tab SELECT i, sha256(i::text::bytea) FROM generate_series(35) s(i);
  UPDATE test_tab SET b = sha256(b) WHERE mod(a,2) = 0;
  DELETE FROM test_tab WHERE mod(a,3) = 0;
  PREPARE TRANSACTION 'test_prepared_tab';});

 $node_publisher->wait_for_catchup($appname);

 check_parallel_log($node_subscriber, $offset, $is_parallel, 'PREPARE');

 # check that transaction is in prepared state on subscriber
 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*) FROM pg_prepared_xacts;");
 is($result, qq(1), 'transaction is prepared on subscriber');

 # Insert a different record (now we are outside of the 2PC transaction)
 # Note: the 2PC transaction still holds row locks so make sure this insert is for a separate primary key
 $node_publisher->safe_psql('postgres',
  "INSERT INTO test_tab VALUES (99999, 'foobar')");

 # 2PC transaction gets committed
 $node_publisher->safe_psql('postgres',
  "COMMIT PREPARED 'test_prepared_tab';");

 $node_publisher->wait_for_catchup($appname);

 # check that transaction is committed on subscriber
 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*), count(c), count(d = 999) FROM test_tab");
 is($result, qq(5|5|5),
  'Rows inserted by 2PC (as well as outside insert) have committed on subscriber, and extra columns contain local defaults'
 );

 $result = $node_subscriber->safe_psql('postgres',
  "SELECT count(*) FROM pg_prepared_xacts;");
 is($result, qq(0), 'transaction is committed on subscriber');

 # Cleanup the test data
 $node_publisher->safe_psql('postgres',
  "DELETE FROM test_tab WHERE a > 2;");
 $node_publisher->wait_for_catchup($appname);
}

###############################
# Setup
###############################

# Initialize publisher node
my $node_publisher = PostgreSQL::Test::Cluster->new('publisher');
$node_publisher->init(allows_streaming => 'logical');
$node_publisher->append_conf(
 'postgresql.conf', qq(
max_prepared_transactions = 10
debug_logical_replication_streaming = immediate
));
$node_publisher->start;

# Create subscriber node
my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
$node_subscriber->init;
$node_subscriber->append_conf(
 'postgresql.conf', qq(
max_prepared_transactions = 10
));
$node_subscriber->start;

# Create some pre-existing content on publisher
$node_publisher->safe_psql('postgres',
 "CREATE TABLE test_tab (a int primary key, b bytea)");
$node_publisher->safe_psql('postgres',
 "INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')");
$node_publisher->safe_psql('postgres'"CREATE TABLE test_tab_2 (a int)");

# Setup structure on subscriber (columns a and b are compatible with same table name on publisher)
$node_subscriber->safe_psql('postgres',
 "CREATE TABLE test_tab (a int primary key, b bytea, c timestamptz DEFAULT now(), d bigint DEFAULT 999)"
);
$node_subscriber->safe_psql('postgres'"CREATE TABLE test_tab_2 (a int)");

# Setup logical replication (streaming = on)
my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
$node_publisher->safe_psql('postgres',
 "CREATE PUBLICATION tap_pub FOR TABLE test_tab, test_tab_2");

my $appname = 'tap_sub';

################################
# Test using streaming mode 'on'
################################
$node_subscriber->safe_psql(
 'postgres'"
 CREATE SUBSCRIPTION tap_sub
 CONNECTION '$publisher_connstr application_name=$appname'
 PUBLICATION tap_pub
 WITH (streaming = on, two_phase = on)");

# Wait for initial table sync to finish
$node_subscriber->wait_for_subscription_sync($node_publisher, $appname);

# Also wait for two-phase to be enabled
my $twophase_query =
  "SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');";
$node_subscriber->poll_query_until('postgres', $twophase_query)
  or die "Timed out while waiting for subscriber to enable twophase";

# Check initial data was copied to subscriber
my $result = $node_subscriber->safe_psql('postgres',
 "SELECT count(*), count(c), count(d = 999) FROM test_tab");
is($result, qq(2|2|2), 'check initial data was copied to subscriber');

test_streaming($node_publisher, $node_subscriber, $appname, 0);

######################################
# Test using streaming mode 'parallel'
######################################
my $oldpid = $node_publisher->safe_psql('postgres',
 "SELECT pid FROM pg_stat_replication WHERE application_name = '$appname' AND state = 'streaming';"
);

$node_subscriber->safe_psql('postgres',
 "ALTER SUBSCRIPTION tap_sub SET(streaming = parallel)");

$node_publisher->poll_query_until('postgres',
 "SELECT pid != $oldpid FROM pg_stat_replication WHERE application_name = '$appname' AND state = 'streaming';"
  )
  or die
  "Timed out while waiting for apply to restart after changing SUBSCRIPTION";

# We need to check DEBUG logs to ensure that the parallel apply worker has
# applied the transaction. So, bump up the log verbosity.
$node_subscriber->append_conf('postgresql.conf'"log_min_messages = debug1");
$node_subscriber->reload;

# Run a query to make sure that the reload has taken effect.
$node_subscriber->safe_psql('postgres', q{SELECT 1});

test_streaming($node_publisher, $node_subscriber, $appname, 1);

# Test serializing changes to files and notify the parallel apply worker to
# apply them at the end of the transaction.
$node_subscriber->append_conf('postgresql.conf',
 'debug_logical_replication_streaming = immediate');
# Reset the log_min_messages to default.
$node_subscriber->append_conf('postgresql.conf',
 "log_min_messages = warning");
$node_subscriber->reload;

# Run a query to make sure that the reload has taken effect.
$node_subscriber->safe_psql('postgres', q{SELECT 1});

my $offset = -s $node_subscriber->logfile;

$node_publisher->safe_psql(
 'postgres', q{
 BEGIN;
 INSERT INTO test_tab_2 values(1);
 PREPARE TRANSACTION 'xact';
 });

# Ensure that the changes are serialized.
$node_subscriber->wait_for_log(
 qr/LOG: ( [A-Z0-9]+:)? logical replication apply worker will serialize the remaining changes of remote transaction \d+ to a file/,
 $offset);

$node_publisher->wait_for_catchup($appname);

# Check that transaction is in prepared state on subscriber
$result = $node_subscriber->safe_psql('postgres',
 "SELECT count(*) FROM pg_prepared_xacts;");
is($result, qq(1), 'transaction is prepared on subscriber');

# Check that 2PC gets committed on subscriber
$node_publisher->safe_psql('postgres'"COMMIT PREPARED 'xact';");

$node_publisher->wait_for_catchup($appname);

# Check that transaction is committed on subscriber
$result =
  $node_subscriber->safe_psql('postgres'"SELECT count(*) FROM test_tab_2");
is($result, qq(1), 'transaction is committed on subscriber');

# Test the ability to re-apply a transaction when a parallel apply worker fails
# to prepare the transaction due to insufficient max_prepared_transactions
# setting.
$node_subscriber->append_conf(
 'postgresql.conf', qq(
max_prepared_transactions = 0
debug_logical_replication_streaming = buffered
));
$node_subscriber->restart;

$node_publisher->safe_psql(
 'postgres', q{
 BEGIN;
 INSERT INTO test_tab_2 values(2);
 PREPARE TRANSACTION 'xact';
 COMMIT PREPARED 'xact';
 });

$offset = -s $node_subscriber->logfile;

# Confirm the ERROR is reported because max_prepared_transactions is zero
$node_subscriber->wait_for_log(
 qr/ERROR: ( [A-Z0-9]+:)? prepared transactions are disabled/,
 $offset);

# Confirm that the parallel apply worker has encountered an error. The check
# focuses on the worker type as a keyword, since the error message content may
# differ based on whether the leader initially detected the parallel apply
# worker's failure or received a signal from it.
$node_subscriber->wait_for_log(
 qr/ERROR: .*logical replication parallel apply worker.*/,
 $offset);

# Set max_prepared_transactions to correct value to resume the replication
$node_subscriber->append_conf('postgresql.conf',
 qq(max_prepared_transactions = 10));
$node_subscriber->restart;

$node_publisher->wait_for_catchup($appname);

# Check that transaction is committed on subscriber
$result =
  $node_subscriber->safe_psql('postgres'"SELECT count(*) FROM test_tab_2");
is($result, qq(2), 'transaction is committed on subscriber after retrying');

###############################
# check all the cleanup
###############################

$node_subscriber->safe_psql('postgres'"DROP SUBSCRIPTION tap_sub");

$result = $node_subscriber->safe_psql('postgres',
 "SELECT count(*) FROM pg_subscription");
is($result, qq(0), 'check subscription was dropped on subscriber');

$result = $node_publisher->safe_psql('postgres',
 "SELECT count(*) FROM pg_replication_slots");
is($result, qq(0), 'check replication slot was dropped on publisher');

$result = $node_subscriber->safe_psql('postgres',
 "SELECT count(*) FROM pg_subscription_rel");
is($result, qq(0),
 'check subscription relation status was dropped on subscriber');

$result = $node_subscriber->safe_psql('postgres',
 "SELECT count(*) FROM pg_replication_origin");
is($result, qq(0), 'check replication origin was dropped on subscriber');

$node_subscriber->stop('fast');
$node_publisher->stop('fast');

done_testing();

Messung V0.5 in Prozent
C=82 H=70 G=76

¤ Dauer der Verarbeitung: 0.14 Sekunden  (vorverarbeitet am  2026-08-10) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=752002