postgresql/src/test/recovery/t/023_pitr_prepared_xact.pl

92 lines
3.0 KiB
Perl

# Copyright (c) 2021-2023, PostgreSQL Global Development Group
# Test for point-in-time recovery (PITR) with prepared transactions
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
use File::Compare;
# Initialize and start primary node with WAL archiving
my $node_primary = PostgreSQL::Test::Cluster->new('primary');
$node_primary->init(has_archiving => 1, allows_streaming => 1);
$node_primary->append_conf(
'postgresql.conf', qq{
max_prepared_transactions = 10});
$node_primary->start;
# Take backup
my $backup_name = 'my_backup';
$node_primary->backup($backup_name);
# Initialize node for PITR targeting a very specific restore point, just
# after a PREPARE TRANSACTION is issued so as we finish with a promoted
# node where this 2PC transaction needs an explicit COMMIT PREPARED.
my $node_pitr = PostgreSQL::Test::Cluster->new('node_pitr');
$node_pitr->init_from_backup(
$node_primary, $backup_name,
standby => 0,
has_restoring => 1);
$node_pitr->append_conf(
'postgresql.conf', qq{
recovery_target_name = 'rp'
recovery_target_action = 'promote'});
# Workload with a prepared transaction and the target restore point.
$node_primary->psql(
'postgres', qq{
CREATE TABLE foo(i int);
BEGIN;
INSERT INTO foo VALUES(1);
PREPARE TRANSACTION 'fooinsert';
SELECT pg_create_restore_point('rp');
INSERT INTO foo VALUES(2);
});
# Find next WAL segment to be archived
my $walfile_to_be_archived = $node_primary->safe_psql('postgres',
"SELECT pg_walfile_name(pg_current_wal_lsn());");
# Make WAL segment eligible for archival
$node_primary->safe_psql('postgres', 'SELECT pg_switch_wal()');
# Wait until the WAL segment has been archived.
my $archive_wait_query =
"SELECT '$walfile_to_be_archived' <= last_archived_wal FROM pg_stat_archiver;";
$node_primary->poll_query_until('postgres', $archive_wait_query)
or die "Timed out while waiting for WAL segment to be archived";
my $last_archived_wal_file = $walfile_to_be_archived;
# Now start the PITR node.
$node_pitr->start;
# Wait until the PITR node exits recovery.
$node_pitr->poll_query_until('postgres', "SELECT pg_is_in_recovery() = 'f';")
or die "Timed out while waiting for PITR promotion";
# Commit the prepared transaction in the latest timeline and check its
# result. There should only be one row in the table, coming from the
# prepared transaction. The row from the INSERT after the restore point
# should not show up, since our recovery target was older than the second
# INSERT done.
$node_pitr->psql('postgres', qq{COMMIT PREPARED 'fooinsert';});
my $result = $node_pitr->safe_psql('postgres', "SELECT * FROM foo;");
is($result, qq{1}, "check table contents after COMMIT PREPARED");
# Insert more data and do a checkpoint. These should be generated on the
# timeline chosen after the PITR promotion.
$node_pitr->psql(
'postgres', qq{
INSERT INTO foo VALUES(3);
CHECKPOINT;
});
# Enforce recovery, the checkpoint record generated previously should
# still be found.
$node_pitr->stop('immediate');
$node_pitr->start;
done_testing();