mirror of
https://github.com/postgres/postgres.git
synced 2025-12-21 05:21:08 +03:00
Detect and Log multiple_unique_conflicts type conflict.
Introduce a new conflict type, multiple_unique_conflicts, to handle cases where an incoming row during logical replication violates multiple UNIQUE constraints. Previously, the apply worker detected and reported only the first encountered key conflict (insert_exists/update_exists), causing repeated failures as each constraint violation needs to be handled one by one making the process slow and error-prone. With this patch, the apply worker checks all unique constraints upfront once the first key conflict is detected and reports multiple_unique_conflicts if multiple violations exist. This allows users to resolve all conflicts at once by deleting all conflicting tuples rather than dealing with them individually or skipping the transaction. In the future, this will also allow us to specify different resolution handlers for such a conflict type. Add the stats for this conflict type in pg_stat_subscription_stats. Author: Nisha Moond <nisha.moond412@gmail.com> Author: Zhijie Hou <houzj.fnst@fujitsu.com> Reviewed-by: Amit Kapila <amit.kapila16@gmail.com> Reviewed-by: Peter Smith <smithpb2250@gmail.com> Reviewed-by: Dilip Kumar <dilipbalaut@gmail.com> Discussion: https://postgr.es/m/CABdArM7FW-_dnthGkg2s0fy1HhUB8C3ELA0gZX1kkbs1ZZoV3Q@mail.gmail.com
This commit is contained in:
@@ -2157,9 +2157,10 @@ pg_stat_subscription_stats| SELECT ss.subid,
|
||||
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, 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_missing, confl_delete_origin_differs, confl_delete_missing, confl_multiple_unique_conflicts, stats_reset);
|
||||
pg_stat_sys_indexes| SELECT relid,
|
||||
indexrelid,
|
||||
schemaname,
|
||||
|
||||
@@ -41,6 +41,7 @@ tests += {
|
||||
't/032_subscribe_use_index.pl',
|
||||
't/033_run_as_table_owner.pl',
|
||||
't/034_temporal.pl',
|
||||
't/035_conflicts.pl',
|
||||
't/100_bugs.pl',
|
||||
],
|
||||
},
|
||||
|
||||
113
src/test/subscription/t/035_conflicts.pl
Normal file
113
src/test/subscription/t/035_conflicts.pl
Normal file
@@ -0,0 +1,113 @@
|
||||
# Copyright (c) 2025, PostgreSQL Global Development Group
|
||||
|
||||
# Test the conflict detection of conflict type 'multiple_unique_conflicts'.
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use PostgreSQL::Test::Cluster;
|
||||
use PostgreSQL::Test::Utils;
|
||||
use Test::More;
|
||||
|
||||
###############################
|
||||
# Setup
|
||||
###############################
|
||||
|
||||
# Create a publisher node
|
||||
my $node_publisher = PostgreSQL::Test::Cluster->new('publisher');
|
||||
$node_publisher->init(allows_streaming => 'logical');
|
||||
$node_publisher->start;
|
||||
|
||||
# Create a subscriber node
|
||||
my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
|
||||
$node_subscriber->init;
|
||||
$node_subscriber->start;
|
||||
|
||||
# Create a table on publisher
|
||||
$node_publisher->safe_psql('postgres',
|
||||
"CREATE TABLE conf_tab (a int PRIMARY KEY, b int UNIQUE, c int UNIQUE);");
|
||||
|
||||
# Create same table on subscriber
|
||||
$node_subscriber->safe_psql('postgres',
|
||||
"CREATE TABLE conf_tab (a int PRIMARY key, b int UNIQUE, c int UNIQUE);");
|
||||
|
||||
# Setup logical replication
|
||||
my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
|
||||
$node_publisher->safe_psql('postgres',
|
||||
"CREATE PUBLICATION pub_tab FOR TABLE conf_tab");
|
||||
|
||||
# Create the subscription
|
||||
my $appname = 'sub_tab';
|
||||
$node_subscriber->safe_psql(
|
||||
'postgres',
|
||||
"CREATE SUBSCRIPTION sub_tab
|
||||
CONNECTION '$publisher_connstr application_name=$appname'
|
||||
PUBLICATION pub_tab;");
|
||||
|
||||
# Wait for initial table sync to finish
|
||||
$node_subscriber->wait_for_subscription_sync($node_publisher, $appname);
|
||||
|
||||
##################################################
|
||||
# INSERT data on Pub and Sub
|
||||
##################################################
|
||||
|
||||
# Insert data in the publisher table
|
||||
$node_publisher->safe_psql('postgres',
|
||||
"INSERT INTO conf_tab VALUES (1,1,1);");
|
||||
|
||||
# Insert data in the subscriber table
|
||||
$node_subscriber->safe_psql('postgres',
|
||||
"INSERT INTO conf_tab VALUES (2,2,2), (3,3,3), (4,4,4);");
|
||||
|
||||
##################################################
|
||||
# Test multiple_unique_conflicts due to INSERT
|
||||
##################################################
|
||||
my $log_offset = -s $node_subscriber->logfile;
|
||||
|
||||
$node_publisher->safe_psql('postgres',
|
||||
"INSERT INTO conf_tab VALUES (2,3,4);");
|
||||
|
||||
# Confirm that this causes an error on the subscriber
|
||||
$node_subscriber->wait_for_log(
|
||||
qr/conflict detected on relation \"public.conf_tab\": conflict=multiple_unique_conflicts.*
|
||||
.*Key already exists in unique index \"conf_tab_pkey\".*
|
||||
.*Key \(a\)=\(2\); existing local tuple \(2, 2, 2\); remote tuple \(2, 3, 4\).*
|
||||
.*Key already exists in unique index \"conf_tab_b_key\".*
|
||||
.*Key \(b\)=\(3\); existing local tuple \(3, 3, 3\); remote tuple \(2, 3, 4\).*
|
||||
.*Key already exists in unique index \"conf_tab_c_key\".*
|
||||
.*Key \(c\)=\(4\); existing local tuple \(4, 4, 4\); remote tuple \(2, 3, 4\)./,
|
||||
$log_offset);
|
||||
|
||||
pass('multiple_unique_conflicts detected during update');
|
||||
|
||||
# Truncate table to get rid of the error
|
||||
$node_subscriber->safe_psql('postgres', "TRUNCATE conf_tab;");
|
||||
|
||||
##################################################
|
||||
# Test multiple_unique_conflicts due to UPDATE
|
||||
##################################################
|
||||
$log_offset = -s $node_subscriber->logfile;
|
||||
|
||||
# Insert data in the publisher table
|
||||
$node_publisher->safe_psql('postgres',
|
||||
"INSERT INTO conf_tab VALUES (5,5,5);");
|
||||
|
||||
# Insert data in the subscriber table
|
||||
$node_subscriber->safe_psql('postgres',
|
||||
"INSERT INTO conf_tab VALUES (6,6,6), (7,7,7), (8,8,8);");
|
||||
|
||||
$node_publisher->safe_psql('postgres',
|
||||
"UPDATE conf_tab set a=6, b=7, c=8 where a=5;");
|
||||
|
||||
# Confirm that this causes an error on the subscriber
|
||||
$node_subscriber->wait_for_log(
|
||||
qr/conflict detected on relation \"public.conf_tab\": conflict=multiple_unique_conflicts.*
|
||||
.*Key already exists in unique index \"conf_tab_pkey\".*
|
||||
.*Key \(a\)=\(6\); existing local tuple \(6, 6, 6\); remote tuple \(6, 7, 8\).*
|
||||
.*Key already exists in unique index \"conf_tab_b_key\".*
|
||||
.*Key \(b\)=\(7\); existing local tuple \(7, 7, 7\); remote tuple \(6, 7, 8\).*
|
||||
.*Key already exists in unique index \"conf_tab_c_key\".*
|
||||
.*Key \(c\)=\(8\); existing local tuple \(8, 8, 8\); remote tuple \(6, 7, 8\)./,
|
||||
$log_offset);
|
||||
|
||||
pass('multiple_unique_conflicts detected during insert');
|
||||
|
||||
done_testing();
|
||||
Reference in New Issue
Block a user