Skip to content

Commit 32a18cc

Browse files
committed
Move routines to manipulate WAL into PostgreSQL::Test::Cluster
These facilities were originally in the recovery TAP test 039_end_of_wal.pl. A follow-up bug fix with a TAP test doing similar WAL manipulations requires them, and all these had better not be duplicated due to their complexity. The routine names are tweaked to use "wal" more consistently, similarly to the existing "advance_wal". In v14 and v13, the new routines are moved to PostgresNode.pm. 039_end_of_wal.pl is updated to use the refactored routines, without changing its coverage. Reviewed-by: Alexander Kukushkin Discussion: https://postgr.es/m/CAFh8B=mozC+e1wGJq0H=0O65goZju+6ab5AU7DEWCSUA2OtwDg@mail.gmail.com Backpatch-through: 13
1 parent d5221c4 commit 32a18cc

File tree

2 files changed

+214
-191
lines changed

2 files changed

+214
-191
lines changed

src/test/perl/PostgreSQL/Test/Cluster.pm

+148
Original file line numberDiff line numberDiff line change
@@ -2954,6 +2954,154 @@ sub lsn
29542954

29552955
=pod
29562956
2957+
=item $node->write_wal($tli, $lsn, $segment_size, $data)
2958+
2959+
Write some arbitrary data in WAL for the given segment at $lsn (in bytes).
2960+
This should be called while the cluster is not running.
2961+
2962+
Returns the path of the WAL segment written to.
2963+
2964+
=cut
2965+
2966+
sub write_wal
2967+
{
2968+
my ($self, $tli, $lsn, $segment_size, $data) = @_;
2969+
2970+
# Calculate segment number and offset position in segment based on the
2971+
# input LSN.
2972+
my $segment = $lsn / $segment_size;
2973+
my $offset = $lsn % $segment_size;
2974+
my $path =
2975+
sprintf("%s/pg_wal/%08X%08X%08X", $self->data_dir, $tli, 0, $segment);
2976+
2977+
open my $fh, "+<:raw", $path or die "could not open WAL segment $path";
2978+
seek($fh, $offset, SEEK_SET) or die "could not seek WAL segment $path";
2979+
print $fh $data;
2980+
close $fh;
2981+
2982+
return $path;
2983+
}
2984+
2985+
=pod
2986+
2987+
=item $node->emit_wal($size)
2988+
2989+
Emit a WAL record of arbitrary size, using pg_logical_emit_message().
2990+
2991+
Returns the end LSN of the record inserted, in bytes.
2992+
2993+
=cut
2994+
2995+
sub emit_wal
2996+
{
2997+
my ($self, $size) = @_;
2998+
2999+
return int(
3000+
$self->safe_psql(
3001+
'postgres',
3002+
"SELECT pg_logical_emit_message(true, '', repeat('a', $size)) - '0/0'"
3003+
));
3004+
}
3005+
3006+
3007+
# Private routine returning the current insert LSN of a node, in bytes.
3008+
# Used by the routines below in charge of advancing WAL to arbitrary
3009+
# positions. The insert LSN is returned in bytes.
3010+
sub _get_insert_lsn
3011+
{
3012+
my ($self) = @_;
3013+
return int(
3014+
$self->safe_psql(
3015+
'postgres', "SELECT pg_current_wal_insert_lsn() - '0/0'"));
3016+
}
3017+
3018+
=pod
3019+
3020+
=item $node->advance_wal_out_of_record_splitting_zone($wal_block_size)
3021+
3022+
Advance WAL at the end of a page, making sure that we are far away enough
3023+
from the end of a page that we could insert a couple of small records.
3024+
3025+
This inserts a few records of a fixed size, until the threshold gets close
3026+
enough to the end of the WAL page inserting records to.
3027+
3028+
Returns the end LSN up to which WAL has advanced, in bytes.
3029+
3030+
=cut
3031+
3032+
sub advance_wal_out_of_record_splitting_zone
3033+
{
3034+
my ($self, $wal_block_size) = @_;
3035+
3036+
my $page_threshold = $wal_block_size / 4;
3037+
my $end_lsn = $self->_get_insert_lsn();
3038+
my $page_offset = $end_lsn % $wal_block_size;
3039+
while ($page_offset >= $wal_block_size - $page_threshold)
3040+
{
3041+
$self->emit_wal($page_threshold);
3042+
$end_lsn = $self->_get_insert_lsn();
3043+
$page_offset = $end_lsn % $wal_block_size;
3044+
}
3045+
return $end_lsn;
3046+
}
3047+
3048+
=pod
3049+
3050+
=item $node->advance_wal_to_record_splitting_zone($wal_block_size)
3051+
3052+
Advance WAL so close to the end of a page that an XLogRecordHeader would not
3053+
fit on it.
3054+
3055+
Returns the end LSN up to which WAL has advanced, in bytes.
3056+
3057+
=cut
3058+
3059+
sub advance_wal_to_record_splitting_zone
3060+
{
3061+
my ($self, $wal_block_size) = @_;
3062+
3063+
# Size of record header.
3064+
my $RECORD_HEADER_SIZE = 24;
3065+
3066+
my $end_lsn = $self->_get_insert_lsn();
3067+
my $page_offset = $end_lsn % $wal_block_size;
3068+
3069+
# Get fairly close to the end of a page in big steps
3070+
while ($page_offset <= $wal_block_size - 512)
3071+
{
3072+
$self->emit_wal($wal_block_size - $page_offset - 256);
3073+
$end_lsn = $self->_get_insert_lsn();
3074+
$page_offset = $end_lsn % $wal_block_size;
3075+
}
3076+
3077+
# Calibrate our message size so that we can get closer 8 bytes at
3078+
# a time.
3079+
my $message_size = $wal_block_size - 80;
3080+
while ($page_offset <= $wal_block_size - $RECORD_HEADER_SIZE)
3081+
{
3082+
$self->emit_wal($message_size);
3083+
$end_lsn = $self->_get_insert_lsn();
3084+
3085+
my $old_offset = $page_offset;
3086+
$page_offset = $end_lsn % $wal_block_size;
3087+
3088+
# Adjust the message size until it causes 8 bytes changes in
3089+
# offset, enough to be able to split a record header.
3090+
my $delta = $page_offset - $old_offset;
3091+
if ($delta > 8)
3092+
{
3093+
$message_size -= 8;
3094+
}
3095+
elsif ($delta <= 0)
3096+
{
3097+
$message_size += 8;
3098+
}
3099+
}
3100+
return $end_lsn;
3101+
}
3102+
3103+
=pod
3104+
29573105
=item $node->check_extension(extension_name)
29583106
29593107
Scan pg_available_extensions to check that an extension is available in an

0 commit comments

Comments
 (0)