Skip to content

Commit 53b9cd2

Browse files
committed
pgstat: test stats interactions with physical replication.
Tests that standbys: - drop stats for objects when the those records are replayed - persist stats across graceful restarts - discard stats after immediate / crash restarts Author: Melanie Plageman <[email protected]> Author: Andres Freund <[email protected]> Discussion: https://postgr.es/m/[email protected]
1 parent a90641e commit 53b9cd2

File tree

1 file changed

+206
-0
lines changed

1 file changed

+206
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# Copyright (c) 2021-2022, PostgreSQL Global Development Group
2+
3+
# Tests that standbys:
4+
# - drop stats for objects when the those records are replayed
5+
# - persist stats across graceful restarts
6+
# - discard stats after immediate / crash restarts
7+
8+
use strict;
9+
use warnings;
10+
use PostgreSQL::Test::Cluster;
11+
use PostgreSQL::Test::Utils;
12+
use Test::More;
13+
14+
my $node_primary = PostgreSQL::Test::Cluster->new('primary');
15+
$node_primary->init(allows_streaming => 1);
16+
$node_primary->append_conf('postgresql.conf', "track_functions = 'all'");
17+
$node_primary->start;
18+
19+
my $backup_name = 'my_backup';
20+
$node_primary->backup($backup_name);
21+
22+
my $node_standby = PostgreSQL::Test::Cluster->new('standby');
23+
$node_standby->init_from_backup($node_primary, $backup_name,
24+
has_streaming => 1);
25+
$node_standby->start;
26+
27+
28+
## Test that stats are cleaned up on standby after dropping table or function
29+
30+
my $sect = 'initial';
31+
32+
my ($dboid, $tableoid, $funcoid) =
33+
populate_standby_stats('postgres', 'public');
34+
test_standby_func_tab_stats_status('postgres',
35+
$dboid, $tableoid, $funcoid, 't');
36+
37+
drop_table_by_oid('postgres', $tableoid);
38+
drop_function_by_oid('postgres', $funcoid);
39+
40+
$sect = 'post drop';
41+
my $primary_lsn = $node_primary->lsn('flush');
42+
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
43+
test_standby_func_tab_stats_status('postgres',
44+
$dboid, $tableoid, $funcoid, 'f');
45+
46+
47+
## Test that stats are cleaned up on standby after dropping indirectly
48+
49+
$sect = "schema creation";
50+
51+
$node_primary->safe_psql('postgres', "CREATE SCHEMA drop_schema_test1");
52+
$primary_lsn = $node_primary->lsn('flush');
53+
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
54+
55+
($dboid, $tableoid, $funcoid) =
56+
populate_standby_stats('postgres', 'drop_schema_test1');
57+
58+
test_standby_func_tab_stats_status('postgres',
59+
$dboid, $tableoid, $funcoid, 't');
60+
$node_primary->safe_psql('postgres', "DROP SCHEMA drop_schema_test1 CASCADE");
61+
62+
$sect = "post schema drop";
63+
64+
$primary_lsn = $node_primary->lsn('flush');
65+
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
66+
67+
# verify table and function stats removed from standby
68+
test_standby_func_tab_stats_status('postgres',
69+
$dboid, $tableoid, $funcoid, 'f');
70+
71+
72+
## Test that stats are cleaned up on standby after dropping database
73+
74+
$sect = "createdb";
75+
76+
$node_primary->safe_psql('postgres', "CREATE DATABASE test");
77+
$primary_lsn = $node_primary->lsn('flush');
78+
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
79+
80+
($dboid, $tableoid, $funcoid) = populate_standby_stats('test', 'public');
81+
82+
# verify stats are present
83+
test_standby_func_tab_stats_status('test', $dboid, $tableoid, $funcoid, 't');
84+
test_standby_db_stats_status('test', $dboid, 't');
85+
86+
$node_primary->safe_psql('postgres', "DROP DATABASE test");
87+
$sect = "post dropdb";
88+
$primary_lsn = $node_primary->lsn('flush');
89+
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
90+
91+
# Test that the stats were cleaned up on standby
92+
# Note that this connects to 'postgres' but provides the dboid of dropped db
93+
# 'test' which we acquired previously
94+
test_standby_func_tab_stats_status('postgres',
95+
$dboid, $tableoid, $funcoid, 'f');
96+
97+
test_standby_db_stats_status('postgres', $dboid, 'f');
98+
99+
100+
## verify that stats persist across graceful restarts on a replica
101+
102+
# NB: Can't test database stats, they're immediately repopulated when
103+
# reconnecting...
104+
$sect = "pre restart";
105+
($dboid, $tableoid, $funcoid) = populate_standby_stats('postgres', 'public');
106+
test_standby_func_tab_stats_status('postgres',
107+
$dboid, $tableoid, $funcoid, 't');
108+
109+
$node_standby->restart();
110+
111+
$sect = "post non-immediate";
112+
113+
test_standby_func_tab_stats_status('postgres',
114+
$dboid, $tableoid, $funcoid, 't');
115+
116+
# but gone after an immediate restart
117+
$node_standby->stop('immediate');
118+
$node_standby->start();
119+
120+
$sect = "post immediate restart";
121+
122+
test_standby_func_tab_stats_status('postgres',
123+
$dboid, $tableoid, $funcoid, 'f');
124+
125+
126+
done_testing();
127+
128+
129+
sub populate_standby_stats
130+
{
131+
my ($connect_db, $schema) = @_;
132+
133+
# create objects on primary
134+
$node_primary->safe_psql($connect_db,
135+
"CREATE TABLE $schema.drop_tab_test1 AS SELECT generate_series(1,100) AS a"
136+
);
137+
$node_primary->safe_psql($connect_db,
138+
"CREATE FUNCTION $schema.drop_func_test1() RETURNS VOID AS 'select 2;' LANGUAGE SQL IMMUTABLE"
139+
);
140+
my $primary_lsn = $node_primary->lsn('flush');
141+
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
142+
143+
# collect object oids
144+
my $dboid = $node_standby->safe_psql($connect_db,
145+
"SELECT oid FROM pg_database WHERE datname = '$connect_db'");
146+
my $tableoid = $node_standby->safe_psql($connect_db,
147+
"SELECT '$schema.drop_tab_test1'::regclass::oid");
148+
my $funcoid = $node_standby->safe_psql($connect_db,
149+
"SELECT '$schema.drop_func_test1()'::regprocedure::oid");
150+
151+
# generate stats on standby
152+
$node_standby->safe_psql($connect_db,
153+
"SELECT * FROM $schema.drop_tab_test1");
154+
$node_standby->safe_psql($connect_db, "SELECT $schema.drop_func_test1()");
155+
156+
return ($dboid, $tableoid, $funcoid);
157+
}
158+
159+
sub drop_function_by_oid
160+
{
161+
my ($connect_db, $funcoid) = @_;
162+
163+
# Get function name from returned oid
164+
my $func_name = $node_primary->safe_psql($connect_db,
165+
"SELECT '$funcoid'::regprocedure");
166+
$node_primary->safe_psql($connect_db, "DROP FUNCTION $func_name");
167+
}
168+
169+
sub drop_table_by_oid
170+
{
171+
my ($connect_db, $tableoid) = @_;
172+
173+
# Get table name from returned oid
174+
my $table_name =
175+
$node_primary->safe_psql($connect_db, "SELECT '$tableoid'::regclass");
176+
$node_primary->safe_psql($connect_db, "DROP TABLE $table_name");
177+
}
178+
179+
sub test_standby_func_tab_stats_status
180+
{
181+
local $Test::Builder::Level = $Test::Builder::Level + 1;
182+
my ($connect_db, $dboid, $tableoid, $funcoid, $present) = @_;
183+
184+
my %expected = (rel => $present, func => $present);
185+
my %stats;
186+
187+
$stats{rel} = $node_standby->safe_psql($connect_db,
188+
"SELECT pg_stat_have_stats('relation', $dboid, $tableoid)");
189+
$stats{func} = $node_standby->safe_psql($connect_db,
190+
"SELECT pg_stat_have_stats('function', $dboid, $funcoid)");
191+
192+
is_deeply(\%stats, \%expected, "$sect: standby stats as expected");
193+
194+
return;
195+
}
196+
197+
sub test_standby_db_stats_status
198+
{
199+
local $Test::Builder::Level = $Test::Builder::Level + 1;
200+
my ($connect_db, $dboid, $present) = @_;
201+
202+
is( $node_standby->safe_psql(
203+
$connect_db, "SELECT pg_stat_have_stats('database', $dboid, 0)"),
204+
$present,
205+
"$sect: standby db stats as expected");
206+
}

0 commit comments

Comments
 (0)