1
0
mirror of https://github.com/postgres/postgres.git synced 2025-08-09 17:03:00 +03:00

Add new pg_walsummary tool.

This can dump the contents of the WAL summary files found in
pg_wal/summaries. Normally, this shouldn't really be something anyone
needs to do, but it may be needed for debugging problems with
incremental backup, or could possibly be useful to external tools.

Discussion: http://postgr.es/m/CA+Tgmobvqqj-DW9F7uUzT-cQqs6wcVb-Xhs=w=hzJnXSE-kRGw@mail.gmail.com
This commit is contained in:
Robert Haas
2024-01-11 12:47:28 -05:00
parent d9ef650fca
commit ee1bfd1683
13 changed files with 600 additions and 0 deletions

1
src/bin/pg_walsummary/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
pg_walsummary

View File

@@ -0,0 +1,48 @@
#-------------------------------------------------------------------------
#
# Makefile for src/bin/pg_walsummary
#
# Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
# Portions Copyright (c) 1994, Regents of the University of California
#
# src/bin/pg_walsummary/Makefile
#
#-------------------------------------------------------------------------
PGFILEDESC = "pg_walsummary - print contents of WAL summary files"
PGAPPICON=win32
subdir = src/bin/pg_walsummary
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils
OBJS = \
$(WIN32RES) \
pg_walsummary.o
all: pg_walsummary
pg_walsummary: $(OBJS) | submake-libpgport submake-libpgfeutils
$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
install: all installdirs
$(INSTALL_PROGRAM) pg_walsummary$(X) '$(DESTDIR)$(bindir)/pg_walsummary$(X)'
installdirs:
$(MKDIR_P) '$(DESTDIR)$(bindir)'
uninstall:
rm -f '$(DESTDIR)$(bindir)/pg_walsummary$(X)'
clean distclean maintainer-clean:
rm -f pg_walsummary$(X) $(OBJS)
check:
$(prove_check)
installcheck:
$(prove_installcheck)

View File

@@ -0,0 +1,30 @@
# Copyright (c) 2022-2023, PostgreSQL Global Development Group
pg_walsummary_sources = files(
'pg_walsummary.c',
)
if host_system == 'windows'
pg_walsummary_sources += rc_bin_gen.process(win32ver_rc, extra_args: [
'--NAME', 'pg_walsummary',
'--FILEDESC', 'pg_walsummary - print contents of WAL summary files',])
endif
pg_walsummary = executable('pg_walsummary',
pg_walsummary_sources,
dependencies: [frontend_code],
kwargs: default_bin_args,
)
bin_targets += pg_walsummary
tests += {
'name': 'pg_walsummary',
'sd': meson.current_source_dir(),
'bd': meson.current_build_dir(),
'tap': {
'tests': [
't/001_basic.pl',
't/002_blocks.pl',
],
}
}

View File

@@ -0,0 +1,6 @@
# src/bin/pg_combinebackup/nls.mk
CATALOG_NAME = pg_walsummary
GETTEXT_FILES = $(FRONTEND_COMMON_GETTEXT_FILES) \
pg_walsummary.c
GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS)
GETTEXT_FLAGS = $(FRONTEND_COMMON_GETTEXT_FLAGS)

View File

@@ -0,0 +1,280 @@
/*-------------------------------------------------------------------------
*
* pg_walsummary.c
* Prints the contents of WAL summary files.
*
* Copyright (c) 2017-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/bin/pg_walsummary/pg_walsummary.c
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <fcntl.h>
#include <limits.h>
#include "common/blkreftable.h"
#include "common/logging.h"
#include "fe_utils/option_utils.h"
#include "lib/stringinfo.h"
#include "getopt_long.h"
typedef struct ws_options
{
bool individual;
bool quiet;
} ws_options;
typedef struct ws_file_info
{
int fd;
char *filename;
} ws_file_info;
static BlockNumber *block_buffer = NULL;
static unsigned block_buffer_size = 512; /* Initial size. */
static void dump_one_relation(ws_options *opt, RelFileLocator *rlocator,
ForkNumber forknum, BlockNumber limit_block,
BlockRefTableReader *reader);
static void help(const char *progname);
static int compare_block_numbers(const void *a, const void *b);
static int walsummary_read_callback(void *callback_arg, void *data,
int length);
static void walsummary_error_callback(void *callback_arg, char *fmt,...) pg_attribute_printf(2, 3);
/*
* Main program.
*/
int
main(int argc, char *argv[])
{
static struct option long_options[] = {
{"individual", no_argument, NULL, 'i'},
{"quiet", no_argument, NULL, 'q'},
{NULL, 0, NULL, 0}
};
const char *progname;
int optindex;
int c;
ws_options opt;
memset(&opt, 0, sizeof(ws_options));
pg_logging_init(argv[0]);
progname = get_progname(argv[0]);
handle_help_version_opts(argc, argv, progname, help);
/* process command-line options */
while ((c = getopt_long(argc, argv, "f:iqw:",
long_options, &optindex)) != -1)
{
switch (c)
{
case 'i':
opt.individual = true;
break;
case 'q':
opt.quiet = true;
break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
exit(1);
}
}
if (optind >= argc)
{
pg_log_error("%s: no input files specified", progname);
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
exit(1);
}
while (optind < argc)
{
ws_file_info ws;
BlockRefTableReader *reader;
RelFileLocator rlocator;
ForkNumber forknum;
BlockNumber limit_block;
ws.filename = argv[optind++];
if ((ws.fd = open(ws.filename, O_RDONLY | PG_BINARY, 0)) < 0)
pg_fatal("could not open file \"%s\": %m", ws.filename);
reader = CreateBlockRefTableReader(walsummary_read_callback, &ws,
ws.filename,
walsummary_error_callback, NULL);
while (BlockRefTableReaderNextRelation(reader, &rlocator, &forknum,
&limit_block))
dump_one_relation(&opt, &rlocator, forknum, limit_block, reader);
DestroyBlockRefTableReader(reader);
close(ws.fd);
}
exit(0);
}
/*
* Dump details for one relation.
*/
static void
dump_one_relation(ws_options *opt, RelFileLocator *rlocator,
ForkNumber forknum, BlockNumber limit_block,
BlockRefTableReader *reader)
{
unsigned i = 0;
unsigned nblocks;
BlockNumber startblock = InvalidBlockNumber;
BlockNumber endblock = InvalidBlockNumber;
/* Dump limit block, if any. */
if (limit_block != InvalidBlockNumber)
printf("TS %u, DB %u, REL %u, FORK %s: limit %u\n",
rlocator->spcOid, rlocator->dbOid, rlocator->relNumber,
forkNames[forknum], limit_block);
/* If we haven't allocated a block buffer yet, do that now. */
if (block_buffer == NULL)
block_buffer = palloc_array(BlockNumber, block_buffer_size);
/* Try to fill the block buffer. */
nblocks = BlockRefTableReaderGetBlocks(reader,
block_buffer,
block_buffer_size);
/* If we filled the block buffer completely, we must enlarge it. */
while (nblocks >= block_buffer_size)
{
unsigned new_size;
/* Double the size, being careful about overflow. */
new_size = block_buffer_size * 2;
if (new_size < block_buffer_size)
new_size = PG_UINT32_MAX;
block_buffer = repalloc_array(block_buffer, BlockNumber, new_size);
/* Try to fill the newly-allocated space. */
nblocks +=
BlockRefTableReaderGetBlocks(reader,
block_buffer + block_buffer_size,
new_size - block_buffer_size);
/* Save the new size for later calls. */
block_buffer_size = new_size;
}
/* If we don't need to produce any output, skip the rest of this. */
if (opt->quiet)
return;
/*
* Sort the returned block numbers. If the block reference table was using
* the bitmap representation for a given chunk, the block numbers in that
* chunk will already be sorted, but when the array-of-offsets
* representation is used, we can receive block numbers here out of order.
*/
qsort(block_buffer, nblocks, sizeof(BlockNumber), compare_block_numbers);
/* Dump block references. */
while (i < nblocks)
{
/*
* Find the next range of blocks to print, but if --individual was
* specified, then consider each block a separate range.
*/
startblock = endblock = block_buffer[i++];
if (!opt->individual)
{
while (i < nblocks && block_buffer[i] == endblock + 1)
{
endblock++;
i++;
}
}
/* Format this range of block numbers as a string. */
if (startblock == endblock)
printf("TS %u, DB %u, REL %u, FORK %s: block %u\n",
rlocator->spcOid, rlocator->dbOid, rlocator->relNumber,
forkNames[forknum], startblock);
else
printf("TS %u, DB %u, REL %u, FORK %s: blocks %u..%u\n",
rlocator->spcOid, rlocator->dbOid, rlocator->relNumber,
forkNames[forknum], startblock, endblock);
}
}
/*
* Quicksort comparator for block numbers.
*/
static int
compare_block_numbers(const void *a, const void *b)
{
BlockNumber aa = *(BlockNumber *) a;
BlockNumber bb = *(BlockNumber *) b;
if (aa > bb)
return 1;
else if (aa == bb)
return 0;
else
return -1;
}
/*
* Error callback.
*/
void
walsummary_error_callback(void *callback_arg, char *fmt,...)
{
va_list ap;
va_start(ap, fmt);
pg_log_generic_v(PG_LOG_ERROR, PG_LOG_PRIMARY, fmt, ap);
va_end(ap);
exit(1);
}
/*
* Read callback.
*/
int
walsummary_read_callback(void *callback_arg, void *data, int length)
{
ws_file_info *ws = callback_arg;
int rc;
if ((rc = read(ws->fd, data, length)) < 0)
pg_fatal("could not read file \"%s\": %m", ws->filename);
return rc;
}
/*
* help
*
* Prints help page for the program
*
* progname: the name of the executed program, such as "pg_walsummary"
*/
static void
help(const char *progname)
{
printf(_("%s prints the contents of a WAL summary file.\n\n"), progname);
printf(_("Usage:\n"));
printf(_(" %s [OPTION]... FILE...\n"), progname);
printf(_("\nOptions:\n"));
printf(_(" -i, --individual list block numbers individually, not as ranges\n"));
printf(_(" -q, --quiet don't print anything, just parse the files\n"));
printf(_(" -?, --help show this help, then exit\n"));
printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
}

View File

@@ -0,0 +1,19 @@
# Copyright (c) 2021-2023, PostgreSQL Global Development Group
use strict;
use warnings;
use PostgreSQL::Test::Utils;
use Test::More;
my $tempdir = PostgreSQL::Test::Utils::tempdir;
program_help_ok('pg_walsummary');
program_version_ok('pg_walsummary');
program_options_handling_ok('pg_walsummary');
command_fails_like(
['pg_walsummary'],
qr/no input files specified/,
'input files must be specified');
done_testing();

View File

@@ -0,0 +1,88 @@
# Copyright (c) 2021-2023, PostgreSQL Global Development Group
use strict;
use warnings;
use File::Compare;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
# Set up a new database instance.
my $node1 = PostgreSQL::Test::Cluster->new('node1');
$node1->init(has_archiving => 1, allows_streaming => 1);
$node1->append_conf('postgresql.conf', 'summarize_wal = on');
$node1->start;
# See what's been summarized up until now.
my $progress = $node1->safe_psql('postgres', <<EOM);
SELECT summarized_tli, summarized_lsn FROM pg_get_wal_summarizer_state()
EOM
my ($summarized_tli, $summarized_lsn) = split(/\|/, $progress);
note("before insert, summarized TLI $summarized_tli through $summarized_lsn");
# Create a table and insert a few test rows into it. VACUUM FREEZE it so that
# autovacuum doesn't induce any future modifications unexpectedly. Then
# trigger a checkpoint.
$node1->safe_psql('postgres', <<EOM);
CREATE TABLE mytable (a int, b text);
INSERT INTO mytable
SELECT
g, random()::text||random()::text||random()::text||random()::text
FROM
generate_series(1, 400) g;
VACUUM FREEZE;
CHECKPOINT;
EOM
# Wait for a new summary to show up.
$node1->poll_query_until('postgres', <<EOM);
SELECT EXISTS (
SELECT * from pg_available_wal_summaries()
WHERE tli = $summarized_tli AND end_lsn > '$summarized_lsn'
)
EOM
# Again check the progress of WAL summarization.
$progress = $node1->safe_psql('postgres', <<EOM);
SELECT summarized_tli, summarized_lsn FROM pg_get_wal_summarizer_state()
EOM
($summarized_tli, $summarized_lsn) = split(/\|/, $progress);
note("after insert, summarized TLI $summarized_tli through $summarized_lsn");
# Update a row in the first block of the table and trigger a checkpoint.
$node1->safe_psql('postgres', <<EOM);
UPDATE mytable SET b = 'abcdefghijklmnopqrstuvwxyz' WHERE a = 2;
CHECKPOINT;
EOM
# Again wait for a new summary to show up.
$node1->poll_query_until('postgres', <<EOM);
SELECT EXISTS (
SELECT * from pg_available_wal_summaries()
WHERE tli = $summarized_tli AND end_lsn > '$summarized_lsn'
)
EOM
# Figure out the exact details for the new sumamry file.
my $details = $node1->safe_psql('postgres', <<EOM);
SELECT tli, start_lsn, end_lsn from pg_available_wal_summaries()
WHERE tli = $summarized_tli AND end_lsn > '$summarized_lsn'
EOM
my ($tli, $start_lsn, $end_lsn) = split(/\|/, $details);
note("examining summary for TLI $tli from $start_lsn to $end_lsn");
# Reconstruct the full pathname for the WAL summary file.
my $filename = sprintf "%s/pg_wal/summaries/%08s%08s%08s%08s%08s.summary",
$node1->data_dir, $tli,
split(m@/@, $start_lsn),
split(m@/@, $end_lsn);
ok(-f $filename, "WAL summary file exists");
# Run pg_walsummary on it. We expect block 0 to be modified, but block 1
# to be unmodified, so the output should say block 0, not block 0..1 or
# similar.
my ($stdout, $stderr) = run_command([ 'pg_walsummary', $filename ]);
like($stdout, qr/FORK main: block 0$/m, "stdout shows block 0 modified");
is($stderr, '', 'stderr is empty');
done_testing();