mirror of https://github.com/postgres/postgres
When decoding from a logical slot, it's necessary for xlog reading to be able to read xlog from historical (i.e. not current) timelines; otherwise, decoding fails after failover, because the archives are in the historical timeline. This is required to make "failover logical slots" possible; it currently has no other use, although theoretically it could be used by an extension that creates a slot on a standby and continues to replay from the slot when the standby is promoted. This commit includes a module in src/test/modules with functions to manipulate the slots (which is not otherwise possible in SQL code) in order to enable testing, and a new test in src/test/recovery to ensure that the behavior is as expected. Author: Craig Ringer Reviewed-By: Oleksii Kliukin, Andres Freund, Petr Jelínekpull/11/head
parent
3b02ea4f07
commit
24c5f1a103
@ -0,0 +1,3 @@ |
||||
results/ |
||||
tmp_check/ |
||||
log/ |
@ -0,0 +1,22 @@ |
||||
# src/test/modules/test_slot_timelines/Makefile
|
||||
|
||||
MODULES = test_slot_timelines
|
||||
PGFILEDESC = "test_slot_timelines - test utility for slot timeline following"
|
||||
|
||||
EXTENSION = test_slot_timelines
|
||||
DATA = test_slot_timelines--1.0.sql
|
||||
|
||||
EXTRA_INSTALL=contrib/test_decoding
|
||||
REGRESS=load_extension
|
||||
REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_slot_timelines/test_slot_timelines.conf
|
||||
|
||||
ifdef USE_PGXS |
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS) |
||||
else |
||||
subdir = src/test/modules/test_slot_timelines
|
||||
top_builddir = ../../../..
|
||||
include $(top_builddir)/src/Makefile.global |
||||
include $(top_srcdir)/contrib/contrib-global.mk |
||||
endif |
@ -0,0 +1,19 @@ |
||||
A test module for logical decoding failover and timeline following. |
||||
|
||||
This module provides a minimal way to maintain logical slots on replicas |
||||
that mirror the state on the master. It doesn't make decoding possible, |
||||
just tracking slot state so that a decoding client that's using the master |
||||
can follow a physical failover to the standby. The master doesn't know |
||||
about the slots on the standby, they're synced by a client that connects |
||||
to both. |
||||
|
||||
This is intentionally not part of the test_decoding module because that's meant |
||||
to serve as example code, where this module exercises internal server features |
||||
by unsafely exposing internal state to SQL. It's not the right way to do |
||||
failover, it's just a simple way to test it from the perl TAP framework to |
||||
prove the feature works. |
||||
|
||||
In a practical implementation of this approach a bgworker on the master would |
||||
monitor slot positions and relay them to a bgworker on the standby that applies |
||||
the position updates without exposing slot internals to SQL. That's too complex |
||||
for this test framework though. |
@ -0,0 +1,19 @@ |
||||
CREATE EXTENSION test_slot_timelines; |
||||
SELECT test_slot_timelines_create_logical_slot('test_slot', 'test_decoding'); |
||||
test_slot_timelines_create_logical_slot |
||||
----------------------------------------- |
||||
|
||||
(1 row) |
||||
|
||||
SELECT test_slot_timelines_advance_logical_slot('test_slot', txid_current(), txid_current(), pg_current_xlog_location(), pg_current_xlog_location()); |
||||
test_slot_timelines_advance_logical_slot |
||||
------------------------------------------ |
||||
|
||||
(1 row) |
||||
|
||||
SELECT pg_drop_replication_slot('test_slot'); |
||||
pg_drop_replication_slot |
||||
-------------------------- |
||||
|
||||
(1 row) |
||||
|
@ -0,0 +1,7 @@ |
||||
CREATE EXTENSION test_slot_timelines; |
||||
|
||||
SELECT test_slot_timelines_create_logical_slot('test_slot', 'test_decoding'); |
||||
|
||||
SELECT test_slot_timelines_advance_logical_slot('test_slot', txid_current(), txid_current(), pg_current_xlog_location(), pg_current_xlog_location()); |
||||
|
||||
SELECT pg_drop_replication_slot('test_slot'); |
@ -0,0 +1,16 @@ |
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION |
||||
\echo Use "CREATE EXTENSION test_slot_timelines" to load this file. \quit |
||||
|
||||
CREATE OR REPLACE FUNCTION test_slot_timelines_create_logical_slot(slot_name text, plugin text) |
||||
RETURNS void |
||||
LANGUAGE c AS 'MODULE_PATHNAME'; |
||||
|
||||
COMMENT ON FUNCTION test_slot_timelines_create_logical_slot(text, text) |
||||
IS 'Create a logical slot at a particular lsn and xid. Do not use in production servers, it is not safe. The slot is created with an invalid xmin and lsn.'; |
||||
|
||||
CREATE OR REPLACE FUNCTION test_slot_timelines_advance_logical_slot(slot_name text, new_xmin bigint, new_catalog_xmin bigint, new_restart_lsn pg_lsn, new_confirmed_lsn pg_lsn) |
||||
RETURNS void |
||||
LANGUAGE c AS 'MODULE_PATHNAME'; |
||||
|
||||
COMMENT ON FUNCTION test_slot_timelines_advance_logical_slot(text, bigint, bigint, pg_lsn, pg_lsn) |
||||
IS 'Advance a logical slot directly. Do not use this in production servers, it is not safe.'; |
@ -0,0 +1,133 @@ |
||||
/*--------------------------------------------------------------------------
|
||||
* |
||||
* test_slot_timelines.c |
||||
* Test harness code for slot timeline following |
||||
* |
||||
* Copyright (c) 2016, PostgreSQL Global Development Group |
||||
* |
||||
* IDENTIFICATION |
||||
* src/test/modules/test_slot_timelines/test_slot_timelines.c |
||||
* |
||||
* ------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/transam.h" |
||||
#include "fmgr.h" |
||||
#include "miscadmin.h" |
||||
#include "replication/slot.h" |
||||
#include "utils/builtins.h" |
||||
#include "utils/pg_lsn.h" |
||||
|
||||
PG_MODULE_MAGIC; |
||||
|
||||
PG_FUNCTION_INFO_V1(test_slot_timelines_create_logical_slot); |
||||
PG_FUNCTION_INFO_V1(test_slot_timelines_advance_logical_slot); |
||||
|
||||
static void clear_slot_transient_state(void); |
||||
|
||||
/*
|
||||
* Create a new logical slot, with invalid LSN and xid, directly. This does not |
||||
* use the snapshot builder or logical decoding machinery. It's only intended |
||||
* for creating a slot on a replica that mirrors the state of a slot on an |
||||
* upstream master. |
||||
* |
||||
* Note that this is test harness code. You shouldn't expose slot internals |
||||
* to SQL like this for any real world usage. See the README. |
||||
*/ |
||||
Datum |
||||
test_slot_timelines_create_logical_slot(PG_FUNCTION_ARGS) |
||||
{ |
||||
char *slotname = text_to_cstring(PG_GETARG_TEXT_P(0)); |
||||
char *plugin = text_to_cstring(PG_GETARG_TEXT_P(1)); |
||||
|
||||
CheckSlotRequirements(); |
||||
|
||||
ReplicationSlotCreate(slotname, true, RS_PERSISTENT); |
||||
|
||||
/* register the plugin name with the slot */ |
||||
StrNCpy(NameStr(MyReplicationSlot->data.plugin), plugin, NAMEDATALEN); |
||||
|
||||
/*
|
||||
* Initialize persistent state to placeholders to be set by |
||||
* test_slot_timelines_advance_logical_slot . |
||||
*/ |
||||
MyReplicationSlot->data.xmin = InvalidTransactionId; |
||||
MyReplicationSlot->data.catalog_xmin = InvalidTransactionId; |
||||
MyReplicationSlot->data.restart_lsn = InvalidXLogRecPtr; |
||||
MyReplicationSlot->data.confirmed_flush = InvalidXLogRecPtr; |
||||
|
||||
clear_slot_transient_state(); |
||||
|
||||
ReplicationSlotRelease(); |
||||
|
||||
PG_RETURN_VOID(); |
||||
} |
||||
|
||||
/*
|
||||
* Set the state of a slot. |
||||
* |
||||
* This doesn't maintain the non-persistent state at all, |
||||
* but since the slot isn't in use that's OK. |
||||
* |
||||
* There's intentionally no check to prevent slots going backwards |
||||
* because they can actually go backwards if the master crashes when |
||||
* it hasn't yet flushed slot state to disk then we copy the older |
||||
* slot state after recovery. |
||||
* |
||||
* There's no checking done for xmin or catalog xmin either, since |
||||
* we can't really do anything useful that accounts for xid wrap-around. |
||||
* |
||||
* Note that this is test harness code. You shouldn't expose slot internals |
||||
* to SQL like this for any real world usage. See the README. |
||||
*/ |
||||
Datum |
||||
test_slot_timelines_advance_logical_slot(PG_FUNCTION_ARGS) |
||||
{ |
||||
char *slotname = text_to_cstring(PG_GETARG_TEXT_P(0)); |
||||
TransactionId new_xmin = (TransactionId) PG_GETARG_INT64(1); |
||||
TransactionId new_catalog_xmin = (TransactionId) PG_GETARG_INT64(2); |
||||
XLogRecPtr restart_lsn = PG_GETARG_LSN(3); |
||||
XLogRecPtr confirmed_lsn = PG_GETARG_LSN(4); |
||||
|
||||
CheckSlotRequirements(); |
||||
|
||||
ReplicationSlotAcquire(slotname); |
||||
|
||||
if (MyReplicationSlot->data.database != MyDatabaseId) |
||||
elog(ERROR, "Trying to update a slot on a different database"); |
||||
|
||||
MyReplicationSlot->data.xmin = new_xmin; |
||||
MyReplicationSlot->data.catalog_xmin = new_catalog_xmin; |
||||
MyReplicationSlot->data.restart_lsn = restart_lsn; |
||||
MyReplicationSlot->data.confirmed_flush = confirmed_lsn; |
||||
|
||||
clear_slot_transient_state(); |
||||
|
||||
ReplicationSlotMarkDirty(); |
||||
ReplicationSlotSave(); |
||||
ReplicationSlotRelease(); |
||||
|
||||
ReplicationSlotsComputeRequiredXmin(false); |
||||
ReplicationSlotsComputeRequiredLSN(); |
||||
|
||||
PG_RETURN_VOID(); |
||||
} |
||||
|
||||
static void |
||||
clear_slot_transient_state(void) |
||||
{ |
||||
Assert(MyReplicationSlot != NULL); |
||||
|
||||
/*
|
||||
* Make sure the slot state is the same as if it were newly loaded from |
||||
* disk on recovery. |
||||
*/ |
||||
MyReplicationSlot->effective_xmin = MyReplicationSlot->data.xmin; |
||||
MyReplicationSlot->effective_catalog_xmin = MyReplicationSlot->data.catalog_xmin; |
||||
|
||||
MyReplicationSlot->candidate_catalog_xmin = InvalidTransactionId; |
||||
MyReplicationSlot->candidate_xmin_lsn = InvalidXLogRecPtr; |
||||
MyReplicationSlot->candidate_restart_lsn = InvalidXLogRecPtr; |
||||
MyReplicationSlot->candidate_restart_valid = InvalidXLogRecPtr; |
||||
} |
@ -0,0 +1,2 @@ |
||||
max_replication_slots=2 |
||||
wal_level=logical |
@ -0,0 +1,5 @@ |
||||
# test_slot_timelines extension |
||||
comment = 'Test utility for slot timeline following and logical decoding' |
||||
default_version = '1.0' |
||||
module_pathname = '$libdir/test_slot_timelines' |
||||
relocatable = true |
@ -0,0 +1,304 @@ |
||||
# Demonstrate that logical can follow timeline switches. |
||||
# |
||||
# Logical replication slots can follow timeline switches but it's |
||||
# normally not possible to have a logical slot on a replica where |
||||
# promotion and a timeline switch can occur. The only ways |
||||
# we can create that circumstance are: |
||||
# |
||||
# * By doing a filesystem-level copy of the DB, since pg_basebackup |
||||
# excludes pg_replslot but we can copy it directly; or |
||||
# |
||||
# * by creating a slot directly at the C level on the replica and |
||||
# advancing it as we go using the low level APIs. It can't be done |
||||
# from SQL since logical decoding isn't allowed on replicas. |
||||
# |
||||
# This module uses the first approach to show that timeline following |
||||
# on a logical slot works. |
||||
# |
||||
use strict; |
||||
use warnings; |
||||
|
||||
use PostgresNode; |
||||
use TestLib; |
||||
use Test::More tests => 20; |
||||
use RecursiveCopy; |
||||
use File::Copy; |
||||
|
||||
my ($stdout, $stderr, $ret); |
||||
|
||||
# Initialize master node |
||||
my $node_master = get_new_node('master'); |
||||
$node_master->init(allows_streaming => 1, has_archiving => 1); |
||||
$node_master->append_conf('postgresql.conf', "wal_level = 'logical'\n"); |
||||
$node_master->append_conf('postgresql.conf', "max_replication_slots = 2\n"); |
||||
$node_master->append_conf('postgresql.conf', "max_wal_senders = 2\n"); |
||||
$node_master->append_conf('postgresql.conf', "log_min_messages = 'debug2'\n"); |
||||
$node_master->dump_info; |
||||
$node_master->start; |
||||
|
||||
diag "Testing logical timeline following with a filesystem-level copy"; |
||||
|
||||
$node_master->safe_psql('postgres', |
||||
"SELECT pg_create_logical_replication_slot('before_basebackup', 'test_decoding');" |
||||
); |
||||
$node_master->safe_psql('postgres', "CREATE TABLE decoding(blah text);"); |
||||
$node_master->safe_psql('postgres', |
||||
"INSERT INTO decoding(blah) VALUES ('beforebb');"); |
||||
$node_master->safe_psql('postgres', 'CHECKPOINT;'); |
||||
|
||||
my $backup_name = 'b1'; |
||||
$node_master->backup_fs_hot($backup_name); |
||||
|
||||
my $node_replica = get_new_node('replica'); |
||||
$node_replica->init_from_backup( |
||||
$node_master, $backup_name, |
||||
has_streaming => 1, |
||||
has_restoring => 1); |
||||
$node_replica->start; |
||||
|
||||
$node_master->safe_psql('postgres', |
||||
"SELECT pg_create_logical_replication_slot('after_basebackup', 'test_decoding');" |
||||
); |
||||
$node_master->safe_psql('postgres', |
||||
"INSERT INTO decoding(blah) VALUES ('afterbb');"); |
||||
$node_master->safe_psql('postgres', 'CHECKPOINT;'); |
||||
|
||||
# Verify that only the before base_backup slot is on the replica |
||||
$stdout = $node_replica->safe_psql('postgres', |
||||
'SELECT slot_name FROM pg_replication_slots ORDER BY slot_name'); |
||||
is($stdout, 'before_basebackup', |
||||
'Expected to find only slot before_basebackup on replica'); |
||||
|
||||
# Boom, crash |
||||
$node_master->stop('immediate'); |
||||
|
||||
$node_replica->promote; |
||||
$node_replica->poll_query_until('postgres', |
||||
"SELECT NOT pg_is_in_recovery();"); |
||||
|
||||
$node_replica->safe_psql('postgres', |
||||
"INSERT INTO decoding(blah) VALUES ('after failover');"); |
||||
|
||||
# Shouldn't be able to read from slot created after base backup |
||||
($ret, $stdout, $stderr) = $node_replica->psql('postgres', |
||||
"SELECT data FROM pg_logical_slot_peek_changes('after_basebackup', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');" |
||||
); |
||||
is($ret, 3, 'replaying from after_basebackup slot fails'); |
||||
like( |
||||
$stderr, |
||||
qr/replication slot "after_basebackup" does not exist/, |
||||
'after_basebackup slot missing'); |
||||
|
||||
# Should be able to read from slot created before base backup |
||||
($ret, $stdout, $stderr) = $node_replica->psql( |
||||
'postgres', |
||||
"SELECT data FROM pg_logical_slot_peek_changes('before_basebackup', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');", |
||||
timeout => 30); |
||||
is($ret, 0, 'replay from slot before_basebackup succeeds'); |
||||
is( $stdout, q(BEGIN |
||||
table public.decoding: INSERT: blah[text]:'beforebb' |
||||
COMMIT |
||||
BEGIN |
||||
table public.decoding: INSERT: blah[text]:'afterbb' |
||||
COMMIT |
||||
BEGIN |
||||
table public.decoding: INSERT: blah[text]:'after failover' |
||||
COMMIT), 'decoded expected data from slot before_basebackup'); |
||||
is($stderr, '', 'replay from slot before_basebackup produces no stderr'); |
||||
|
||||
# We don't need the standby anymore |
||||
$node_replica->teardown_node(); |
||||
|
||||
|
||||
# OK, time to try the same thing again, but this time we'll be using slot |
||||
# mirroring on the standby and a pg_basebackup of the master. |
||||
|
||||
diag "Testing logical timeline following with test_slot_timelines module"; |
||||
|
||||
$node_master->start(); |
||||
|
||||
# Clean up after the last test |
||||
$node_master->safe_psql('postgres', 'DELETE FROM decoding;'); |
||||
is( $node_master->psql( |
||||
'postgres', |
||||
'SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots;'), |
||||
0, |
||||
'dropping slots succeeds via pg_drop_replication_slot'); |
||||
|
||||
# Same as before, we'll make one slot before basebackup, one after. This time |
||||
# the basebackup will be with pg_basebackup so it'll omit both slots, then |
||||
# we'll use SQL functions provided by the test_slot_timelines test module to sync |
||||
# them to the replica, do some work, sync them and fail over then test again. |
||||
# This time we should have both the before- and after-basebackup slots working. |
||||
|
||||
is( $node_master->psql( |
||||
'postgres', |
||||
"SELECT pg_create_logical_replication_slot('before_basebackup', 'test_decoding');" |
||||
), |
||||
0, |
||||
'creating slot before_basebackup succeeds'); |
||||
|
||||
$node_master->safe_psql('postgres', |
||||
"INSERT INTO decoding(blah) VALUES ('beforebb');"); |
||||
|
||||
$backup_name = 'b2'; |
||||
$node_master->backup($backup_name); |
||||
|
||||
is( $node_master->psql( |
||||
'postgres', |
||||
"SELECT pg_create_logical_replication_slot('after_basebackup', 'test_decoding');" |
||||
), |
||||
0, |
||||
'creating slot after_basebackup succeeds'); |
||||
|
||||
$node_master->safe_psql('postgres', |
||||
"INSERT INTO decoding(blah) VALUES ('afterbb');"); |
||||
|
||||
$node_replica = get_new_node('replica2'); |
||||
$node_replica->init_from_backup( |
||||
$node_master, $backup_name, |
||||
has_streaming => 1, |
||||
has_restoring => 1); |
||||
|
||||
$node_replica->start; |
||||
|
||||
# Verify the slots are both absent on the replica |
||||
$stdout = $node_replica->safe_psql('postgres', |
||||
'SELECT slot_name FROM pg_replication_slots ORDER BY slot_name'); |
||||
is($stdout, '', 'No slots exist on the replica'); |
||||
|
||||
# Now do our magic to sync the slot states across. Normally |
||||
# this would be being done continuously by a bgworker but |
||||
# we're just doing it by hand for this test. This is exposing |
||||
# postgres innards to SQL so it's unsafe except for testing. |
||||
$node_master->safe_psql('postgres', 'CREATE EXTENSION test_slot_timelines;'); |
||||
my $slotinfo = $node_master->safe_psql('postgres', |
||||
'SELECT slot_name, plugin, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn FROM pg_replication_slots ORDER BY slot_name' |
||||
); |
||||
diag "Copying slots to replica"; |
||||
open my $fh, '<', \$slotinfo or die $!; |
||||
while (<$fh>) |
||||
{ |
||||
print $_; |
||||
chomp $_; |
||||
my ($slot_name, $plugin, $xmin, $catalog_xmin, $restart_lsn, |
||||
$confirmed_flush_lsn) |
||||
= map { |
||||
if ($_ ne '') { "'$_'" } |
||||
else { 'NULL' } |
||||
} split qr/\|/, $_; |
||||
|
||||
print |
||||
"# Copying slot $slot_name,$plugin,$xmin,$catalog_xmin,$restart_lsn,$confirmed_flush_lsn\n"; |
||||
$node_replica->safe_psql('postgres', |
||||
"SELECT test_slot_timelines_create_logical_slot($slot_name, $plugin);" |
||||
); |
||||
$node_replica->safe_psql('postgres', |
||||
"SELECT test_slot_timelines_advance_logical_slot($slot_name, $xmin, $catalog_xmin, $restart_lsn, $confirmed_flush_lsn);" |
||||
); |
||||
} |
||||
close $fh or die $!; |
||||
|
||||
# Now both slots are present on the replica and exactly match the master |
||||
$stdout = $node_replica->safe_psql('postgres', |
||||
'SELECT slot_name FROM pg_replication_slots ORDER BY slot_name'); |
||||
is( $stdout, |
||||
"after_basebackup\nbefore_basebackup", |
||||
'both slots now exist on replica'); |
||||
|
||||
$stdout = $node_replica->safe_psql( |
||||
'postgres', |
||||
qq{SELECT slot_name, plugin, xmin, catalog_xmin, |
||||
restart_lsn, confirmed_flush_lsn |
||||
FROM pg_replication_slots |
||||
ORDER BY slot_name}); |
||||
is($stdout, $slotinfo, |
||||
"slot data read back from replica matches slot data on master"); |
||||
|
||||
# We now have to copy some extra WAL to satisfy the requirements of the oldest |
||||
# replication slot. pg_basebackup doesn't know to copy the extra WAL for slots |
||||
# so we have to help out. We know the WAL is still retained on the master |
||||
# because we haven't advanced the slots there. |
||||
# |
||||
# Figure out what the oldest segment we need is by looking at the restart_lsn |
||||
# of the oldest slot. |
||||
# |
||||
# It only makes sense to do this once the slots are created on the replica, |
||||
# otherwise it might just delete the segments again. |
||||
|
||||
my $oldest_needed_segment = $node_master->safe_psql( |
||||
'postgres', |
||||
qq{SELECT pg_xlogfile_name(( |
||||
SELECT restart_lsn |
||||
FROM pg_replication_slots |
||||
ORDER BY restart_lsn ASC |
||||
LIMIT 1 |
||||
));} |
||||
); |
||||
|
||||
diag "oldest needed xlog seg is $oldest_needed_segment "; |
||||
|
||||
# WAL segment names sort lexically so we can just grab everything > than this |
||||
# segment. |
||||
opendir(my $pg_xlog, $node_master->data_dir . "/pg_xlog") or die $!; |
||||
while (my $seg = readdir $pg_xlog) |
||||
{ |
||||
next unless $seg >= $oldest_needed_segment && $seg =~ /^[0-9]{24}/; |
||||
diag "copying xlog seg $seg"; |
||||
copy( |
||||
$node_master->data_dir . "/pg_xlog/" . $seg, |
||||
$node_replica->data_dir . "/pg_xlog/" . $seg |
||||
) or die "copy of xlog seg $seg failed: $!"; |
||||
} |
||||
closedir $pg_xlog; |
||||
|
||||
# Boom, crash the master |
||||
$node_master->stop('immediate'); |
||||
|
||||
$node_replica->promote; |
||||
$node_replica->poll_query_until('postgres', |
||||
"SELECT NOT pg_is_in_recovery();"); |
||||
|
||||
$node_replica->safe_psql('postgres', |
||||
"INSERT INTO decoding(blah) VALUES ('after failover');"); |
||||
|
||||
# This time we can read from both slots |
||||
($ret, $stdout, $stderr) = $node_replica->psql( |
||||
'postgres', |
||||
"SELECT data FROM pg_logical_slot_peek_changes('after_basebackup', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');", |
||||
timeout => 30); |
||||
is($ret, 0, 'replay from slot after_basebackup succeeds'); |
||||
is( $stdout, q(BEGIN |
||||
table public.decoding: INSERT: blah[text]:'afterbb' |
||||
COMMIT |
||||
BEGIN |
||||
table public.decoding: INSERT: blah[text]:'after failover' |
||||
COMMIT), 'decoded expected data from slot after_basebackup'); |
||||
is($stderr, '', 'replay from slot after_basebackup produces no stderr'); |
||||
|
||||
# Should be able to read from slot created before base backup |
||||
# |
||||
# This would fail with an error about missing WAL segments if we hadn't |
||||
# copied extra WAL earlier. |
||||
($ret, $stdout, $stderr) = $node_replica->psql( |
||||
'postgres', |
||||
"SELECT data FROM pg_logical_slot_peek_changes('before_basebackup', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');", |
||||
timeout => 30); |
||||
is($ret, 0, 'replay from slot before_basebackup succeeds'); |
||||
is( $stdout, q(BEGIN |
||||
table public.decoding: INSERT: blah[text]:'beforebb' |
||||
COMMIT |
||||
BEGIN |
||||
table public.decoding: INSERT: blah[text]:'afterbb' |
||||
COMMIT |
||||
BEGIN |
||||
table public.decoding: INSERT: blah[text]:'after failover' |
||||
COMMIT), 'decoded expected data from slot before_basebackup'); |
||||
is($stderr, '', 'replay from slot before_basebackup produces no stderr'); |
||||
|
||||
($ret, $stdout, $stderr) = $node_replica->psql('postgres', |
||||
'SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots;'); |
||||
is($ret, 0, 'dropping slots succeeds via pg_drop_replication_slot'); |
||||
is($stderr, '', 'dropping slots produces no stderr output'); |
||||
|
||||
1; |
Loading…
Reference in new issue