mirror of https://github.com/postgres/postgres
To add support for streaming transactions at prepare time into the built-in logical replication, we need to do the following things: * Modify the output plugin (pgoutput) to implement the new two-phase API callbacks, by leveraging the extended replication protocol. * Modify the replication apply worker, to properly handle two-phase transactions by replaying them on prepare. * Add a new SUBSCRIPTION option "two_phase" to allow users to enable two-phase transactions. We enable the two_phase once the initial data sync is over. We however must explicitly disable replication of two-phase transactions during replication slot creation, even if the plugin supports it. We don't need to replicate the changes accumulated during this phase, and moreover, we don't have a replication connection open so we don't know where to send the data anyway. The streaming option is not allowed with this new two_phase option. This can be done as a separate patch. We don't allow to toggle two_phase option of a subscription because it can lead to an inconsistent replica. For the same reason, we don't allow to refresh the publication once the two_phase is enabled for a subscription unless copy_data option is false. Author: Peter Smith, Ajin Cherian and Amit Kapila based on previous work by Nikhil Sontakke and Stas Kelvich Reviewed-by: Amit Kapila, Sawada Masahiko, Vignesh C, Dilip Kumar, Takamichi Osumi, Greg Nancarrow Tested-By: Haiying Tang Discussion: https://postgr.es/m/02DA5F5E-CECE-4D9C-8B4B-418077E2C010@postgrespro.ru Discussion: https://postgr.es/m/CAA4eK1+opiV4aFTmWWUF9h_32=HfPOW9vZASHarT0UA5oBrtGw@mail.gmail.compull/67/head
parent
6c9c283166
commit
a8fd13cab0
@ -0,0 +1,359 @@ |
||||
|
||||
# Copyright (c) 2021, PostgreSQL Global Development Group |
||||
|
||||
# logical replication of 2PC test |
||||
use strict; |
||||
use warnings; |
||||
use PostgresNode; |
||||
use TestLib; |
||||
use Test::More tests => 24; |
||||
|
||||
############################### |
||||
# Setup |
||||
############################### |
||||
|
||||
# Initialize publisher node |
||||
my $node_publisher = get_new_node('publisher'); |
||||
$node_publisher->init(allows_streaming => 'logical'); |
||||
$node_publisher->append_conf('postgresql.conf', |
||||
qq(max_prepared_transactions = 10)); |
||||
$node_publisher->start; |
||||
|
||||
# Create subscriber node |
||||
my $node_subscriber = get_new_node('subscriber'); |
||||
$node_subscriber->init(allows_streaming => 'logical'); |
||||
$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 tab_full (a int PRIMARY KEY)"); |
||||
$node_publisher->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_full SELECT generate_series(1,10); |
||||
PREPARE TRANSACTION 'some_initial_data'; |
||||
COMMIT PREPARED 'some_initial_data';"); |
||||
|
||||
# Setup structure on subscriber |
||||
$node_subscriber->safe_psql('postgres', |
||||
"CREATE TABLE tab_full (a int PRIMARY KEY)"); |
||||
|
||||
# Setup logical replication |
||||
my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres'; |
||||
$node_publisher->safe_psql('postgres', |
||||
"CREATE PUBLICATION tap_pub FOR TABLE tab_full"); |
||||
|
||||
my $appname = 'tap_sub'; |
||||
$node_subscriber->safe_psql('postgres', " |
||||
CREATE SUBSCRIPTION tap_sub |
||||
CONNECTION '$publisher_connstr application_name=$appname' |
||||
PUBLICATION tap_pub |
||||
WITH (two_phase = on)"); |
||||
|
||||
# Wait for subscriber to finish initialization |
||||
$node_publisher->wait_for_catchup($appname); |
||||
|
||||
# Also wait for initial table sync to finish |
||||
my $synced_query = |
||||
"SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');"; |
||||
$node_subscriber->poll_query_until('postgres', $synced_query) |
||||
or die "Timed out while waiting for subscriber to synchronize data"; |
||||
|
||||
# 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 that 2PC gets replicated to subscriber |
||||
# then COMMIT PREPARED |
||||
############################### |
||||
|
||||
$node_publisher->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_full VALUES (11); |
||||
PREPARE TRANSACTION 'test_prepared_tab_full';"); |
||||
|
||||
$node_publisher->wait_for_catchup($appname); |
||||
|
||||
# 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'); |
||||
|
||||
# check that 2PC gets committed on subscriber |
||||
$node_publisher->safe_psql('postgres', "COMMIT PREPARED 'test_prepared_tab_full';"); |
||||
|
||||
$node_publisher->wait_for_catchup($appname); |
||||
|
||||
# check that transaction is committed on subscriber |
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_full where a = 11;"); |
||||
is($result, qq(1), 'Row inserted via 2PC has committed on subscriber'); |
||||
|
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(0), 'transaction is committed on subscriber'); |
||||
|
||||
############################### |
||||
# check that 2PC gets replicated to subscriber |
||||
# then ROLLBACK PREPARED |
||||
############################### |
||||
|
||||
$node_publisher->safe_psql('postgres'," |
||||
BEGIN; |
||||
INSERT INTO tab_full VALUES (12); |
||||
PREPARE TRANSACTION 'test_prepared_tab_full';"); |
||||
|
||||
$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 aborted on subscriber |
||||
$node_publisher->safe_psql('postgres', "ROLLBACK PREPARED 'test_prepared_tab_full';"); |
||||
|
||||
$node_publisher->wait_for_catchup($appname); |
||||
|
||||
# check that transaction is aborted on subscriber |
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_full where a = 12;"); |
||||
is($result, qq(0), 'Row inserted via 2PC is not present on subscriber'); |
||||
|
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(0), 'transaction is aborted on subscriber'); |
||||
|
||||
############################### |
||||
# Check that ROLLBACK PREPARED is decoded properly on crash restart |
||||
# (publisher and subscriber crash) |
||||
############################### |
||||
|
||||
$node_publisher->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_full VALUES (12); |
||||
INSERT INTO tab_full VALUES (13); |
||||
PREPARE TRANSACTION 'test_prepared_tab';"); |
||||
|
||||
$node_subscriber->stop('immediate'); |
||||
$node_publisher->stop('immediate'); |
||||
|
||||
$node_publisher->start; |
||||
$node_subscriber->start; |
||||
|
||||
# rollback post the restart |
||||
$node_publisher->safe_psql('postgres', "ROLLBACK PREPARED 'test_prepared_tab';"); |
||||
$node_publisher->wait_for_catchup($appname); |
||||
|
||||
# check inserts are rolled back |
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_full where a IN (12,13);"); |
||||
is($result, qq(0), 'Rows rolled back are not on the subscriber'); |
||||
|
||||
############################### |
||||
# Check that COMMIT PREPARED is decoded properly on crash restart |
||||
# (publisher and subscriber crash) |
||||
############################### |
||||
|
||||
$node_publisher->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_full VALUES (12); |
||||
INSERT INTO tab_full VALUES (13); |
||||
PREPARE TRANSACTION 'test_prepared_tab';"); |
||||
|
||||
$node_subscriber->stop('immediate'); |
||||
$node_publisher->stop('immediate'); |
||||
|
||||
$node_publisher->start; |
||||
$node_subscriber->start; |
||||
|
||||
# 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(*) FROM tab_full where a IN (12,13);"); |
||||
is($result, qq(2), 'Rows inserted via 2PC are visible on the subscriber'); |
||||
|
||||
############################### |
||||
# Check that COMMIT PREPARED is decoded properly on crash restart |
||||
# (subscriber only crash) |
||||
############################### |
||||
|
||||
$node_publisher->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_full VALUES (14); |
||||
INSERT INTO tab_full VALUES (15); |
||||
PREPARE TRANSACTION 'test_prepared_tab';"); |
||||
|
||||
$node_subscriber->stop('immediate'); |
||||
$node_subscriber->start; |
||||
|
||||
# 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(*) FROM tab_full where a IN (14,15);"); |
||||
is($result, qq(2), 'Rows inserted via 2PC are visible on the subscriber'); |
||||
|
||||
############################### |
||||
# Check that COMMIT PREPARED is decoded properly on crash restart |
||||
# (publisher only crash) |
||||
############################### |
||||
|
||||
$node_publisher->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_full VALUES (16); |
||||
INSERT INTO tab_full VALUES (17); |
||||
PREPARE TRANSACTION 'test_prepared_tab';"); |
||||
|
||||
$node_publisher->stop('immediate'); |
||||
$node_publisher->start; |
||||
|
||||
# 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(*) FROM tab_full where a IN (16,17);"); |
||||
is($result, qq(2), 'Rows inserted via 2PC are visible on the subscriber'); |
||||
|
||||
############################### |
||||
# Test nested transaction with 2PC |
||||
############################### |
||||
|
||||
# check that 2PC gets replicated to subscriber |
||||
$node_publisher->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_full VALUES (21); |
||||
SAVEPOINT sp_inner; |
||||
INSERT INTO tab_full VALUES (22); |
||||
ROLLBACK TO SAVEPOINT sp_inner; |
||||
PREPARE TRANSACTION 'outer'; |
||||
"); |
||||
$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'); |
||||
|
||||
# COMMIT |
||||
$node_publisher->safe_psql('postgres', "COMMIT PREPARED 'outer';"); |
||||
|
||||
$node_publisher->wait_for_catchup($appname); |
||||
|
||||
# check the transaction state on subscriber |
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(0), 'transaction is ended on subscriber'); |
||||
|
||||
# check inserts are visible. 22 should be rolled back. 21 should be committed. |
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT a FROM tab_full where a IN (21,22);"); |
||||
is($result, qq(21), 'Rows committed are on the subscriber'); |
||||
|
||||
############################### |
||||
# Test using empty GID |
||||
############################### |
||||
|
||||
# check that 2PC gets replicated to subscriber |
||||
$node_publisher->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_full VALUES (51); |
||||
PREPARE TRANSACTION '';"); |
||||
$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'); |
||||
|
||||
# ROLLBACK |
||||
$node_publisher->safe_psql('postgres', "ROLLBACK PREPARED '';"); |
||||
|
||||
# check that 2PC gets aborted on subscriber |
||||
$node_publisher->wait_for_catchup($appname); |
||||
|
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(0), 'transaction is aborted on subscriber'); |
||||
|
||||
############################### |
||||
# copy_data=false and two_phase |
||||
############################### |
||||
|
||||
#create some test tables for copy tests |
||||
$node_publisher->safe_psql('postgres', "CREATE TABLE tab_copy (a int PRIMARY KEY)"); |
||||
$node_publisher->safe_psql('postgres', "INSERT INTO tab_copy SELECT generate_series(1,5);"); |
||||
$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_copy (a int PRIMARY KEY)"); |
||||
$node_subscriber->safe_psql('postgres', "INSERT INTO tab_copy VALUES (88);"); |
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_copy;"); |
||||
is($result, qq(1), 'initial data in subscriber table'); |
||||
|
||||
# Setup logical replication |
||||
$node_publisher->safe_psql('postgres', |
||||
"CREATE PUBLICATION tap_pub_copy FOR TABLE tab_copy;"); |
||||
|
||||
my $appname_copy = 'appname_copy'; |
||||
$node_subscriber->safe_psql('postgres', " |
||||
CREATE SUBSCRIPTION tap_sub_copy |
||||
CONNECTION '$publisher_connstr application_name=$appname_copy' |
||||
PUBLICATION tap_pub_copy |
||||
WITH (two_phase=on, copy_data=false);"); |
||||
|
||||
# Wait for subscriber to finish initialization |
||||
$node_publisher->wait_for_catchup($appname_copy); |
||||
|
||||
# Also wait for initial table sync to finish |
||||
$node_subscriber->poll_query_until('postgres', $synced_query) |
||||
or die "Timed out while waiting for subscriber to synchronize data"; |
||||
|
||||
# Also wait for two-phase to be enabled |
||||
$node_subscriber->poll_query_until('postgres', $twophase_query) |
||||
or die "Timed out while waiting for subscriber to enable twophase"; |
||||
|
||||
# Check that the initial table data was NOT replicated (because we said copy_data=false) |
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_copy;"); |
||||
is($result, qq(1), 'initial data in subscriber table'); |
||||
|
||||
# Now do a prepare on publisher and check that it IS replicated |
||||
$node_publisher->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_copy VALUES (99); |
||||
PREPARE TRANSACTION 'mygid';"); |
||||
|
||||
$node_publisher->wait_for_catchup($appname_copy); |
||||
|
||||
# Check that the transaction has been prepared on the subscriber, there will be 2 |
||||
# prepared transactions for the 2 subscriptions. |
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(2), 'transaction is prepared on subscriber'); |
||||
|
||||
# Now commit the insert and verify that it IS replicated |
||||
$node_publisher->safe_psql('postgres', "COMMIT PREPARED 'mygid';"); |
||||
|
||||
$result = $node_publisher->safe_psql('postgres', "SELECT count(*) FROM tab_copy;"); |
||||
is($result, qq(6), 'publisher inserted data'); |
||||
|
||||
$node_publisher->wait_for_catchup($appname_copy); |
||||
|
||||
$result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_copy;"); |
||||
is($result, qq(2), 'replicated data in subscriber table'); |
||||
|
||||
$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_copy;"); |
||||
$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_copy;"); |
||||
|
||||
############################### |
||||
# 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'); |
@ -0,0 +1,235 @@ |
||||
|
||||
# Copyright (c) 2021, PostgreSQL Global Development Group |
||||
|
||||
# Test cascading logical replication of 2PC. |
||||
use strict; |
||||
use warnings; |
||||
use PostgresNode; |
||||
use TestLib; |
||||
use Test::More tests => 27; |
||||
|
||||
############################### |
||||
# Setup a cascade of pub/sub nodes. |
||||
# node_A -> node_B -> node_C |
||||
############################### |
||||
|
||||
# Initialize nodes |
||||
# node_A |
||||
my $node_A = get_new_node('node_A'); |
||||
$node_A->init(allows_streaming => 'logical'); |
||||
$node_A->append_conf('postgresql.conf', |
||||
qq(max_prepared_transactions = 10)); |
||||
$node_A->start; |
||||
# node_B |
||||
my $node_B = get_new_node('node_B'); |
||||
$node_B->init(allows_streaming => 'logical'); |
||||
$node_B->append_conf('postgresql.conf', |
||||
qq(max_prepared_transactions = 10)); |
||||
$node_B->start; |
||||
# node_C |
||||
my $node_C = get_new_node('node_C'); |
||||
$node_C->init(allows_streaming => 'logical'); |
||||
$node_C->append_conf('postgresql.conf', |
||||
qq(max_prepared_transactions = 10)); |
||||
$node_C->start; |
||||
|
||||
# Create some pre-existing content on node_A |
||||
$node_A->safe_psql('postgres', |
||||
"CREATE TABLE tab_full (a int PRIMARY KEY)"); |
||||
$node_A->safe_psql('postgres', " |
||||
INSERT INTO tab_full SELECT generate_series(1,10);"); |
||||
|
||||
# Create the same tables on node_B amd node_C |
||||
$node_B->safe_psql('postgres', |
||||
"CREATE TABLE tab_full (a int PRIMARY KEY)"); |
||||
$node_C->safe_psql('postgres', |
||||
"CREATE TABLE tab_full (a int PRIMARY KEY)"); |
||||
|
||||
# Setup logical replication |
||||
|
||||
# node_A (pub) -> node_B (sub) |
||||
my $node_A_connstr = $node_A->connstr . ' dbname=postgres'; |
||||
$node_A->safe_psql('postgres', |
||||
"CREATE PUBLICATION tap_pub_A FOR TABLE tab_full"); |
||||
my $appname_B = 'tap_sub_B'; |
||||
$node_B->safe_psql('postgres', " |
||||
CREATE SUBSCRIPTION tap_sub_B |
||||
CONNECTION '$node_A_connstr application_name=$appname_B' |
||||
PUBLICATION tap_pub_A |
||||
WITH (two_phase = on)"); |
||||
|
||||
# node_B (pub) -> node_C (sub) |
||||
my $node_B_connstr = $node_B->connstr . ' dbname=postgres'; |
||||
$node_B->safe_psql('postgres', |
||||
"CREATE PUBLICATION tap_pub_B FOR TABLE tab_full"); |
||||
my $appname_C = 'tap_sub_C'; |
||||
$node_C->safe_psql('postgres', " |
||||
CREATE SUBSCRIPTION tap_sub_C |
||||
CONNECTION '$node_B_connstr application_name=$appname_C' |
||||
PUBLICATION tap_pub_B |
||||
WITH (two_phase = on)"); |
||||
|
||||
# Wait for subscribers to finish initialization |
||||
$node_A->wait_for_catchup($appname_B); |
||||
$node_B->wait_for_catchup($appname_C); |
||||
|
||||
# Also wait for two-phase to be enabled |
||||
my $twophase_query = "SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');"; |
||||
$node_B->poll_query_until('postgres', $twophase_query) |
||||
or die "Timed out while waiting for subscriber to enable twophase"; |
||||
$node_C->poll_query_until('postgres', $twophase_query) |
||||
or die "Timed out while waiting for subscriber to enable twophase"; |
||||
|
||||
is(1,1, "Cascade setup is complete"); |
||||
|
||||
my $result; |
||||
|
||||
############################### |
||||
# check that 2PC gets replicated to subscriber(s) |
||||
# then COMMIT PREPARED |
||||
############################### |
||||
|
||||
# 2PC PREPARE |
||||
$node_A->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_full VALUES (11); |
||||
PREPARE TRANSACTION 'test_prepared_tab_full';"); |
||||
|
||||
$node_A->wait_for_catchup($appname_B); |
||||
$node_B->wait_for_catchup($appname_C); |
||||
|
||||
# check the transaction state is prepared on subscriber(s) |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(1), 'transaction is prepared on subscriber B'); |
||||
$result = $node_C->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(1), 'transaction is prepared on subscriber C'); |
||||
|
||||
# 2PC COMMIT |
||||
$node_A->safe_psql('postgres', "COMMIT PREPARED 'test_prepared_tab_full';"); |
||||
|
||||
$node_A->wait_for_catchup($appname_B); |
||||
$node_B->wait_for_catchup($appname_C); |
||||
|
||||
# check that transaction was committed on subscriber(s) |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM tab_full where a = 11;"); |
||||
is($result, qq(1), 'Row inserted via 2PC has committed on subscriber B'); |
||||
$result = $node_C->safe_psql('postgres', "SELECT count(*) FROM tab_full where a = 11;"); |
||||
is($result, qq(1), 'Row inserted via 2PC has committed on subscriber C'); |
||||
|
||||
# check the transaction state is ended on subscriber(s) |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(0), 'transaction is committed on subscriber B'); |
||||
$result = $node_C->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(0), 'transaction is committed on subscriber C'); |
||||
|
||||
############################### |
||||
# check that 2PC gets replicated to subscriber(s) |
||||
# then ROLLBACK PREPARED |
||||
############################### |
||||
|
||||
# 2PC PREPARE |
||||
$node_A->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_full VALUES (12); |
||||
PREPARE TRANSACTION 'test_prepared_tab_full';"); |
||||
|
||||
$node_A->wait_for_catchup($appname_B); |
||||
$node_B->wait_for_catchup($appname_C); |
||||
|
||||
# check the transaction state is prepared on subscriber(s) |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(1), 'transaction is prepared on subscriber B'); |
||||
$result = $node_C->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(1), 'transaction is prepared on subscriber C'); |
||||
|
||||
# 2PC ROLLBACK |
||||
$node_A->safe_psql('postgres', "ROLLBACK PREPARED 'test_prepared_tab_full';"); |
||||
|
||||
$node_A->wait_for_catchup($appname_B); |
||||
$node_B->wait_for_catchup($appname_C); |
||||
|
||||
# check that transaction is aborted on subscriber(s) |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM tab_full where a = 12;"); |
||||
is($result, qq(0), 'Row inserted via 2PC is not present on subscriber B'); |
||||
$result = $node_C->safe_psql('postgres', "SELECT count(*) FROM tab_full where a = 12;"); |
||||
is($result, qq(0), 'Row inserted via 2PC is not present on subscriber C'); |
||||
|
||||
# check the transaction state is ended on subscriber(s) |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(0), 'transaction is ended on subscriber B'); |
||||
$result = $node_C->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(0), 'transaction is ended on subscriber C'); |
||||
|
||||
############################### |
||||
# Test nested transactions with 2PC |
||||
############################### |
||||
|
||||
# 2PC PREPARE with a nested ROLLBACK TO SAVEPOINT |
||||
$node_A->safe_psql('postgres', " |
||||
BEGIN; |
||||
INSERT INTO tab_full VALUES (21); |
||||
SAVEPOINT sp_inner; |
||||
INSERT INTO tab_full VALUES (22); |
||||
ROLLBACK TO SAVEPOINT sp_inner; |
||||
PREPARE TRANSACTION 'outer'; |
||||
"); |
||||
|
||||
$node_A->wait_for_catchup($appname_B); |
||||
$node_B->wait_for_catchup($appname_C); |
||||
|
||||
# check the transaction state prepared on subscriber(s) |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(1), 'transaction is prepared on subscriber B'); |
||||
$result = $node_C->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(1), 'transaction is prepared on subscriber C'); |
||||
|
||||
# 2PC COMMIT |
||||
$node_A->safe_psql('postgres', "COMMIT PREPARED 'outer';"); |
||||
|
||||
$node_A->wait_for_catchup($appname_B); |
||||
$node_B->wait_for_catchup($appname_C); |
||||
|
||||
# check the transaction state is ended on subscriber |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(0), 'transaction is ended on subscriber B'); |
||||
$result = $node_C->safe_psql('postgres', "SELECT count(*) FROM pg_prepared_xacts;"); |
||||
is($result, qq(0), 'transaction is ended on subscriber C'); |
||||
|
||||
# check inserts are visible at subscriber(s). |
||||
# 22 should be rolled back. |
||||
# 21 should be committed. |
||||
$result = $node_B->safe_psql('postgres', "SELECT a FROM tab_full where a IN (21,22);"); |
||||
is($result, qq(21), 'Rows committed are present on subscriber B'); |
||||
$result = $node_C->safe_psql('postgres', "SELECT a FROM tab_full where a IN (21,22);"); |
||||
is($result, qq(21), 'Rows committed are present on subscriber C'); |
||||
|
||||
############################### |
||||
# check all the cleanup |
||||
############################### |
||||
|
||||
# cleanup the node_B => node_C pub/sub |
||||
$node_C->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_C"); |
||||
$result = $node_C->safe_psql('postgres', "SELECT count(*) FROM pg_subscription"); |
||||
is($result, qq(0), 'check subscription was dropped on subscriber node C'); |
||||
$result = $node_C->safe_psql('postgres', "SELECT count(*) FROM pg_subscription_rel"); |
||||
is($result, qq(0), 'check subscription relation status was dropped on subscriber node C'); |
||||
$result = $node_C->safe_psql('postgres', "SELECT count(*) FROM pg_replication_origin"); |
||||
is($result, qq(0), 'check replication origin was dropped on subscriber node C'); |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM pg_replication_slots"); |
||||
is($result, qq(0), 'check replication slot was dropped on publisher node B'); |
||||
|
||||
# cleanup the node_A => node_B pub/sub |
||||
$node_B->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_B"); |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM pg_subscription"); |
||||
is($result, qq(0), 'check subscription was dropped on subscriber node B'); |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM pg_subscription_rel"); |
||||
is($result, qq(0), 'check subscription relation status was dropped on subscriber node B'); |
||||
$result = $node_B->safe_psql('postgres', "SELECT count(*) FROM pg_replication_origin"); |
||||
is($result, qq(0), 'check replication origin was dropped on subscriber node B'); |
||||
$result = $node_A->safe_psql('postgres', "SELECT count(*) FROM pg_replication_slots"); |
||||
is($result, qq(0), 'check replication slot was dropped on publisher node A'); |
||||
|
||||
# shutdown |
||||
$node_C->stop('fast'); |
||||
$node_B->stop('fast'); |
||||
$node_A->stop('fast'); |
Loading…
Reference in new issue