Skip to content

Commit c9e50ce

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 cfd7aad commit c9e50ce

File tree

2 files changed

+214
-197
lines changed

2 files changed

+214
-197
lines changed

src/test/perl/PostgresNode.pm

+148
Original file line numberDiff line numberDiff line change
@@ -2122,6 +2122,154 @@ sub lsn
21222122

21232123
=pod
21242124
2125+
=item $node->write_wal($tli, $lsn, $segment_size, $data)
2126+
2127+
Write some arbitrary data in WAL for the given segment at $lsn (in bytes).
2128+
This should be called while the cluster is not running.
2129+
2130+
Returns the path of the WAL segment written to.
2131+
2132+
=cut
2133+
2134+
sub write_wal
2135+
{
2136+
my ($self, $tli, $lsn, $segment_size, $data) = @_;
2137+
2138+
# Calculate segment number and offset position in segment based on the
2139+
# input LSN.
2140+
my $segment = $lsn / $segment_size;
2141+
my $offset = $lsn % $segment_size;
2142+
my $path =
2143+
sprintf("%s/pg_wal/%08X%08X%08X", $self->data_dir, $tli, 0, $segment);
2144+
2145+
open my $fh, "+<:raw", $path or die "could not open WAL segment $path";
2146+
seek($fh, $offset, SEEK_SET) or die "could not seek WAL segment $path";
2147+
print $fh $data;
2148+
close $fh;
2149+
2150+
return $path;
2151+
}
2152+
2153+
=pod
2154+
2155+
=item $node->emit_wal($size)
2156+
2157+
Emit a WAL record of arbitrary size, using pg_logical_emit_message().
2158+
2159+
Returns the end LSN of the record inserted, in bytes.
2160+
2161+
=cut
2162+
2163+
sub emit_wal
2164+
{
2165+
my ($self, $size) = @_;
2166+
2167+
return int(
2168+
$self->safe_psql(
2169+
'postgres',
2170+
"SELECT pg_logical_emit_message(true, '', repeat('a', $size)) - '0/0'"
2171+
));
2172+
}
2173+
2174+
2175+
# Private routine returning the current insert LSN of a node, in bytes.
2176+
# Used by the routines below in charge of advancing WAL to arbitrary
2177+
# positions. The insert LSN is returned in bytes.
2178+
sub _get_insert_lsn
2179+
{
2180+
my ($self) = @_;
2181+
return int(
2182+
$self->safe_psql(
2183+
'postgres', "SELECT pg_current_wal_insert_lsn() - '0/0'"));
2184+
}
2185+
2186+
=pod
2187+
2188+
=item $node->advance_wal_out_of_record_splitting_zone($wal_block_size)
2189+
2190+
Advance WAL at the end of a page, making sure that we are far away enough
2191+
from the end of a page that we could insert a couple of small records.
2192+
2193+
This inserts a few records of a fixed size, until the threshold gets close
2194+
enough to the end of the WAL page inserting records to.
2195+
2196+
Returns the end LSN up to which WAL has advanced, in bytes.
2197+
2198+
=cut
2199+
2200+
sub advance_wal_out_of_record_splitting_zone
2201+
{
2202+
my ($self, $wal_block_size) = @_;
2203+
2204+
my $page_threshold = $wal_block_size / 4;
2205+
my $end_lsn = $self->_get_insert_lsn();
2206+
my $page_offset = $end_lsn % $wal_block_size;
2207+
while ($page_offset >= $wal_block_size - $page_threshold)
2208+
{
2209+
$self->emit_wal($page_threshold);
2210+
$end_lsn = $self->_get_insert_lsn();
2211+
$page_offset = $end_lsn % $wal_block_size;
2212+
}
2213+
return $end_lsn;
2214+
}
2215+
2216+
=pod
2217+
2218+
=item $node->advance_wal_to_record_splitting_zone($wal_block_size)
2219+
2220+
Advance WAL so close to the end of a page that an XLogRecordHeader would not
2221+
fit on it.
2222+
2223+
Returns the end LSN up to which WAL has advanced, in bytes.
2224+
2225+
=cut
2226+
2227+
sub advance_wal_to_record_splitting_zone
2228+
{
2229+
my ($self, $wal_block_size) = @_;
2230+
2231+
# Size of record header.
2232+
my $RECORD_HEADER_SIZE = 24;
2233+
2234+
my $end_lsn = $self->_get_insert_lsn();
2235+
my $page_offset = $end_lsn % $wal_block_size;
2236+
2237+
# Get fairly close to the end of a page in big steps
2238+
while ($page_offset <= $wal_block_size - 512)
2239+
{
2240+
$self->emit_wal($wal_block_size - $page_offset - 256);
2241+
$end_lsn = $self->_get_insert_lsn();
2242+
$page_offset = $end_lsn % $wal_block_size;
2243+
}
2244+
2245+
# Calibrate our message size so that we can get closer 8 bytes at
2246+
# a time.
2247+
my $message_size = $wal_block_size - 80;
2248+
while ($page_offset <= $wal_block_size - $RECORD_HEADER_SIZE)
2249+
{
2250+
$self->emit_wal($message_size);
2251+
$end_lsn = $self->_get_insert_lsn();
2252+
2253+
my $old_offset = $page_offset;
2254+
$page_offset = $end_lsn % $wal_block_size;
2255+
2256+
# Adjust the message size until it causes 8 bytes changes in
2257+
# offset, enough to be able to split a record header.
2258+
my $delta = $page_offset - $old_offset;
2259+
if ($delta > 8)
2260+
{
2261+
$message_size -= 8;
2262+
}
2263+
elsif ($delta <= 0)
2264+
{
2265+
$message_size += 8;
2266+
}
2267+
}
2268+
return $end_lsn;
2269+
}
2270+
2271+
=pod
2272+
21252273
=item $node->wait_for_catchup(standby_name, mode, target_lsn)
21262274
21272275
Wait for the node with application_name standby_name (usually from node->name,

0 commit comments

Comments
 (0)