=pod
+=item $node->write_wal($tli, $lsn, $segment_size, $data)
+
+Write some arbitrary data in WAL for the given segment at $lsn (in bytes).
+This should be called while the cluster is not running.
+
+Returns the path of the WAL segment written to.
+
+=cut
+
+sub write_wal
+{
+ my ($self, $tli, $lsn, $segment_size, $data) = @_;
+
+ # Calculate segment number and offset position in segment based on the
+ # input LSN.
+ my $segment = $lsn / $segment_size;
+ my $offset = $lsn % $segment_size;
+ my $path =
+ sprintf("%s/pg_wal/%08X%08X%08X", $self->data_dir, $tli, 0, $segment);
+
+ open my $fh, "+<:raw", $path or die "could not open WAL segment $path";
+ seek($fh, $offset, SEEK_SET) or die "could not seek WAL segment $path";
+ print $fh $data;
+ close $fh;
+
+ return $path;
+}
+
+=pod
+
+=item $node->emit_wal($size)
+
+Emit a WAL record of arbitrary size, using pg_logical_emit_message().
+
+Returns the end LSN of the record inserted, in bytes.
+
+=cut
+
+sub emit_wal
+{
+ my ($self, $size) = @_;
+
+ return int(
+ $self->safe_psql(
+ 'postgres',
+ "SELECT pg_logical_emit_message(true, '', repeat('a', $size)) - '0/0'"
+ ));
+}
+
+
+# Private routine returning the current insert LSN of a node, in bytes.
+# Used by the routines below in charge of advancing WAL to arbitrary
+# positions. The insert LSN is returned in bytes.
+sub _get_insert_lsn
+{
+ my ($self) = @_;
+ return int(
+ $self->safe_psql(
+ 'postgres', "SELECT pg_current_wal_insert_lsn() - '0/0'"));
+}
+
+=pod
+
+=item $node->advance_wal_out_of_record_splitting_zone($wal_block_size)
+
+Advance WAL at the end of a page, making sure that we are far away enough
+from the end of a page that we could insert a couple of small records.
+
+This inserts a few records of a fixed size, until the threshold gets close
+enough to the end of the WAL page inserting records to.
+
+Returns the end LSN up to which WAL has advanced, in bytes.
+
+=cut
+
+sub advance_wal_out_of_record_splitting_zone
+{
+ my ($self, $wal_block_size) = @_;
+
+ my $page_threshold = $wal_block_size / 4;
+ my $end_lsn = $self->_get_insert_lsn();
+ my $page_offset = $end_lsn % $wal_block_size;
+ while ($page_offset >= $wal_block_size - $page_threshold)
+ {
+ $self->emit_wal($page_threshold);
+ $end_lsn = $self->_get_insert_lsn();
+ $page_offset = $end_lsn % $wal_block_size;
+ }
+ return $end_lsn;
+}
+
+=pod
+
+=item $node->advance_wal_to_record_splitting_zone($wal_block_size)
+
+Advance WAL so close to the end of a page that an XLogRecordHeader would not
+fit on it.
+
+Returns the end LSN up to which WAL has advanced, in bytes.
+
+=cut
+
+sub advance_wal_to_record_splitting_zone
+{
+ my ($self, $wal_block_size) = @_;
+
+ # Size of record header.
+ my $RECORD_HEADER_SIZE = 24;
+
+ my $end_lsn = $self->_get_insert_lsn();
+ my $page_offset = $end_lsn % $wal_block_size;
+
+ # Get fairly close to the end of a page in big steps
+ while ($page_offset <= $wal_block_size - 512)
+ {
+ $self->emit_wal($wal_block_size - $page_offset - 256);
+ $end_lsn = $self->_get_insert_lsn();
+ $page_offset = $end_lsn % $wal_block_size;
+ }
+
+ # Calibrate our message size so that we can get closer 8 bytes at
+ # a time.
+ my $message_size = $wal_block_size - 80;
+ while ($page_offset <= $wal_block_size - $RECORD_HEADER_SIZE)
+ {
+ $self->emit_wal($message_size);
+ $end_lsn = $self->_get_insert_lsn();
+
+ my $old_offset = $page_offset;
+ $page_offset = $end_lsn % $wal_block_size;
+
+ # Adjust the message size until it causes 8 bytes changes in
+ # offset, enough to be able to split a record header.
+ my $delta = $page_offset - $old_offset;
+ if ($delta > 8)
+ {
+ $message_size -= 8;
+ }
+ elsif ($delta <= 0)
+ {
+ $message_size += 8;
+ }
+ }
+ return $end_lsn;
+}
+
+=pod
+
=item $node->check_extension(extension_name)
Scan pg_available_extensions to check that an extension is available in an
# we need to know the endianness to do that.
my $BIG_ENDIAN = pack("L", 0x12345678) eq pack("N", 0x12345678);
-# Header size of record header.
-my $RECORD_HEADER_SIZE = 24;
-
# Fields retrieved from code headers.
my @scan_result = scan_server_header('access/xlog_internal.h',
'#define\s+XLOG_PAGE_MAGIC\s+(\w+)');
my $WAL_BLOCK_SIZE;
my $TLI;
-# Build path of a WAL segment.
-sub wal_segment_path
-{
- my $node = shift;
- my $tli = shift;
- my $segment = shift;
- my $wal_path =
- sprintf("%s/pg_wal/%08X%08X%08X", $node->data_dir, $tli, 0, $segment);
- return $wal_path;
-}
-
-# Calculate from a LSN (in bytes) its segment number and its offset.
-sub lsn_to_segment_and_offset
-{
- my $lsn = shift;
- return ($lsn / $WAL_SEGMENT_SIZE, $lsn % $WAL_SEGMENT_SIZE);
-}
-
-# Write some arbitrary data in WAL for the given segment at LSN.
-# This should be called while the cluster is not running.
-sub write_wal
-{
- my $node = shift;
- my $tli = shift;
- my $lsn = shift;
- my $data = shift;
-
- my ($segment, $offset) = lsn_to_segment_and_offset($lsn);
- my $path = wal_segment_path($node, $tli, $segment);
-
- open my $fh, "+<:raw", $path or die;
- seek($fh, $offset, SEEK_SET) or die;
- print $fh $data;
- close $fh;
-}
-
-# Emit a WAL record of arbitrary size. Returns the end LSN of the
-# record inserted, in bytes.
-sub emit_message
-{
- my $node = shift;
- my $size = shift;
- return int(
- $node->safe_psql(
- 'postgres',
- "SELECT pg_logical_emit_message(true, '', repeat('a', $size)) - '0/0'"
- ));
-}
-
-# Get the current insert LSN of a node, in bytes.
-sub get_insert_lsn
-{
- my $node = shift;
- return int(
- $node->safe_psql(
- 'postgres', "SELECT pg_current_wal_insert_lsn() - '0/0'"));
-}
-
# Get GUC value, converted to an int.
sub get_int_setting
{
$BIG_ENDIAN ? $xlp_pageaddr : 0, $xlp_rem_len);
}
-# Make sure we are far away enough from the end of a page that we could insert
-# a couple of small records. This inserts a few records of a fixed size, until
-# the threshold gets close enough to the end of the WAL page inserting records
-# to.
-sub advance_out_of_record_splitting_zone
-{
- my $node = shift;
-
- my $page_threshold = $WAL_BLOCK_SIZE / 4;
- my $end_lsn = get_insert_lsn($node);
- my $page_offset = $end_lsn % $WAL_BLOCK_SIZE;
- while ($page_offset >= $WAL_BLOCK_SIZE - $page_threshold)
- {
- emit_message($node, $page_threshold);
- $end_lsn = get_insert_lsn($node);
- $page_offset = $end_lsn % $WAL_BLOCK_SIZE;
- }
- return $end_lsn;
-}
-
-# Advance so close to the end of a page that an XLogRecordHeader would not
-# fit on it.
-sub advance_to_record_splitting_zone
-{
- my $node = shift;
-
- my $end_lsn = get_insert_lsn($node);
- my $page_offset = $end_lsn % $WAL_BLOCK_SIZE;
-
- # Get fairly close to the end of a page in big steps
- while ($page_offset <= $WAL_BLOCK_SIZE - 512)
- {
- emit_message($node, $WAL_BLOCK_SIZE - $page_offset - 256);
- $end_lsn = get_insert_lsn($node);
- $page_offset = $end_lsn % $WAL_BLOCK_SIZE;
- }
-
- # Calibrate our message size so that we can get closer 8 bytes at
- # a time.
- my $message_size = $WAL_BLOCK_SIZE - 80;
- while ($page_offset <= $WAL_BLOCK_SIZE - $RECORD_HEADER_SIZE)
- {
- emit_message($node, $message_size);
- $end_lsn = get_insert_lsn($node);
-
- my $old_offset = $page_offset;
- $page_offset = $end_lsn % $WAL_BLOCK_SIZE;
-
- # Adjust the message size until it causes 8 bytes changes in
- # offset, enough to be able to split a record header.
- my $delta = $page_offset - $old_offset;
- if ($delta > 8)
- {
- $message_size -= 8;
- }
- elsif ($delta <= 0)
- {
- $message_size += 8;
- }
- }
- return $end_lsn;
-}
-
# Setup a new node. The configuration chosen here minimizes the number
# of arbitrary records that could get generated in a cluster. Enlarging
# checkpoint_timeout avoids noise with checkpoint activity. wal_level
###########################################################################
# xl_tot_len is 0 (a common case, we hit trailing zeroes).
-emit_message($node, 0);
-$end_lsn = advance_out_of_record_splitting_zone($node);
+$node->emit_wal(0);
+$end_lsn = $node->advance_wal_out_of_record_splitting_zone($WAL_BLOCK_SIZE);
$node->stop('immediate');
my $log_size = -s $node->logfile;
$node->start;
"xl_tot_len zero");
# xl_tot_len is < 24 (presumably recycled garbage).
-emit_message($node, 0);
-$end_lsn = advance_out_of_record_splitting_zone($node);
+$node->emit_wal(0);
+$end_lsn = $node->advance_wal_out_of_record_splitting_zone($WAL_BLOCK_SIZE);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn, build_record_header(23));
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE, build_record_header(23));
$log_size = -s $node->logfile;
$node->start;
ok( $node->log_contains(
# xl_tot_len in final position, not big enough to span into a new page but
# also not eligible for regular record header validation
-emit_message($node, 0);
-$end_lsn = advance_to_record_splitting_zone($node);
+$node->emit_wal(0);
+$end_lsn = $node->advance_wal_to_record_splitting_zone($WAL_BLOCK_SIZE);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn, build_record_header(1));
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE, build_record_header(1));
$log_size = -s $node->logfile;
$node->start;
ok( $node->log_contains(
"xl_tot_len short at end-of-page");
# Need more pages, but xl_prev check fails first.
-emit_message($node, 0);
-$end_lsn = advance_out_of_record_splitting_zone($node);
+$node->emit_wal(0);
+$end_lsn = $node->advance_wal_out_of_record_splitting_zone($WAL_BLOCK_SIZE);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn,
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE,
build_record_header(2 * 1024 * 1024 * 1024, 0, 0xdeadbeef));
$log_size = -s $node->logfile;
$node->start;
"xl_prev bad");
# xl_crc check fails.
-emit_message($node, 0);
-advance_out_of_record_splitting_zone($node);
-$end_lsn = emit_message($node, 10);
+$node->emit_wal(0);
+$node->advance_wal_out_of_record_splitting_zone($WAL_BLOCK_SIZE);
+$end_lsn = $node->emit_wal(10);
$node->stop('immediate');
# Corrupt a byte in that record, breaking its CRC.
-write_wal($node, $TLI, $end_lsn - 8, '!');
+$node->write_wal($TLI, $end_lsn - 8, $WAL_SEGMENT_SIZE, '!');
$log_size = -s $node->logfile;
$node->start;
ok( $node->log_contains(
# written to WAL.
# Good xl_prev, we hit zero page next (zero magic).
-emit_message($node, 0);
-$prev_lsn = advance_out_of_record_splitting_zone($node);
-$end_lsn = emit_message($node, 0);
+$node->emit_wal(0);
+$prev_lsn = $node->advance_wal_out_of_record_splitting_zone($WAL_BLOCK_SIZE);
+$end_lsn = $node->emit_wal(0);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn,
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE,
build_record_header(2 * 1024 * 1024 * 1024, 0, $prev_lsn));
$log_size = -s $node->logfile;
$node->start;
"xlp_magic zero");
# Good xl_prev, we hit garbage page next (bad magic).
-emit_message($node, 0);
-$prev_lsn = advance_out_of_record_splitting_zone($node);
-$end_lsn = emit_message($node, 0);
+$node->emit_wal(0);
+$prev_lsn = $node->advance_wal_out_of_record_splitting_zone($WAL_BLOCK_SIZE);
+$end_lsn = $node->emit_wal(0);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn,
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE,
build_record_header(2 * 1024 * 1024 * 1024, 0, $prev_lsn));
-write_wal(
- $node, $TLI,
- start_of_next_page($end_lsn),
- build_page_header(0xcafe, 0, 1, 0));
+$node->write_wal($TLI, start_of_next_page($end_lsn),
+ $WAL_SEGMENT_SIZE, build_page_header(0xcafe, 0, 1, 0));
$log_size = -s $node->logfile;
$node->start;
ok($node->log_contains("invalid magic number CAFE .* LSN .*", $log_size),
# Good xl_prev, we hit typical recycled page (good xlp_magic, bad
# xlp_pageaddr).
-emit_message($node, 0);
-$prev_lsn = advance_out_of_record_splitting_zone($node);
-$end_lsn = emit_message($node, 0);
+$node->emit_wal(0);
+$prev_lsn = $node->advance_wal_out_of_record_splitting_zone($WAL_BLOCK_SIZE);
+$end_lsn = $node->emit_wal(0);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn,
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE,
build_record_header(2 * 1024 * 1024 * 1024, 0, $prev_lsn));
-write_wal(
- $node, $TLI,
- start_of_next_page($end_lsn),
- build_page_header($XLP_PAGE_MAGIC, 0, 1, 0xbaaaaaad));
+$node->write_wal($TLI, start_of_next_page($end_lsn),
+ $WAL_SEGMENT_SIZE, build_page_header($XLP_PAGE_MAGIC, 0, 1, 0xbaaaaaad));
$log_size = -s $node->logfile;
$node->start;
ok( $node->log_contains(
"xlp_pageaddr bad");
# Good xl_prev, xlp_magic, xlp_pageaddr, but bogus xlp_info.
-emit_message($node, 0);
-$prev_lsn = advance_out_of_record_splitting_zone($node);
-$end_lsn = emit_message($node, 0);
+$node->emit_wal(0);
+$prev_lsn = $node->advance_wal_out_of_record_splitting_zone($WAL_BLOCK_SIZE);
+$end_lsn = $node->emit_wal(0);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn,
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE,
build_record_header(2 * 1024 * 1024 * 1024, 42, $prev_lsn));
-write_wal(
- $node, $TLI,
+$node->write_wal(
+ $TLI,
start_of_next_page($end_lsn),
+ $WAL_SEGMENT_SIZE,
build_page_header(
$XLP_PAGE_MAGIC, 0x1234, 1, start_of_next_page($end_lsn)));
$log_size = -s $node->logfile;
# Good xl_prev, xlp_magic, xlp_pageaddr, but xlp_info doesn't mention
# continuation record.
-emit_message($node, 0);
-$prev_lsn = advance_out_of_record_splitting_zone($node);
-$end_lsn = emit_message($node, 0);
+$node->emit_wal(0);
+$prev_lsn = $node->advance_wal_out_of_record_splitting_zone($WAL_BLOCK_SIZE);
+$end_lsn = $node->emit_wal(0);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn,
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE,
build_record_header(2 * 1024 * 1024 * 1024, 42, $prev_lsn));
-write_wal(
- $node, $TLI,
- start_of_next_page($end_lsn),
+$node->write_wal($TLI, start_of_next_page($end_lsn),
+ $WAL_SEGMENT_SIZE,
build_page_header($XLP_PAGE_MAGIC, 0, 1, start_of_next_page($end_lsn)));
$log_size = -s $node->logfile;
$node->start;
# Good xl_prev, xlp_magic, xlp_pageaddr, xlp_info but xlp_rem_len doesn't add
# up.
-emit_message($node, 0);
-$prev_lsn = advance_out_of_record_splitting_zone($node);
-$end_lsn = emit_message($node, 0);
+$node->emit_wal(0);
+$prev_lsn = $node->advance_wal_out_of_record_splitting_zone($WAL_BLOCK_SIZE);
+$end_lsn = $node->emit_wal(0);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn,
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE,
build_record_header(2 * 1024 * 1024 * 1024, 42, $prev_lsn));
-write_wal(
- $node, $TLI,
+$node->write_wal(
+ $TLI,
start_of_next_page($end_lsn),
+ $WAL_SEGMENT_SIZE,
build_page_header(
$XLP_PAGE_MAGIC, $XLP_FIRST_IS_CONTRECORD,
1, start_of_next_page($end_lsn),
###########################################################################
# xl_prev is bad and xl_tot_len is too big, but we'll check xlp_magic first.
-emit_message($node, 0);
-$end_lsn = advance_to_record_splitting_zone($node);
+$node->emit_wal(0);
+$end_lsn = $node->advance_wal_to_record_splitting_zone($WAL_BLOCK_SIZE);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn,
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE,
build_record_header(2 * 1024 * 1024 * 1024, 0, 0xdeadbeef));
$log_size = -s $node->logfile;
$node->start;
"xlp_magic zero (split record header)");
# And we'll also check xlp_pageaddr before any header checks.
-emit_message($node, 0);
-$end_lsn = advance_to_record_splitting_zone($node);
+$node->emit_wal(0);
+$end_lsn = $node->advance_wal_to_record_splitting_zone($WAL_BLOCK_SIZE);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn,
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE,
build_record_header(2 * 1024 * 1024 * 1024, 0, 0xdeadbeef));
-write_wal(
- $node, $TLI,
+$node->write_wal(
+ $TLI,
start_of_next_page($end_lsn),
+ $WAL_SEGMENT_SIZE,
build_page_header(
$XLP_PAGE_MAGIC, $XLP_FIRST_IS_CONTRECORD, 1, 0xbaaaaaad));
$log_size = -s $node->logfile;
# We'll also discover that xlp_rem_len doesn't add up before any
# header checks,
-emit_message($node, 0);
-$end_lsn = advance_to_record_splitting_zone($node);
+$node->emit_wal(0);
+$end_lsn = $node->advance_wal_to_record_splitting_zone($WAL_BLOCK_SIZE);
$node->stop('immediate');
-write_wal($node, $TLI, $end_lsn,
+$node->write_wal($TLI, $end_lsn, $WAL_SEGMENT_SIZE,
build_record_header(2 * 1024 * 1024 * 1024, 0, 0xdeadbeef));
-write_wal(
- $node, $TLI,
+$node->write_wal(
+ $TLI,
start_of_next_page($end_lsn),
+ $WAL_SEGMENT_SIZE,
build_page_header(
$XLP_PAGE_MAGIC, $XLP_FIRST_IS_CONTRECORD,
1, start_of_next_page($end_lsn),