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 <melanieplageman@gmail.com>
Author: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/20220303021600.hs34ghqcw6zcokdh@alap3.anarazel.de
This commit is contained in:
Andres Freund 2022-03-21 12:58:51 -07:00
parent a90641eac2
commit 53b9cd20d4
1 changed files with 206 additions and 0 deletions

View File

@ -0,0 +1,206 @@
# Copyright (c) 2021-2022, PostgreSQL Global Development Group
# Tests that standbys:
# - drop stats for objects when the those records are replayed
# - persist stats across graceful restarts
# - discard stats after immediate / crash restarts
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
my $node_primary = PostgreSQL::Test::Cluster->new('primary');
$node_primary->init(allows_streaming => 1);
$node_primary->append_conf('postgresql.conf', "track_functions = 'all'");
$node_primary->start;
my $backup_name = 'my_backup';
$node_primary->backup($backup_name);
my $node_standby = PostgreSQL::Test::Cluster->new('standby');
$node_standby->init_from_backup($node_primary, $backup_name,
has_streaming => 1);
$node_standby->start;
## Test that stats are cleaned up on standby after dropping table or function
my $sect = 'initial';
my ($dboid, $tableoid, $funcoid) =
populate_standby_stats('postgres', 'public');
test_standby_func_tab_stats_status('postgres',
$dboid, $tableoid, $funcoid, 't');
drop_table_by_oid('postgres', $tableoid);
drop_function_by_oid('postgres', $funcoid);
$sect = 'post drop';
my $primary_lsn = $node_primary->lsn('flush');
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
test_standby_func_tab_stats_status('postgres',
$dboid, $tableoid, $funcoid, 'f');
## Test that stats are cleaned up on standby after dropping indirectly
$sect = "schema creation";
$node_primary->safe_psql('postgres', "CREATE SCHEMA drop_schema_test1");
$primary_lsn = $node_primary->lsn('flush');
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
($dboid, $tableoid, $funcoid) =
populate_standby_stats('postgres', 'drop_schema_test1');
test_standby_func_tab_stats_status('postgres',
$dboid, $tableoid, $funcoid, 't');
$node_primary->safe_psql('postgres', "DROP SCHEMA drop_schema_test1 CASCADE");
$sect = "post schema drop";
$primary_lsn = $node_primary->lsn('flush');
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
# verify table and function stats removed from standby
test_standby_func_tab_stats_status('postgres',
$dboid, $tableoid, $funcoid, 'f');
## Test that stats are cleaned up on standby after dropping database
$sect = "createdb";
$node_primary->safe_psql('postgres', "CREATE DATABASE test");
$primary_lsn = $node_primary->lsn('flush');
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
($dboid, $tableoid, $funcoid) = populate_standby_stats('test', 'public');
# verify stats are present
test_standby_func_tab_stats_status('test', $dboid, $tableoid, $funcoid, 't');
test_standby_db_stats_status('test', $dboid, 't');
$node_primary->safe_psql('postgres', "DROP DATABASE test");
$sect = "post dropdb";
$primary_lsn = $node_primary->lsn('flush');
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
# Test that the stats were cleaned up on standby
# Note that this connects to 'postgres' but provides the dboid of dropped db
# 'test' which we acquired previously
test_standby_func_tab_stats_status('postgres',
$dboid, $tableoid, $funcoid, 'f');
test_standby_db_stats_status('postgres', $dboid, 'f');
## verify that stats persist across graceful restarts on a replica
# NB: Can't test database stats, they're immediately repopulated when
# reconnecting...
$sect = "pre restart";
($dboid, $tableoid, $funcoid) = populate_standby_stats('postgres', 'public');
test_standby_func_tab_stats_status('postgres',
$dboid, $tableoid, $funcoid, 't');
$node_standby->restart();
$sect = "post non-immediate";
test_standby_func_tab_stats_status('postgres',
$dboid, $tableoid, $funcoid, 't');
# but gone after an immediate restart
$node_standby->stop('immediate');
$node_standby->start();
$sect = "post immediate restart";
test_standby_func_tab_stats_status('postgres',
$dboid, $tableoid, $funcoid, 'f');
done_testing();
sub populate_standby_stats
{
my ($connect_db, $schema) = @_;
# create objects on primary
$node_primary->safe_psql($connect_db,
"CREATE TABLE $schema.drop_tab_test1 AS SELECT generate_series(1,100) AS a"
);
$node_primary->safe_psql($connect_db,
"CREATE FUNCTION $schema.drop_func_test1() RETURNS VOID AS 'select 2;' LANGUAGE SQL IMMUTABLE"
);
my $primary_lsn = $node_primary->lsn('flush');
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
# collect object oids
my $dboid = $node_standby->safe_psql($connect_db,
"SELECT oid FROM pg_database WHERE datname = '$connect_db'");
my $tableoid = $node_standby->safe_psql($connect_db,
"SELECT '$schema.drop_tab_test1'::regclass::oid");
my $funcoid = $node_standby->safe_psql($connect_db,
"SELECT '$schema.drop_func_test1()'::regprocedure::oid");
# generate stats on standby
$node_standby->safe_psql($connect_db,
"SELECT * FROM $schema.drop_tab_test1");
$node_standby->safe_psql($connect_db, "SELECT $schema.drop_func_test1()");
return ($dboid, $tableoid, $funcoid);
}
sub drop_function_by_oid
{
my ($connect_db, $funcoid) = @_;
# Get function name from returned oid
my $func_name = $node_primary->safe_psql($connect_db,
"SELECT '$funcoid'::regprocedure");
$node_primary->safe_psql($connect_db, "DROP FUNCTION $func_name");
}
sub drop_table_by_oid
{
my ($connect_db, $tableoid) = @_;
# Get table name from returned oid
my $table_name =
$node_primary->safe_psql($connect_db, "SELECT '$tableoid'::regclass");
$node_primary->safe_psql($connect_db, "DROP TABLE $table_name");
}
sub test_standby_func_tab_stats_status
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
my ($connect_db, $dboid, $tableoid, $funcoid, $present) = @_;
my %expected = (rel => $present, func => $present);
my %stats;
$stats{rel} = $node_standby->safe_psql($connect_db,
"SELECT pg_stat_have_stats('relation', $dboid, $tableoid)");
$stats{func} = $node_standby->safe_psql($connect_db,
"SELECT pg_stat_have_stats('function', $dboid, $funcoid)");
is_deeply(\%stats, \%expected, "$sect: standby stats as expected");
return;
}
sub test_standby_db_stats_status
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
my ($connect_db, $dboid, $present) = @_;
is( $node_standby->safe_psql(
$connect_db, "SELECT pg_stat_have_stats('database', $dboid, 0)"),
$present,
"$sect: standby db stats as expected");
}