mirror of
https://github.com/postgres/postgres.git
synced 2025-12-19 17:02:53 +03:00
Detect and report update_deleted conflicts.
This enhancement builds upon the infrastructure introduced in commit
228c370868, which enables the preservation of deleted tuples and their
origin information on the subscriber. This capability is crucial for
handling concurrent transactions replicated from remote nodes.
The update introduces support for detecting update_deleted conflicts
during the application of update operations on the subscriber. When an
update operation fails to locate the target row-typically because it has
been concurrently deleted-we perform an additional table scan. This scan
uses the SnapshotAny mechanism and we do this additional scan only when
the retain_dead_tuples option is enabled for the relevant subscription.
The goal of this scan is to locate the most recently deleted tuple-matching
the old column values from the remote update-that has not yet been removed
by VACUUM and is still visible according to our slot (i.e., its deletion
is not older than conflict-detection-slot's xmin). If such a tuple is
found, the system reports an update_deleted conflict, including the origin
and transaction details responsible for the deletion.
This provides a groundwork for more robust and accurate conflict
resolution process, preventing unexpected behavior by correctly
identifying cases where a remote update clashes with a deletion from
another origin.
Author: Zhijie Hou <houzj.fnst@fujitsu.com>
Reviewed-by: shveta malik <shveta.malik@gmail.com>
Reviewed-by: Nisha Moond <nisha.moond412@gmail.com>
Reviewed-by: Dilip Kumar <dilipbalaut@gmail.com>
Reviewed-by: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Discussion: https://postgr.es/m/OS0PR01MB5716BE80DAEB0EE2A6A5D1F5949D2@OS0PR01MB5716.jpnprd01.prod.outlook.com
This commit is contained in:
@@ -2179,13 +2179,14 @@ pg_stat_subscription_stats| SELECT ss.subid,
|
||||
ss.confl_insert_exists,
|
||||
ss.confl_update_origin_differs,
|
||||
ss.confl_update_exists,
|
||||
ss.confl_update_deleted,
|
||||
ss.confl_update_missing,
|
||||
ss.confl_delete_origin_differs,
|
||||
ss.confl_delete_missing,
|
||||
ss.confl_multiple_unique_conflicts,
|
||||
ss.stats_reset
|
||||
FROM pg_subscription s,
|
||||
LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, confl_insert_exists, confl_update_origin_differs, confl_update_exists, confl_update_missing, confl_delete_origin_differs, confl_delete_missing, confl_multiple_unique_conflicts, stats_reset);
|
||||
LATERAL pg_stat_get_subscription_stats(s.oid) ss(subid, apply_error_count, sync_error_count, confl_insert_exists, confl_update_origin_differs, confl_update_exists, confl_update_deleted, confl_update_missing, confl_delete_origin_differs, confl_delete_missing, confl_multiple_unique_conflicts, stats_reset);
|
||||
pg_stat_sys_indexes| SELECT relid,
|
||||
indexrelid,
|
||||
schemaname,
|
||||
|
||||
@@ -150,7 +150,9 @@ pass('multiple_unique_conflicts detected on a leaf partition during insert');
|
||||
# Setup a bidirectional logical replication between node_A & node_B
|
||||
###############################################################################
|
||||
|
||||
# Initialize nodes.
|
||||
# Initialize nodes. Enable the track_commit_timestamp on both nodes to detect
|
||||
# the conflict when attempting to update a row that was previously modified by
|
||||
# a different origin.
|
||||
|
||||
# node_A. Increase the log_min_messages setting to DEBUG2 to debug test
|
||||
# failures. Disable autovacuum to avoid generating xid that could affect the
|
||||
@@ -158,7 +160,8 @@ pass('multiple_unique_conflicts detected on a leaf partition during insert');
|
||||
my $node_A = $node_publisher;
|
||||
$node_A->append_conf(
|
||||
'postgresql.conf',
|
||||
qq{autovacuum = off
|
||||
qq{track_commit_timestamp = on
|
||||
autovacuum = off
|
||||
log_min_messages = 'debug2'});
|
||||
$node_A->restart;
|
||||
|
||||
@@ -270,6 +273,8 @@ $node_A->psql('postgres',
|
||||
###############################################################################
|
||||
# Check that dead tuples on node A cannot be cleaned by VACUUM until the
|
||||
# concurrent transactions on Node B have been applied and flushed on Node A.
|
||||
# Also, check that an update_deleted conflict is detected when updating a row
|
||||
# that was deleted by a different origin.
|
||||
###############################################################################
|
||||
|
||||
# Insert a record
|
||||
@@ -288,6 +293,8 @@ $node_A->poll_query_until('postgres',
|
||||
"SELECT count(*) = 0 FROM pg_stat_activity WHERE backend_type = 'logical replication apply worker'"
|
||||
);
|
||||
|
||||
my $log_location = -s $node_B->logfile;
|
||||
|
||||
$node_B->safe_psql('postgres', "UPDATE tab SET b = 3 WHERE a = 1;");
|
||||
$node_A->safe_psql('postgres', "DELETE FROM tab WHERE a = 1;");
|
||||
|
||||
@@ -299,10 +306,30 @@ ok( $stderr =~
|
||||
qr/1 are dead but not yet removable/,
|
||||
'the deleted column is non-removable');
|
||||
|
||||
# Ensure the DELETE is replayed on Node B
|
||||
$node_A->wait_for_catchup($subname_BA);
|
||||
|
||||
# Check the conflict detected on Node B
|
||||
my $logfile = slurp_file($node_B->logfile(), $log_location);
|
||||
ok( $logfile =~
|
||||
qr/conflict detected on relation "public.tab": conflict=delete_origin_differs.*
|
||||
.*DETAIL:.* Deleting the row that was modified locally in transaction [0-9]+ at .*
|
||||
.*Existing local tuple \(1, 3\); replica identity \(a\)=\(1\)/,
|
||||
'delete target row was modified in tab');
|
||||
|
||||
$log_location = -s $node_A->logfile;
|
||||
|
||||
$node_A->safe_psql(
|
||||
'postgres', "ALTER SUBSCRIPTION $subname_AB ENABLE;");
|
||||
$node_B->wait_for_catchup($subname_AB);
|
||||
|
||||
$logfile = slurp_file($node_A->logfile(), $log_location);
|
||||
ok( $logfile =~
|
||||
qr/conflict detected on relation "public.tab": conflict=update_deleted.*
|
||||
.*DETAIL:.* The row to be updated was deleted locally in transaction [0-9]+ at .*
|
||||
.*Remote tuple \(1, 3\); replica identity \(a\)=\(1\)/,
|
||||
'update target row was deleted in tab');
|
||||
|
||||
# Remember the next transaction ID to be assigned
|
||||
my $next_xid = $node_A->safe_psql('postgres', "SELECT txid_current() + 1;");
|
||||
|
||||
@@ -324,6 +351,41 @@ ok( $stderr =~
|
||||
qr/1 removed, 1 remain, 0 are dead but not yet removable/,
|
||||
'the deleted column is removed');
|
||||
|
||||
###############################################################################
|
||||
# Ensure that the deleted tuple needed to detect an update_deleted conflict is
|
||||
# accessible via a sequential table scan.
|
||||
###############################################################################
|
||||
|
||||
# Drop the primary key from tab on node A and set REPLICA IDENTITY to FULL to
|
||||
# enforce sequential scanning of the table.
|
||||
$node_A->safe_psql('postgres', "ALTER TABLE tab REPLICA IDENTITY FULL");
|
||||
$node_B->safe_psql('postgres', "ALTER TABLE tab REPLICA IDENTITY FULL");
|
||||
$node_A->safe_psql('postgres', "ALTER TABLE tab DROP CONSTRAINT tab_pkey;");
|
||||
|
||||
# Disable the logical replication from node B to node A
|
||||
$node_A->safe_psql('postgres', "ALTER SUBSCRIPTION $subname_AB DISABLE");
|
||||
|
||||
# Wait for the apply worker to stop
|
||||
$node_A->poll_query_until('postgres',
|
||||
"SELECT count(*) = 0 FROM pg_stat_activity WHERE backend_type = 'logical replication apply worker'"
|
||||
);
|
||||
|
||||
$node_B->safe_psql('postgres', "UPDATE tab SET b = 4 WHERE a = 2;");
|
||||
$node_A->safe_psql('postgres', "DELETE FROM tab WHERE a = 2;");
|
||||
|
||||
$log_location = -s $node_A->logfile;
|
||||
|
||||
$node_A->safe_psql(
|
||||
'postgres', "ALTER SUBSCRIPTION $subname_AB ENABLE;");
|
||||
$node_B->wait_for_catchup($subname_AB);
|
||||
|
||||
$logfile = slurp_file($node_A->logfile(), $log_location);
|
||||
ok( $logfile =~
|
||||
qr/conflict detected on relation "public.tab": conflict=update_deleted.*
|
||||
.*DETAIL:.* The row to be updated was deleted locally in transaction [0-9]+ at .*
|
||||
.*Remote tuple \(2, 4\); replica identity full \(2, 2\)/,
|
||||
'update target row was deleted in tab');
|
||||
|
||||
###############################################################################
|
||||
# Check that the replication slot pg_conflict_detection is dropped after
|
||||
# removing all the subscriptions.
|
||||
|
||||
Reference in New Issue
Block a user