postgresql/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

365 lines
10 KiB
Perl
Raw Normal View History

pg_createsubscriber: creates a new logical replica from a standby server It must be run on the target server and should be able to connect to the source server (publisher) and the target server (subscriber). All tables in the specified database(s) are included in the logical replication setup. A pair of publication and subscription objects are created for each database. The main advantage of pg_createsubscriber over the common logical replication setup is the initial data copy. It also reduces the catchup phase. Some prerequisites must be met to successfully run it. It is basically the logical replication requirements. It starts creating a publication using FOR ALL TABLES and a replication slot for each specified database. Write recovery parameters into the target data directory and start the target server. It specifies the LSN of the last replication slot (replication start point) up to which the recovery will proceed. Wait until the target server is promoted. Create one subscription per specified database (using publication and replication slot created in a previous step) on the target server. Set the replication progress to the replication start point for each subscription. Enable the subscription for each specified database on the target server. And finally, change the system identifier on the target server. Author: Euler Taveira <euler.taveira@enterprisedb.com> Reviewed-by: Hayato Kuroda <kuroda.hayato@fujitsu.com> Reviewed-by: Amit Kapila <amit.kapila16@gmail.com> Reviewed-by: Shlok Kyal <shlok.kyal.oss@gmail.com> Reviewed-by: Vignesh C <vignesh21@gmail.com> Reviewed-by: Shubham Khanna <khannashubham1197@gmail.com> Reviewed-by: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> Reviewed-by: Peter Eisentraut <peter@eisentraut.org> Discussion: https://www.postgresql.org/message-id/flat/5ac50071-f2ed-4ace-a8fd-b892cffd33eb@www.fastmail.com
2024-03-25 12:30:55 +01:00
# Copyright (c) 2024, PostgreSQL Global Development Group
#
# Test using a standby server as the subscriber.
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
program_help_ok('pg_createsubscriber');
program_version_ok('pg_createsubscriber');
program_options_handling_ok('pg_createsubscriber');
my $datadir = PostgreSQL::Test::Utils::tempdir;
#
# Test mandatory options
command_fails(['pg_createsubscriber'],
'no subscriber data directory specified');
command_fails(
[ 'pg_createsubscriber', '--pgdata', $datadir ],
'no publisher connection string specified');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432'
],
'no database name specified');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432',
'--database', 'pg1',
'--database', 'pg1'
],
'duplicate database name');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432',
'--publication', 'foo1',
'--publication', 'foo1',
'--database', 'pg1',
'--database', 'pg2'
],
'duplicate publication name');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432',
'--publication', 'foo1',
'--database', 'pg1',
'--database', 'pg2'
],
'wrong number of publication names');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432',
'--publication', 'foo1',
'--publication', 'foo2',
'--subscription', 'bar1',
'--database', 'pg1',
'--database', 'pg2'
],
'wrong number of subscription names');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432',
'--publication', 'foo1',
'--publication', 'foo2',
'--subscription', 'bar1',
'--subscription', 'bar2',
'--replication-slot', 'baz1',
'--database', 'pg1',
'--database', 'pg2'
],
'wrong number of replication slot names');
# Set up node P as primary
my $node_p = PostgreSQL::Test::Cluster->new('node_p');
$node_p->init(allows_streaming => 'logical');
$node_p->start;
# Set up node F as about-to-fail node
# Force it to initialize a new cluster instead of copying a
# previously initdb'd cluster. New cluster has a different system identifier so
# we can test if the target cluster is a copy of the source cluster.
my $node_f = PostgreSQL::Test::Cluster->new('node_f');
$node_f->init(force_initdb => 1, allows_streaming => 'logical');
# On node P
# - create databases
# - create test tables
# - insert a row
# - create a physical replication slot
$node_p->safe_psql(
'postgres', q(
CREATE DATABASE pg1;
CREATE DATABASE pg2;
));
$node_p->safe_psql('pg1', 'CREATE TABLE tbl1 (a text)');
$node_p->safe_psql('pg1', "INSERT INTO tbl1 VALUES('first row')");
$node_p->safe_psql('pg2', 'CREATE TABLE tbl2 (a text)');
my $slotname = 'physical_slot';
$node_p->safe_psql('pg2',
"SELECT pg_create_physical_replication_slot('$slotname')");
# Set up node S as standby linking to node P
$node_p->backup('backup_1');
my $node_s = PostgreSQL::Test::Cluster->new('node_s');
$node_s->init_from_backup($node_p, 'backup_1', has_streaming => 1);
$node_s->append_conf(
'postgresql.conf', qq[
primary_slot_name = '$slotname'
]);
$node_s->set_standby_mode();
$node_s->start;
# Set up node T as standby linking to node P then promote it
my $node_t = PostgreSQL::Test::Cluster->new('node_t');
$node_t->init_from_backup($node_p, 'backup_1', has_streaming => 1);
$node_t->set_standby_mode();
$node_t->start;
$node_t->promote;
$node_t->stop;
# Run pg_createsubscriber on a promoted server
command_fails(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_t->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_t->host,
'--subscriber-port', $node_t->port,
'--database', 'pg1',
'--database', 'pg2'
],
'target server is not in recovery');
# Run pg_createsubscriber when standby is running
command_fails(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--database', 'pg1',
'--database', 'pg2'
],
'standby is up and running');
# Run pg_createsubscriber on about-to-fail node F
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $node_f->data_dir,
'--publisher-server', $node_p->connstr('pg1'),
'--socket-directory', $node_f->host,
'--subscriber-port', $node_f->port,
'--database', 'pg1',
'--database', 'pg2'
],
'subscriber data directory is not a copy of the source database cluster');
# Set up node C as standby linking to node S
$node_s->backup('backup_2');
my $node_c = PostgreSQL::Test::Cluster->new('node_c');
$node_c->init_from_backup($node_s, 'backup_2', has_streaming => 1);
$node_c->adjust_conf('postgresql.conf', 'primary_slot_name', undef);
$node_c->set_standby_mode();
# Run pg_createsubscriber on node C (P -> S -> C)
command_fails(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_c->data_dir, '--publisher-server',
$node_s->connstr('pg1'),
'--socket-directory', $node_c->host,
'--subscriber-port', $node_c->port,
'--database', 'pg1',
'--database', 'pg2'
],
'primary server is in recovery');
# Insert another row on node P and wait node S to catch up
$node_p->safe_psql('pg1', "INSERT INTO tbl1 VALUES('second row')");
$node_p->wait_for_replay_catchup($node_s);
# Check some unmet conditions on node P
$node_p->append_conf('postgresql.conf', q{
wal_level = replica
max_replication_slots = 1
max_wal_senders = 1
max_worker_processes = 2
});
$node_p->restart;
$node_s->stop;
command_fails(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--database', 'pg1',
'--database', 'pg2'
],
'primary contains unmet conditions on node P');
# Restore default settings here but only apply it after testing standby. Some
# standby settings should not be a lower setting than on the primary.
$node_p->append_conf('postgresql.conf', q{
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10
max_worker_processes = 8
});
# Check some unmet conditions on node S
$node_s->append_conf('postgresql.conf', q{
max_replication_slots = 1
max_logical_replication_workers = 1
max_worker_processes = 2
});
command_fails(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--database', 'pg1',
'--database', 'pg2'
],
'standby contains unmet conditions on node S');
$node_s->append_conf('postgresql.conf', q{
max_replication_slots = 10
max_logical_replication_workers = 4
max_worker_processes = 8
});
# Restore default settings on both servers
$node_p->restart;
# dry run mode on node S
command_ok(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--publication', 'pub1',
'--publication', 'pub2',
'--subscription', 'sub1',
'--subscription', 'sub2',
'--database', 'pg1',
'--database', 'pg2'
],
'run pg_createsubscriber --dry-run on node S');
# Check if node S is still a standby
$node_s->start;
is($node_s->safe_psql('postgres', 'SELECT pg_catalog.pg_is_in_recovery()'),
't', 'standby is in recovery');
$node_s->stop;
# pg_createsubscriber can run without --databases option
command_ok(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--replication-slot', 'replslot1'
],
'run pg_createsubscriber without --databases');
# Run pg_createsubscriber on node S
command_ok(
[
'pg_createsubscriber', '--verbose',
'--verbose', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--publication', 'pub1',
'--publication', 'Pub2',
'--replication-slot', 'replslot1',
'--replication-slot', 'replslot2',
'--database', 'pg1',
'--database', 'pg2'
],
'run pg_createsubscriber on node S');
# Confirm the physical replication slot has been removed
my $result = $node_p->safe_psql('pg1',
"SELECT count(*) FROM pg_replication_slots WHERE slot_name = '$slotname'"
);
is($result, qq(0),
'the physical replication slot used as primary_slot_name has been removed'
);
# Insert rows on P
$node_p->safe_psql('pg1', "INSERT INTO tbl1 VALUES('third row')");
$node_p->safe_psql('pg2', "INSERT INTO tbl2 VALUES('row 1')");
# Start subscriber
$node_s->start;
# Get subscription names
$result = $node_s->safe_psql(
'postgres', qq(
SELECT subname FROM pg_subscription WHERE subname ~ '^pg_createsubscriber_'
));
my @subnames = split("\n", $result);
# Wait subscriber to catch up
$node_s->wait_for_subscription_sync($node_p, $subnames[0]);
$node_s->wait_for_subscription_sync($node_p, $subnames[1]);
# Check result on database pg1
$result = $node_s->safe_psql('pg1', 'SELECT * FROM tbl1');
is( $result, qq(first row
second row
third row),
'logical replication works on database pg1');
# Check result on database pg2
$result = $node_s->safe_psql('pg2', 'SELECT * FROM tbl2');
is($result, qq(row 1), 'logical replication works on database pg2');
# Different system identifier?
my $sysid_p = $node_p->safe_psql('postgres',
'SELECT system_identifier FROM pg_control_system()');
my $sysid_s = $node_s->safe_psql('postgres',
'SELECT system_identifier FROM pg_control_system()');
ok($sysid_p != $sysid_s, 'system identifier was changed');
# clean up
$node_p->teardown_node;
$node_s->teardown_node;
$node_t->teardown_node;
$node_f->teardown_node;
done_testing();