mirror of
https://github.com/postgres/postgres.git
synced 2025-11-26 23:43:30 +03:00
Add sequence synchronization for logical replication.
This patch introduces sequence synchronization. Sequences that are synced
will have 2 states:
- INIT (needs [re]synchronizing)
- READY (is already synchronized)
A new sequencesync worker is launched as needed to synchronize sequences.
A single sequencesync worker is responsible for synchronizing all
sequences. It begins by retrieving the list of sequences that are flagged
for synchronization, i.e., those in the INIT state. These sequences are
then processed in batches, allowing multiple entries to be synchronized
within a single transaction. The worker fetches the current sequence
values and page LSNs from the remote publisher, updates the corresponding
sequences on the local subscriber, and finally marks each sequence as
READY upon successful synchronization.
Sequence synchronization occurs in 3 places:
1) CREATE SUBSCRIPTION
- The command syntax remains unchanged.
- The subscriber retrieves sequences associated with publications.
- Published sequences are added to pg_subscription_rel with INIT
state.
- Initiate the sequencesync worker to synchronize all sequences.
2) ALTER SUBSCRIPTION ... REFRESH PUBLICATION
- The command syntax remains unchanged.
- Dropped published sequences are removed from pg_subscription_rel.
- Newly published sequences are added to pg_subscription_rel with INIT
state.
- Initiate the sequencesync worker to synchronize only newly added
sequences.
3) ALTER SUBSCRIPTION ... REFRESH SEQUENCES
- A new command introduced for PG19 by f0b3573c3a.
- All sequences in pg_subscription_rel are reset to INIT state.
- Initiate the sequencesync worker to synchronize all sequences.
- Unlike "ALTER SUBSCRIPTION ... REFRESH PUBLICATION" command,
addition and removal of missing sequences will not be done in this
case.
Author: Vignesh C <vignesh21@gmail.com>
Reviewed-by: shveta malik <shveta.malik@gmail.com>
Reviewed-by: Hou Zhijie <houzj.fnst@fujitsu.com>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Reviewed-by: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Reviewed-by: Dilip Kumar <dilipbalaut@gmail.com>
Reviewed-by: Peter Smith <smithpb2250@gmail.com>
Reviewed-by: Nisha Moond <nisha.moond412@gmail.com>
Reviewed-by: Shlok Kyal <shlok.kyal.oss@gmail.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/CAA4eK1LC+KJiAkSrpE_NwvNdidw9F2os7GERUeSxSKv71gXysQ@mail.gmail.com
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
|
||||
# Copyright (c) 2025, PostgreSQL Global Development Group
|
||||
|
||||
# This tests that sequences are registered to be synced to the subscriber
|
||||
# This tests that sequences are synced correctly to the subscriber
|
||||
use strict;
|
||||
use warnings;
|
||||
use PostgreSQL::Test::Cluster;
|
||||
@@ -10,9 +10,6 @@ use Test::More;
|
||||
|
||||
# Initialize publisher node
|
||||
my $node_publisher = PostgreSQL::Test::Cluster->new('publisher');
|
||||
|
||||
# Avoid checkpoint during the test, otherwise, extra values will be fetched for
|
||||
# the sequences which will cause the test to fail randomly.
|
||||
$node_publisher->init(allows_streaming => 'logical');
|
||||
$node_publisher->start;
|
||||
|
||||
@@ -28,7 +25,14 @@ my $ddl = qq(
|
||||
);
|
||||
$node_publisher->safe_psql('postgres', $ddl);
|
||||
|
||||
# Setup the same structure on the subscriber
|
||||
# Setup the same structure on the subscriber, plus some extra sequences that
|
||||
# we'll create on the publisher later
|
||||
$ddl = qq(
|
||||
CREATE TABLE regress_seq_test (v BIGINT);
|
||||
CREATE SEQUENCE regress_s1;
|
||||
CREATE SEQUENCE regress_s2;
|
||||
CREATE SEQUENCE regress_s3;
|
||||
);
|
||||
$node_subscriber->safe_psql('postgres', $ddl);
|
||||
|
||||
# Insert initial test data
|
||||
@@ -46,10 +50,165 @@ $node_subscriber->safe_psql('postgres',
|
||||
"CREATE SUBSCRIPTION regress_seq_sub CONNECTION '$publisher_connstr' PUBLICATION regress_seq_pub"
|
||||
);
|
||||
|
||||
# Confirm sequences can be listed in pg_subscription_rel
|
||||
my $result = $node_subscriber->safe_psql('postgres',
|
||||
"SELECT relname, srsubstate FROM pg_class, pg_subscription_rel WHERE oid = srrelid"
|
||||
# Wait for initial sync to finish
|
||||
my $synced_query =
|
||||
"SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
|
||||
$node_subscriber->poll_query_until('postgres', $synced_query)
|
||||
or die "Timed out while waiting for subscriber to synchronize data";
|
||||
|
||||
# Check the initial data on subscriber
|
||||
my $result = $node_subscriber->safe_psql(
|
||||
'postgres', qq(
|
||||
SELECT last_value, is_called FROM regress_s1;
|
||||
));
|
||||
is($result, '100|t', 'initial test data replicated');
|
||||
|
||||
##########
|
||||
## ALTER SUBSCRIPTION ... REFRESH PUBLICATION should cause sync of new
|
||||
# sequences of the publisher, but changes to existing sequences should
|
||||
# not be synced.
|
||||
##########
|
||||
|
||||
# Create a new sequence 'regress_s2', and update existing sequence 'regress_s1'
|
||||
$node_publisher->safe_psql(
|
||||
'postgres', qq(
|
||||
CREATE SEQUENCE regress_s2;
|
||||
INSERT INTO regress_seq_test SELECT nextval('regress_s2') FROM generate_series(1,100);
|
||||
|
||||
-- Existing sequence
|
||||
INSERT INTO regress_seq_test SELECT nextval('regress_s1') FROM generate_series(1,100);
|
||||
));
|
||||
|
||||
# Do ALTER SUBSCRIPTION ... REFRESH PUBLICATION
|
||||
$result = $node_subscriber->safe_psql(
|
||||
'postgres', qq(
|
||||
ALTER SUBSCRIPTION regress_seq_sub REFRESH PUBLICATION;
|
||||
));
|
||||
$node_subscriber->poll_query_until('postgres', $synced_query)
|
||||
or die "Timed out while waiting for subscriber to synchronize data";
|
||||
|
||||
$result = $node_publisher->safe_psql(
|
||||
'postgres', qq(
|
||||
SELECT last_value, is_called FROM regress_s1;
|
||||
));
|
||||
is($result, '200|t', 'Check sequence value in the publisher');
|
||||
|
||||
# Check - existing sequence ('regress_s1') is not synced
|
||||
$result = $node_subscriber->safe_psql(
|
||||
'postgres', qq(
|
||||
SELECT last_value, is_called FROM regress_s1;
|
||||
));
|
||||
is($result, '100|t', 'REFRESH PUBLICATION will not sync existing sequence');
|
||||
|
||||
# Check - newly published sequence ('regress_s2') is synced
|
||||
$result = $node_subscriber->safe_psql(
|
||||
'postgres', qq(
|
||||
SELECT last_value, is_called FROM regress_s2;
|
||||
));
|
||||
is($result, '100|t',
|
||||
'REFRESH PUBLICATION will sync newly published sequence');
|
||||
|
||||
##########
|
||||
# Test: REFRESH SEQUENCES and REFRESH PUBLICATION (copy_data = false)
|
||||
#
|
||||
# 1. ALTER SUBSCRIPTION ... REFRESH SEQUENCES should re-synchronize all
|
||||
# existing sequences, but not synchronize newly added ones.
|
||||
# 2. ALTER SUBSCRIPTION ... REFRESH PUBLICATION with (copy_data = false) should
|
||||
# also not update sequence values for newly added sequences.
|
||||
##########
|
||||
|
||||
# Create a new sequence 'regress_s3', and update the existing sequence
|
||||
# 'regress_s2'.
|
||||
$node_publisher->safe_psql(
|
||||
'postgres', qq(
|
||||
CREATE SEQUENCE regress_s3;
|
||||
INSERT INTO regress_seq_test SELECT nextval('regress_s3') FROM generate_series(1,100);
|
||||
|
||||
-- Existing sequence
|
||||
INSERT INTO regress_seq_test SELECT nextval('regress_s2') FROM generate_series(1,100);
|
||||
));
|
||||
|
||||
# 1. Do ALTER SUBSCRIPTION ... REFRESH SEQUENCES
|
||||
$result = $node_subscriber->safe_psql(
|
||||
'postgres', qq(
|
||||
ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES;
|
||||
));
|
||||
$node_subscriber->poll_query_until('postgres', $synced_query)
|
||||
or die "Timed out while waiting for subscriber to synchronize data";
|
||||
|
||||
# Check - existing sequences ('regress_s1' and 'regress_s2') are synced
|
||||
$result = $node_subscriber->safe_psql(
|
||||
'postgres', qq(
|
||||
SELECT last_value, is_called FROM regress_s1;
|
||||
));
|
||||
is($result, '200|t', 'REFRESH SEQUENCES will sync existing sequences');
|
||||
$result = $node_subscriber->safe_psql(
|
||||
'postgres', qq(
|
||||
SELECT last_value, is_called FROM regress_s2;
|
||||
));
|
||||
is($result, '200|t', 'REFRESH SEQUENCES will sync existing sequences');
|
||||
|
||||
# Check - newly published sequence ('regress_s3') is not synced
|
||||
$result = $node_subscriber->safe_psql(
|
||||
'postgres', qq(
|
||||
SELECT last_value, is_called FROM regress_s3;
|
||||
));
|
||||
is($result, '1|f',
|
||||
'REFRESH SEQUENCES will not sync newly published sequence');
|
||||
|
||||
# 2. Do ALTER SUBSCRIPTION ... REFRESH PUBLICATION with copy_data as false
|
||||
$result = $node_subscriber->safe_psql(
|
||||
'postgres', qq(
|
||||
ALTER SUBSCRIPTION regress_seq_sub REFRESH PUBLICATION WITH (copy_data = false);
|
||||
));
|
||||
$node_subscriber->poll_query_until('postgres', $synced_query)
|
||||
or die "Timed out while waiting for subscriber to synchronize data";
|
||||
|
||||
# Check - newly published sequence ('regress_s3') is not synced with copy_data
|
||||
# as false.
|
||||
$result = $node_subscriber->safe_psql(
|
||||
'postgres', qq(
|
||||
SELECT last_value, is_called FROM regress_s3;
|
||||
));
|
||||
is($result, '1|f',
|
||||
'REFRESH PUBLICATION will not sync newly published sequence with copy_data as false'
|
||||
);
|
||||
is($result, 'regress_s1|i', "Sequence can be in pg_subscription_rel catalog");
|
||||
|
||||
##########
|
||||
# ALTER SUBSCRIPTION ... REFRESH PUBLICATION should report an error when:
|
||||
# a) sequence definitions differ between the publisher and subscriber, or
|
||||
# b) a sequence is missing on the publisher.
|
||||
##########
|
||||
|
||||
# Create a new sequence 'regress_s4' whose START value is not the same in the
|
||||
# publisher and subscriber.
|
||||
$node_publisher->safe_psql(
|
||||
'postgres', qq(
|
||||
CREATE SEQUENCE regress_s4 START 1 INCREMENT 2;
|
||||
));
|
||||
|
||||
$node_subscriber->safe_psql(
|
||||
'postgres', qq(
|
||||
CREATE SEQUENCE regress_s4 START 10 INCREMENT 2;
|
||||
));
|
||||
|
||||
my $log_offset = -s $node_subscriber->logfile;
|
||||
|
||||
# Do ALTER SUBSCRIPTION ... REFRESH PUBLICATION
|
||||
$node_subscriber->safe_psql('postgres',
|
||||
"ALTER SUBSCRIPTION regress_seq_sub REFRESH PUBLICATION");
|
||||
|
||||
# Verify that an error is logged for parameter differences on sequence
|
||||
# ('regress_s4').
|
||||
$node_subscriber->wait_for_log(
|
||||
qr/WARNING: ( [A-Z0-9]+:)? mismatched or renamed sequence on subscriber \("public.regress_s4"\)\n.*ERROR: ( [A-Z0-9]+:)? logical replication sequence synchronization failed for subscription "regress_seq_sub"/,
|
||||
$log_offset);
|
||||
|
||||
# Verify that an error is logged for the missing sequence ('regress_s4').
|
||||
$node_publisher->safe_psql('postgres', qq(DROP SEQUENCE regress_s4;));
|
||||
|
||||
$node_subscriber->wait_for_log(
|
||||
qr/WARNING: ( [A-Z0-9]+:)? missing sequence on publisher \("public.regress_s4"\)\n.*ERROR: ( [A-Z0-9]+:)? logical replication sequence synchronization failed for subscription "regress_seq_sub"/,
|
||||
$log_offset);
|
||||
|
||||
done_testing();
|
||||
|
||||
Reference in New Issue
Block a user