From 41eed2a90d68f4d9ac1ee3d00c89879358d19fd1 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Thu, 21 Nov 2024 23:07:22 +0100 Subject: [PATCH 2/5] Add TAP test to check logical repl slot advance during checkpoint The test verifies that logical replication slot is still valid after immediate restart on checkpoint completion in case when the slot was advanced during checkpoint. Original patch by: Tomas Vondra Modified by: Vitaly Davydov Discussion: https://www.postgresql.org/message-id/flat/1d12d2-67235980-35-19a406a0%4063439497 --- src/test/modules/Makefile | 4 +- src/test/modules/meson.build | 1 + .../test_replslot_required_lsn/Makefile | 18 +++ .../test_replslot_required_lsn/meson.build | 15 +++ .../t/001_logical_slot.pl | 124 ++++++++++++++++++ 5 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 src/test/modules/test_replslot_required_lsn/Makefile create mode 100644 src/test/modules/test_replslot_required_lsn/meson.build create mode 100644 src/test/modules/test_replslot_required_lsn/t/001_logical_slot.pl diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index aa1d27bbed3..53d3dd8e0ed 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -46,9 +46,9 @@ SUBDIRS = \ ifeq ($(enable_injection_points),yes) -SUBDIRS += injection_points gin typcache +SUBDIRS += injection_points gin typcache test_replslot_required_lsn else -ALWAYS_SUBDIRS += injection_points gin typcache +ALWAYS_SUBDIRS += injection_points gin typcache test_replslot_required_lsn endif ifeq ($(with_ssl),openssl) diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 9de0057bd1d..ac0dbd1f10f 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -43,3 +43,4 @@ subdir('typcache') subdir('unsafe_tests') subdir('worker_spi') subdir('xid_wraparound') +subdir('test_replslot_required_lsn') diff --git a/src/test/modules/test_replslot_required_lsn/Makefile b/src/test/modules/test_replslot_required_lsn/Makefile new file mode 100644 index 00000000000..e5ff8af255b --- /dev/null +++ b/src/test/modules/test_replslot_required_lsn/Makefile @@ -0,0 +1,18 @@ +# src/test/modules/test_replslot_required_lsn/Makefile + +EXTRA_INSTALL=src/test/modules/injection_points \ + contrib/test_decoding + +export enable_injection_points +TAP_TESTS = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_replslot_required_lsn +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_replslot_required_lsn/meson.build b/src/test/modules/test_replslot_required_lsn/meson.build new file mode 100644 index 00000000000..999c16201fb --- /dev/null +++ b/src/test/modules/test_replslot_required_lsn/meson.build @@ -0,0 +1,15 @@ +# Copyright (c) 2025, PostgreSQL Global Development Group + +tests += { + 'name': 'test_replslot_required_lsn', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'env': { + 'enable_injection_points': get_option('injection_points') ? 'yes' : 'no', + }, + 'tests': [ + 't/001_logical_slot.pl' + ], + }, +} diff --git a/src/test/modules/test_replslot_required_lsn/t/001_logical_slot.pl b/src/test/modules/test_replslot_required_lsn/t/001_logical_slot.pl new file mode 100644 index 00000000000..ff13c741ad0 --- /dev/null +++ b/src/test/modules/test_replslot_required_lsn/t/001_logical_slot.pl @@ -0,0 +1,124 @@ +# Copyright (c) 2025, PostgreSQL Global Development Group +# +# This test verifies the case when the logical slot is advanced during +# checkpoint. The test checks that the logical slot's restart_lsn still refers +# to an existed WAL segment after immediate restart. +# +# Discussion: +# https://www.postgresql.org/message-id/flat/1d12d2-67235980-35-19a406a0%4063439497 +# +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; + +use Test::More; + +if ($ENV{enable_injection_points} ne 'yes') +{ + plan skip_all => 'Injection points not supported by this build'; +} + +my ($node, $result); + +$node = PostgreSQL::Test::Cluster->new('mike'); +$node->init; +$node->append_conf('postgresql.conf', + "shared_preload_libraries = 'injection_points'"); +$node->append_conf('postgresql.conf', + "wal_level = 'logical'"); +$node->start; +$node->safe_psql('postgres', q(CREATE EXTENSION injection_points)); + +# create a simple table to generate data into +$node->safe_psql('postgres', + q{create table t (id serial primary key, b text)}); + +# create the two slots we'll need +$node->safe_psql('postgres', + q{select pg_create_logical_replication_slot('slot_logical', 'test_decoding')}); +$node->safe_psql('postgres', + q{select pg_create_physical_replication_slot('slot_physical', true)}); + +# advance both to current position, just to have everything "valid" +$node->safe_psql('postgres', + q{select count(*) from pg_logical_slot_get_changes('slot_logical', null, null)}); +$node->safe_psql('postgres', + q{select pg_replication_slot_advance('slot_physical', pg_current_wal_lsn())}); + +# run checkpoint, to flush current state to disk and set a baseline +$node->safe_psql('postgres', q{checkpoint}); + +# generate transactions to get RUNNING_XACTS +my $xacts = $node->background_psql('postgres'); +$xacts->query_until(qr/run_xacts/, +q(\echo run_xacts +SELECT 1 \watch 0.1 +\q +)); + +# insert 2M rows, that's about 260MB (~20 segments) worth of WAL +$node->safe_psql('postgres', + q{insert into t (b) select md5(i::text) from generate_series(1,1000000) s(i)}); + +# run another checkpoint, to set a new restore LSN +$node->safe_psql('postgres', q{checkpoint}); + +# another 2M rows, that's about 260MB (~20 segments) worth of WAL +$node->safe_psql('postgres', + q{insert into t (b) select md5(i::text) from generate_series(1,1000000) s(i)}); + +# run another checkpoint, this time in the background, and make it wait +# on the injection point), so that the checkpoint stops right before +# removing old WAL segments +print('starting checkpoint\n'); + +my $checkpoint = $node->background_psql('postgres'); +$checkpoint->query_safe(q(select injection_points_attach('checkpoint-before-old-wal-removal','wait'))); +$checkpoint->query_until(qr/starting_checkpoint/, +q(\echo starting_checkpoint +checkpoint; +\q +)); + +print('waiting for injection_point\n'); +# wait until the checkpoint stops right before removing WAL segments +$node->wait_for_event('checkpointer', 'checkpoint-before-old-wal-removal'); + + +# try to advance the logical slot, but make it stop when it moves to the +# next WAL segment (has to happen in the background too) +my $logical = $node->background_psql('postgres'); +$logical->query_safe(q{select injection_points_attach('logical-replication-slot-advance-segment','wait');}); +$logical->query_until(qr/get_changes/, +q( +\echo get_changes +select count(*) from pg_logical_slot_get_changes('slot_logical', null, null) \watch 1 +\q +)); + +# wait until the checkpoint stops right before removing WAL segments +$node->wait_for_event('client backend', 'logical-replication-slot-advance-segment'); + +# OK, we're in the right situation, time to advance the physical slot, +# which recalculates the required LSN, and then unblock the checkpoint, +# which removes the WAL still needed by the logical slot +$node->safe_psql('postgres', + q{select pg_replication_slot_advance('slot_physical', pg_current_wal_lsn())}); + +$node->safe_psql('postgres', + q{select injection_points_wakeup('checkpoint-before-old-wal-removal')}); + +# abruptly stop the server (1 second should be enough for the checkpoint +# to finish, would be better ) +$node->stop('immediate'); + +$node->start; + +eval { + $node->safe_psql('postgres', q{select count(*) from pg_logical_slot_get_changes('slot_logical', null, null);}); +}; +is($@, '', "Logical slot still valid"); + +done_testing(); -- 2.34.1