1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
# Copyright (c) 2021-2024, PostgreSQL Global Development Group
# Test replication statistics data in pg_stat_replication_slots is sane after
# drop replication slot and restart.
use strict;
use warnings FATAL => 'all';
use File::Path qw(rmtree);
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
# Test set-up
my $node = PostgreSQL::Test::Cluster->new('test');
$node->init(allows_streaming => 'logical');
$node->append_conf('postgresql.conf', 'synchronous_commit = on');
$node->start;
# Check that replication slot stats are expected.
sub test_slot_stats
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
my ($node, $expected, $msg) = @_;
my $result = $node->safe_psql(
'postgres', qq[
SELECT slot_name, total_txns > 0 AS total_txn,
total_bytes > 0 AS total_bytes
FROM pg_stat_replication_slots
ORDER BY slot_name]);
is($result, $expected, $msg);
}
# Create table.
$node->safe_psql('postgres', "CREATE TABLE test_repl_stat(col1 int)");
# Create replication slots.
$node->safe_psql(
'postgres', qq[
SELECT pg_create_logical_replication_slot('regression_slot1', 'test_decoding');
SELECT pg_create_logical_replication_slot('regression_slot2', 'test_decoding');
SELECT pg_create_logical_replication_slot('regression_slot3', 'test_decoding');
SELECT pg_create_logical_replication_slot('regression_slot4', 'test_decoding');
]);
# Insert some data.
$node->safe_psql('postgres',
"INSERT INTO test_repl_stat values(generate_series(1, 5));");
$node->safe_psql(
'postgres', qq[
SELECT data FROM pg_logical_slot_get_changes('regression_slot1', NULL,
NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
SELECT data FROM pg_logical_slot_get_changes('regression_slot2', NULL,
NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
SELECT data FROM pg_logical_slot_get_changes('regression_slot3', NULL,
NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
SELECT data FROM pg_logical_slot_get_changes('regression_slot4', NULL,
NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
]);
# Wait for the statistics to be updated.
$node->poll_query_until(
'postgres', qq[
SELECT count(slot_name) >= 4 FROM pg_stat_replication_slots
WHERE slot_name ~ 'regression_slot'
AND total_txns > 0 AND total_bytes > 0;
]) or die "Timed out while waiting for statistics to be updated";
# Test to drop one of the replication slot and verify replication statistics data is
# fine after restart.
$node->safe_psql('postgres',
"SELECT pg_drop_replication_slot('regression_slot4')");
$node->stop;
$node->start;
# Verify statistics data present in pg_stat_replication_slots are sane after
# restart.
test_slot_stats(
$node,
qq(regression_slot1|t|t
regression_slot2|t|t
regression_slot3|t|t),
'check replication statistics are updated');
# Test to remove one of the replication slots and adjust
# max_replication_slots accordingly to the number of slots. This leads
# to a mismatch between the number of slots present in the stats file and the
# number of stats present in shared memory. We verify
# replication statistics data is fine after restart.
$node->stop;
my $datadir = $node->data_dir;
my $slot3_replslotdir = "$datadir/pg_replslot/regression_slot3";
rmtree($slot3_replslotdir);
$node->append_conf('postgresql.conf', 'max_replication_slots = 2');
$node->start;
# Verify statistics data present in pg_stat_replication_slots are sane after
# restart.
test_slot_stats(
$node,
qq(regression_slot1|t|t
regression_slot2|t|t),
'check replication statistics after removing the slot file');
# cleanup
$node->safe_psql('postgres', "DROP TABLE test_repl_stat");
$node->safe_psql('postgres',
"SELECT pg_drop_replication_slot('regression_slot1')");
$node->safe_psql('postgres',
"SELECT pg_drop_replication_slot('regression_slot2')");
# shutdown
$node->stop;
# Test replication slot stats persistence in a single session. The slot
# is dropped and created concurrently of a session peeking at its data
# repeatedly, hence holding in its local cache a reference to the stats.
$node->start;
my $slot_name_restart = 'regression_slot5';
$node->safe_psql('postgres',
"SELECT pg_create_logical_replication_slot('$slot_name_restart', 'test_decoding');"
);
# Look at slot data, with a persistent connection.
my $bpgsql = $node->background_psql('postgres', on_error_stop => 1);
# Launch query and look at slot data, incrementing the refcount of the
# stats entry.
$bpgsql->query_safe(
"SELECT pg_logical_slot_peek_binary_changes('$slot_name_restart', NULL, NULL)"
);
# Drop the slot entry. The stats entry is not dropped yet as the previous
# session still holds a reference to it.
$node->safe_psql('postgres',
"SELECT pg_drop_replication_slot('$slot_name_restart')");
# Create again the same slot. The stats entry is reinitialized, not marked
# as dropped anymore.
$node->safe_psql('postgres',
"SELECT pg_create_logical_replication_slot('$slot_name_restart', 'test_decoding');"
);
# Look again at the slot data. The local stats reference should be refreshed
# to the reinitialized entry.
$bpgsql->query_safe(
"SELECT pg_logical_slot_peek_binary_changes('$slot_name_restart', NULL, NULL)"
);
# Drop again the slot, the entry is not dropped yet as the previous session
# still has a refcount on it.
$node->safe_psql('postgres',
"SELECT pg_drop_replication_slot('$slot_name_restart')");
# Shutdown the node, which should happen cleanly with the stats file written
# to disk. Note that the background session created previously needs to be
# hold *while* the node is shutting down to check that it drops the stats
# entry of the slot before writing the stats file.
$node->stop;
# Make sure that the node is correctly shut down. Checking the control file
# is not enough, as the node may detect that something is incorrect after the
# control file has been updated and the shutdown checkpoint is finished, so
# also check that the stats file has been written out.
command_like(
[ 'pg_controldata', $node->data_dir ],
qr/Database cluster state:\s+shut down\n/,
'node shut down ok');
my $stats_file = "$datadir/pg_stat/pgstat.stat";
ok(-f "$stats_file", "stats file must exist after shutdown");
$bpgsql->quit;
done_testing();
|