diff options
| author | Amit Kapila | 2024-08-20 03:05:11 +0000 |
|---|---|---|
| committer | Amit Kapila | 2024-08-20 03:05:11 +0000 |
| commit | 9758174e2e5cd278cf37e0980da76b51890e0011 (patch) | |
| tree | 9ca019972be8f6b4b20acd98cdeb12a9475851e9 /src/test | |
| parent | adf97c1562380e02acd60dc859c289ed3a8352ee (diff) | |
Log the conflicts while applying changes in logical replication.
This patch provides the additional logging information in the following
conflict scenarios while applying changes:
insert_exists: Inserting a row that violates a NOT DEFERRABLE unique constraint.
update_differ: Updating a row that was previously modified by another origin.
update_exists: The updated row value violates a NOT DEFERRABLE unique constraint.
update_missing: The tuple to be updated is missing.
delete_differ: Deleting a row that was previously modified by another origin.
delete_missing: The tuple to be deleted is missing.
For insert_exists and update_exists conflicts, the log can include the origin
and commit timestamp details of the conflicting key with track_commit_timestamp
enabled.
update_differ and delete_differ conflicts can only be detected when
track_commit_timestamp is enabled on the subscriber.
We do not offer additional logging for exclusion constraint violations because
these constraints can specify rules that are more complex than simple equality
checks. Resolving such conflicts won't be straightforward. This area can be
further enhanced if required.
Author: Hou Zhijie
Reviewed-by: Shveta Malik, Amit Kapila, Nisha Moond, Hayato Kuroda, Dilip Kumar
Discussion: https://postgr.es/m/OS0PR01MB5716352552DFADB8E9AD1D8994C92@OS0PR01MB5716.jpnprd01.prod.outlook.com
Diffstat (limited to 'src/test')
| -rw-r--r-- | src/test/subscription/t/001_rep_changes.pl | 18 | ||||
| -rw-r--r-- | src/test/subscription/t/013_partition.pl | 53 | ||||
| -rw-r--r-- | src/test/subscription/t/029_on_error.pl | 11 | ||||
| -rw-r--r-- | src/test/subscription/t/030_origin.pl | 47 |
4 files changed, 90 insertions, 39 deletions
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl index 471e9819628..d377e7ae2b6 100644 --- a/src/test/subscription/t/001_rep_changes.pl +++ b/src/test/subscription/t/001_rep_changes.pl @@ -331,13 +331,8 @@ is( $result, qq(1|bar 2|baz), 'update works with REPLICA IDENTITY FULL and a primary key'); -# Check that subscriber handles cases where update/delete target tuple -# is missing. We have to look for the DEBUG1 log messages about that, -# so temporarily bump up the log verbosity. -$node_subscriber->append_conf('postgresql.conf', "log_min_messages = debug1"); -$node_subscriber->reload; - $node_subscriber->safe_psql('postgres', "DELETE FROM tab_full_pk"); +$node_subscriber->safe_psql('postgres', "DELETE FROM tab_full WHERE a = 25"); # Note that the current location of the log file is not grabbed immediately # after reloading the configuration, but after sending one SQL command to @@ -346,16 +341,21 @@ my $log_location = -s $node_subscriber->logfile; $node_publisher->safe_psql('postgres', "UPDATE tab_full_pk SET b = 'quux' WHERE a = 1"); +$node_publisher->safe_psql('postgres', + "UPDATE tab_full SET a = a + 1 WHERE a = 25"); $node_publisher->safe_psql('postgres', "DELETE FROM tab_full_pk WHERE a = 2"); $node_publisher->wait_for_catchup('tap_sub'); my $logfile = slurp_file($node_subscriber->logfile, $log_location); ok( $logfile =~ - qr/logical replication did not find row to be updated in replication target relation "tab_full_pk"/, + qr/conflict detected on relation "public.tab_full_pk": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated.*\n.*Remote tuple \(1, quux\); replica identity \(a\)=\(1\)/m, + 'update target row is missing'); +ok( $logfile =~ + qr/conflict detected on relation "public.tab_full": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated.*\n.*Remote tuple \(26\); replica identity full \(25\)/m, 'update target row is missing'); ok( $logfile =~ - qr/logical replication did not find row to be deleted in replication target relation "tab_full_pk"/, + qr/conflict detected on relation "public.tab_full_pk": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted.*\n.*Replica identity \(a\)=\(2\)/m, 'delete target row is missing'); $node_subscriber->append_conf('postgresql.conf', @@ -517,7 +517,7 @@ is($result, qq(1052|1|1002), $result = $node_subscriber->safe_psql('postgres', "SELECT count(*), min(a), max(a) FROM tab_full"); -is($result, qq(21|0|100), 'check replicated insert after alter publication'); +is($result, qq(19|0|100), 'check replicated insert after alter publication'); # check restart on rename $oldpid = $node_publisher->safe_psql('postgres', diff --git a/src/test/subscription/t/013_partition.pl b/src/test/subscription/t/013_partition.pl index 29580525a97..cf91542ed00 100644 --- a/src/test/subscription/t/013_partition.pl +++ b/src/test/subscription/t/013_partition.pl @@ -343,13 +343,6 @@ $result = $node_subscriber2->safe_psql('postgres', "SELECT a FROM tab1 ORDER BY 1"); is($result, qq(), 'truncate of tab1 replicated'); -# Check that subscriber handles cases where update/delete target tuple -# is missing. We have to look for the DEBUG1 log messages about that, -# so temporarily bump up the log verbosity. -$node_subscriber1->append_conf('postgresql.conf', - "log_min_messages = debug1"); -$node_subscriber1->reload; - $node_publisher->safe_psql('postgres', "INSERT INTO tab1 VALUES (1, 'foo'), (4, 'bar'), (10, 'baz')"); @@ -372,22 +365,18 @@ $node_publisher->wait_for_catchup('sub2'); my $logfile = slurp_file($node_subscriber1->logfile(), $log_location); ok( $logfile =~ - qr/logical replication did not find row to be updated in replication target relation's partition "tab1_2_2"/, + qr/conflict detected on relation "public.tab1_2_2": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated.*\n.*Remote tuple \(null, 4, quux\); replica identity \(a\)=\(4\)/, 'update target row is missing in tab1_2_2'); ok( $logfile =~ - qr/logical replication did not find row to be deleted in replication target relation "tab1_1"/, + qr/conflict detected on relation "public.tab1_1": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted.*\n.*Replica identity \(a\)=\(1\)/, 'delete target row is missing in tab1_1'); ok( $logfile =~ - qr/logical replication did not find row to be deleted in replication target relation "tab1_2_2"/, + qr/conflict detected on relation "public.tab1_2_2": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted.*\n.*Replica identity \(a\)=\(4\)/, 'delete target row is missing in tab1_2_2'); ok( $logfile =~ - qr/logical replication did not find row to be deleted in replication target relation "tab1_def"/, + qr/conflict detected on relation "public.tab1_def": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted.*\n.*Replica identity \(a\)=\(10\)/, 'delete target row is missing in tab1_def'); -$node_subscriber1->append_conf('postgresql.conf', - "log_min_messages = warning"); -$node_subscriber1->reload; - # Tests for replication using root table identity and schema # publisher @@ -773,13 +762,6 @@ pub_tab2|3|yyy pub_tab2|5|zzz xxx_c|6|aaa), 'inserts into tab2 replicated'); -# Check that subscriber handles cases where update/delete target tuple -# is missing. We have to look for the DEBUG1 log messages about that, -# so temporarily bump up the log verbosity. -$node_subscriber1->append_conf('postgresql.conf', - "log_min_messages = debug1"); -$node_subscriber1->reload; - $node_subscriber1->safe_psql('postgres', "DELETE FROM tab2"); # Note that the current location of the log file is not grabbed immediately @@ -796,15 +778,34 @@ $node_publisher->wait_for_catchup('sub2'); $logfile = slurp_file($node_subscriber1->logfile(), $log_location); ok( $logfile =~ - qr/logical replication did not find row to be updated in replication target relation's partition "tab2_1"/, + qr/conflict detected on relation "public.tab2_1": conflict=update_missing.*\n.*DETAIL:.* Could not find the row to be updated.*\n.*Remote tuple \(pub_tab2, quux, 5\); replica identity \(a\)=\(5\)/, 'update target row is missing in tab2_1'); ok( $logfile =~ - qr/logical replication did not find row to be deleted in replication target relation "tab2_1"/, + qr/conflict detected on relation "public.tab2_1": conflict=delete_missing.*\n.*DETAIL:.* Could not find the row to be deleted.*\n.*Replica identity \(a\)=\(1\)/, 'delete target row is missing in tab2_1'); +# Enable the track_commit_timestamp to detect the conflict when attempting +# to update a row that was previously modified by a different origin. +$node_subscriber1->append_conf('postgresql.conf', + 'track_commit_timestamp = on'); +$node_subscriber1->restart; + +$node_subscriber1->safe_psql('postgres', + "INSERT INTO tab2 VALUES (3, 'yyy')"); +$node_publisher->safe_psql('postgres', + "UPDATE tab2 SET b = 'quux' WHERE a = 3"); + +$node_publisher->wait_for_catchup('sub_viaroot'); + +$logfile = slurp_file($node_subscriber1->logfile(), $log_location); +ok( $logfile =~ + qr/conflict detected on relation "public.tab2_1": conflict=update_differ.*\n.*DETAIL:.* Updating the row that was modified locally in transaction [0-9]+ at .*\n.*Existing local tuple \(yyy, null, 3\); remote tuple \(pub_tab2, quux, 3\); replica identity \(a\)=\(3\)/, + 'updating a tuple that was modified by a different origin'); + +# The remaining tests no longer test conflict detection. $node_subscriber1->append_conf('postgresql.conf', - "log_min_messages = warning"); -$node_subscriber1->reload; + 'track_commit_timestamp = off'); +$node_subscriber1->restart; # Test that replication continues to work correctly after altering the # partition of a partitioned target table. diff --git a/src/test/subscription/t/029_on_error.pl b/src/test/subscription/t/029_on_error.pl index 0ab57a4b5b0..2f099a74f39 100644 --- a/src/test/subscription/t/029_on_error.pl +++ b/src/test/subscription/t/029_on_error.pl @@ -30,7 +30,7 @@ sub test_skip_lsn # ERROR with its CONTEXT when retrieving this information. my $contents = slurp_file($node_subscriber->logfile, $offset); $contents =~ - qr/duplicate key value violates unique constraint "tbl_pkey".*\n.*DETAIL:.*\n.*CONTEXT:.* for replication target relation "public.tbl" in transaction \d+, finished at ([[:xdigit:]]+\/[[:xdigit:]]+)/m + qr/conflict detected on relation "public.tbl".*\n.*DETAIL:.* Key already exists in unique index "tbl_pkey", modified by .*origin.* transaction \d+ at .*\n.*Key \(i\)=\(\d+\); existing local tuple .*; remote tuple .*\n.*CONTEXT:.* for replication target relation "public.tbl" in transaction \d+, finished at ([[:xdigit:]]+\/[[:xdigit:]]+)/m or die "could not get error-LSN"; my $lsn = $1; @@ -83,6 +83,7 @@ $node_subscriber->append_conf( 'postgresql.conf', qq[ max_prepared_transactions = 10 +track_commit_timestamp = on ]); $node_subscriber->start; @@ -93,6 +94,7 @@ $node_publisher->safe_psql( 'postgres', qq[ CREATE TABLE tbl (i INT, t BYTEA); +ALTER TABLE tbl REPLICA IDENTITY FULL; INSERT INTO tbl VALUES (1, NULL); ]); $node_subscriber->safe_psql( @@ -144,13 +146,14 @@ COMMIT; test_skip_lsn($node_publisher, $node_subscriber, "(2, NULL)", "2", "test skipping transaction"); -# Test for PREPARE and COMMIT PREPARED. Insert the same data to tbl and -# PREPARE the transaction, raising an error. Then skip the transaction. +# Test for PREPARE and COMMIT PREPARED. Update the data and PREPARE the +# transaction, raising an error on the subscriber due to violation of the +# unique constraint on tbl. Then skip the transaction. $node_publisher->safe_psql( 'postgres', qq[ BEGIN; -INSERT INTO tbl VALUES (1, NULL); +UPDATE tbl SET i = 2; PREPARE TRANSACTION 'gtx'; COMMIT PREPARED 'gtx'; ]); diff --git a/src/test/subscription/t/030_origin.pl b/src/test/subscription/t/030_origin.pl index 056561f0084..01536a13e73 100644 --- a/src/test/subscription/t/030_origin.pl +++ b/src/test/subscription/t/030_origin.pl @@ -27,9 +27,14 @@ my $stderr; my $node_A = PostgreSQL::Test::Cluster->new('node_A'); $node_A->init(allows_streaming => 'logical'); $node_A->start; + # node_B my $node_B = PostgreSQL::Test::Cluster->new('node_B'); $node_B->init(allows_streaming => 'logical'); + +# Enable the track_commit_timestamp to detect the conflict when attempting to +# update a row that was previously modified by a different origin. +$node_B->append_conf('postgresql.conf', 'track_commit_timestamp = on'); $node_B->start; # Create table on node_A @@ -140,6 +145,48 @@ is($result, qq(), ); ############################################################################### +# Check that the conflict can be detected when attempting to update or +# delete a row that was previously modified by a different source. +############################################################################### + +$node_B->safe_psql('postgres', "DELETE FROM tab;"); + +$node_A->safe_psql('postgres', "INSERT INTO tab VALUES (32);"); + +$node_A->wait_for_catchup($subname_BA); +$node_B->wait_for_catchup($subname_AB); + +$result = $node_B->safe_psql('postgres', "SELECT * FROM tab ORDER BY 1;"); +is($result, qq(32), 'The node_A data replicated to node_B'); + +# The update should update the row on node B that was inserted by node A. +$node_C->safe_psql('postgres', "UPDATE tab SET a = 33 WHERE a = 32;"); + +$node_B->wait_for_log( + qr/conflict detected on relation "public.tab": conflict=update_differ.*\n.*DETAIL:.* Updating the row that was modified by a different origin ".*" in transaction [0-9]+ at .*\n.*Existing local tuple \(32\); remote tuple \(33\); replica identity \(a\)=\(32\)/ +); + +$node_B->safe_psql('postgres', "DELETE FROM tab;"); +$node_A->safe_psql('postgres', "INSERT INTO tab VALUES (33);"); + +$node_A->wait_for_catchup($subname_BA); +$node_B->wait_for_catchup($subname_AB); + +$result = $node_B->safe_psql('postgres', "SELECT * FROM tab ORDER BY 1;"); +is($result, qq(33), 'The node_A data replicated to node_B'); + +# The delete should remove the row on node B that was inserted by node A. +$node_C->safe_psql('postgres', "DELETE FROM tab WHERE a = 33;"); + +$node_B->wait_for_log( + qr/conflict detected on relation "public.tab": conflict=delete_differ.*\n.*DETAIL:.* Deleting the row that was modified by a different origin ".*" in transaction [0-9]+ at .*\n.*Existing local tuple \(33\); replica identity \(a\)=\(33\)/ +); + +# The remaining tests no longer test conflict detection. +$node_B->append_conf('postgresql.conf', 'track_commit_timestamp = off'); +$node_B->restart; + +############################################################################### # Specifying origin = NONE indicates that the publisher should only replicate the # changes that are generated locally from node_B, but in this case since the # node_B is also subscribing data from node_A, node_B can have remotely |
