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