mirror of https://github.com/postgres/postgres
This option extracts (potentially decompressing) full-page images included in WAL records into a given target directory. These images are subject to the same filtering rules as the normal display of the WAL records, hence with --relation one can for example extract only the FPIs issued on the relation defined. By default, the records are printed or their stats computed (--stats), using --quiet would only save the images without any output generated. This is a tool aimed mostly for very experienced users, useful for fixing page-level corruption or just analyzing the past state of a page, and there were no easy way to do that with the in-core tools up to now when looking at WAL. Each block is saved in a separate file, to ease their manipulation, with the file respecting <lsn>.<ts>.<db>.<rel>.<blk>_<fork> with as format. For instance, 00000000-010000C0.1663.1.6117.123_main refers to: - WAL record LSN in hexa format (00000000-010000C0). - Tablespace OID (1663). - Database OID (1). - Relfilenode (6117). - Block number (123). - Fork name of the file this block came from (_main). Author: David Christensen Reviewed-by: Sho Kato, Justin Pryzby, Bharath Rupireddy, Matthias van de Meent Discussion: https://postgr.es/m/CAOxo6XKjQb2bMSBRpePf3ZpzfNTwjQUc4Tafh21=jzjX6bX8CA@mail.gmail.compull/111/head
parent
5de94a041e
commit
d497093cbe
@ -0,0 +1,111 @@ |
|||||||
|
|
||||||
|
# Copyright (c) 2022, PostgreSQL Global Development Group |
||||||
|
|
||||||
|
use strict; |
||||||
|
use warnings; |
||||||
|
use File::Basename; |
||||||
|
use PostgreSQL::Test::Cluster; |
||||||
|
use PostgreSQL::Test::RecursiveCopy; |
||||||
|
use PostgreSQL::Test::Utils; |
||||||
|
use Test::More; |
||||||
|
|
||||||
|
my ($blocksize, $walfile_name); |
||||||
|
|
||||||
|
# Function to extract the LSN from the given block structure |
||||||
|
sub get_block_lsn |
||||||
|
{ |
||||||
|
my $path = shift; |
||||||
|
my $blocksize = shift; |
||||||
|
my $block; |
||||||
|
|
||||||
|
open my $fh, '<', $path or die "couldn't open file: $path\n"; |
||||||
|
die "could not read block\n" |
||||||
|
if $blocksize != read($fh, $block, $blocksize); |
||||||
|
my ($lsn_hi, $lsn_lo) = unpack('LL', $block); |
||||||
|
|
||||||
|
$lsn_hi = sprintf('%08X', $lsn_hi); |
||||||
|
$lsn_lo = sprintf('%08X', $lsn_lo); |
||||||
|
|
||||||
|
return ($lsn_hi, $lsn_lo); |
||||||
|
} |
||||||
|
|
||||||
|
my $node = PostgreSQL::Test::Cluster->new('main'); |
||||||
|
$node->init; |
||||||
|
$node->append_conf( |
||||||
|
'postgresql.conf', q{ |
||||||
|
wal_level = 'replica' |
||||||
|
max_wal_senders = 4 |
||||||
|
}); |
||||||
|
$node->start; |
||||||
|
|
||||||
|
# Generate data/WAL to examine that will have full pages in them. |
||||||
|
$node->safe_psql( |
||||||
|
'postgres', |
||||||
|
"SELECT 'init' FROM pg_create_physical_replication_slot('regress_pg_waldump_slot', true, false); |
||||||
|
CREATE TABLE test_table AS SELECT generate_series(1,100) a; |
||||||
|
-- Force FPWs on the next writes. |
||||||
|
CHECKPOINT; |
||||||
|
UPDATE test_table SET a = a + 1; |
||||||
|
"); |
||||||
|
|
||||||
|
($walfile_name, $blocksize) = split '\|' => $node->safe_psql('postgres', |
||||||
|
"SELECT pg_walfile_name(pg_switch_wal()), current_setting('block_size')"); |
||||||
|
|
||||||
|
# Get the relation node, etc for the new table |
||||||
|
my $relation = $node->safe_psql( |
||||||
|
'postgres', |
||||||
|
q{SELECT format( |
||||||
|
'%s/%s/%s', |
||||||
|
CASE WHEN reltablespace = 0 THEN dattablespace ELSE reltablespace END, |
||||||
|
pg_database.oid, |
||||||
|
pg_relation_filenode(pg_class.oid)) |
||||||
|
FROM pg_class, pg_database |
||||||
|
WHERE relname = 'test_table' AND |
||||||
|
datname = current_database()} |
||||||
|
); |
||||||
|
|
||||||
|
my $walfile = $node->data_dir . '/pg_wal/' . $walfile_name; |
||||||
|
my $tmp_folder = PostgreSQL::Test::Utils::tempdir; |
||||||
|
|
||||||
|
ok(-f $walfile, "Got a WAL file"); |
||||||
|
|
||||||
|
$node->command_ok( |
||||||
|
[ |
||||||
|
'pg_waldump', '--quiet', |
||||||
|
'--save-fullpage', "$tmp_folder/raw", |
||||||
|
'--relation', $relation, |
||||||
|
$walfile |
||||||
|
]); |
||||||
|
|
||||||
|
# This regexp will match filenames formatted as: |
||||||
|
# XXXXXXXX-XXXXXXXX.DBOID.TLOID.NODEOID.dd_fork with the components being: |
||||||
|
# - WAL LSN in hex format, |
||||||
|
# - Tablespace OID (0 for global) |
||||||
|
# - Database OID. |
||||||
|
# - Relfilenode. |
||||||
|
# - Block number. |
||||||
|
# - Fork this block came from (vm, init, fsm, or main). |
||||||
|
my $file_re = |
||||||
|
qr/^([0-9A-F]{8})-([0-9A-F]{8})[.][0-9]+[.][0-9]+[.][0-9]+[.][0-9]+(?:_vm|_init|_fsm|_main)?$/; |
||||||
|
|
||||||
|
my $file_count = 0; |
||||||
|
|
||||||
|
# Verify filename format matches --save-fullpage. |
||||||
|
for my $fullpath (glob "$tmp_folder/raw/*") |
||||||
|
{ |
||||||
|
my $file = File::Basename::basename($fullpath); |
||||||
|
|
||||||
|
like($file, $file_re, "verify filename format for file $file"); |
||||||
|
$file_count++; |
||||||
|
|
||||||
|
my ($hi_lsn_fn, $lo_lsn_fn) = ($file =~ $file_re); |
||||||
|
my ($hi_lsn_bk, $lo_lsn_bk) = get_block_lsn($fullpath, $blocksize); |
||||||
|
|
||||||
|
# The LSN on the block comes before the file's LSN. |
||||||
|
ok( $hi_lsn_fn . $lo_lsn_fn gt $hi_lsn_bk . $lo_lsn_bk, |
||||||
|
'LSN stored in the file precedes the one stored in the block'); |
||||||
|
} |
||||||
|
|
||||||
|
ok($file_count > 0, 'verify that at least one block has been saved'); |
||||||
|
|
||||||
|
done_testing(); |
Loading…
Reference in new issue