1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Fix temporary WAL receiver slot handling on timeline switches

Previously, when wal_receiver_create_temp_slot was enabled, a timeline
switch could cause the walreceiver to try to create the same temporary
replication slot again on the same connection. The slot had already been
created before the first streaming attempt and still existed, so the
second creation attempt failed with a FATAL error such as
"could not create replication slot ...".

The walreceiver would later be restarted and streaming replication could
continue, so this did not permanently break replication. Nevertheless,
the unexpected failure is a bug and should be fixed.

Fix this by tracking whether the temporary replication slot has already
been created for the lifetime of the walreceiver and skipping subsequent
creation attempts. Also copy the retained slot name to shared memory on
each streaming attempt, since RequestXLogStreaming() clears it when
streaming is restarted without a configured primary slot. This also
keeps pg_stat_wal_receiver.slot_name populated after timeline switches.

Backpatch to all supported versions.

Author: ChangAo Chen <cca5507@qq.com>
Reviewed-by: Quan Zongliang <quanzongliang@yeah.net>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/tencent_628FDAF814231923BC8E8357BBBC50F94207@qq.com
Backpatch-through: 14
This commit is contained in:
Fujii Masao
2026-08-28 01:16:45 +09:00
parent e1a78c3c0d
commit 58fb12f4aa
2 changed files with 59 additions and 7 deletions

View File

@@ -197,6 +197,7 @@ WalReceiverMain(void)
char *tmp_conninfo;
char slotname[NAMEDATALEN];
bool is_temp_slot;
bool temp_slot_created = false;
XLogRecPtr startpoint;
TimeLineID startpointTLI;
TimeLineID primaryTLI;
@@ -454,17 +455,25 @@ WalReceiverMain(void)
WalRcvFetchTimeLineHistoryFiles(startpointTLI, primaryTLI);
/*
* Create temporary replication slot if requested, and update slot
* name in shared memory. (Note the slot name cannot already be set
* in this case.)
* Create a temporary replication slot if requested. This only needs
* to be done for the first streaming attempt on this connection
* because the slot remains available while this connection is reused
* for later streaming attempts. Update the slot name in shared
* memory each time because RequestXLogStreaming() clears it when
* restarting streaming.
*/
if (is_temp_slot)
{
snprintf(slotname, sizeof(slotname),
"pg_walreceiver_%lld",
(long long int) walrcv_get_backend_pid(wrconn));
if (!temp_slot_created)
{
snprintf(slotname, sizeof(slotname),
"pg_walreceiver_%lld",
(long long int) walrcv_get_backend_pid(wrconn));
walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
temp_slot_created = true;
}
SpinLockAcquire(&walrcv->mutex);
strlcpy(walrcv->slotname, slotname, NAMEDATALEN);

View File

@@ -147,4 +147,47 @@ my $result_2 =
$node_standby_3->safe_psql('postgres', "SELECT count(*) FROM tab_int");
is($result_2, qq(1), 'check content of standby 3');
# Ensure that a WAL receiver creates a temporary replication slot only once
# when following an upstream across a timeline switch.
# Initialize primary node
my $node_primary_3 = PostgreSQL::Test::Cluster->new('primary_3');
$node_primary_3->init(allows_streaming => 1);
$node_primary_3->start;
# Take backup
$node_primary_3->backup($backup_name);
# Create standby node
my $node_standby_4 = PostgreSQL::Test::Cluster->new('standby_4');
$node_standby_4->init_from_backup($node_primary_3, $backup_name,
has_streaming => 1);
$node_standby_4->append_conf(
'postgresql.conf', qq(
wal_receiver_create_temp_slot = on
));
# Restart primary node in standby mode and promote it, switching it
# to a new timeline.
$node_primary_3->set_standby_mode;
$node_primary_3->restart;
$node_primary_3->promote;
# Start standby node, create some content on primary and check its presence
# in standby, to ensure that the timeline switch has been done.
$node_standby_4->start;
$node_primary_3->safe_psql('postgres',
"CREATE TABLE tab_int AS SELECT 1 AS a");
$node_primary_3->wait_for_catchup($node_standby_4);
ok( !$node_standby_4->log_contains(
'could not create replication slot "pg_walreceiver_[0-9]+".*already exists'
),
'temporary replication slot is not recreated across timeline jumps');
my $temp_slot_name = $node_standby_4->safe_psql('postgres',
"SELECT slot_name FROM pg_stat_wal_receiver");
like($temp_slot_name, qr/^pg_walreceiver_[0-9]+$/,
'pg_stat_wal_receiver.slot_name remains set across timeline jumps');
done_testing();