1
0
mirror of https://github.com/postgres/postgres.git synced 2025-04-25 21:42:33 +03:00

pg_basebackup: Allow client-side LZ4 (de)compression.

LZ4 compression can now be performed on the client using
pg_basebackup -Ft --compress client-lz4, and LZ4 decompression of
a backup compressed on the server can be performed on the client
using pg_basebackup -Fp --compress server-lz4.

Dipesh Pandit, reviewed and tested by Jeevan Ladhe and Tushar Ahuja,
with a few corrections - and some documentation - by me.

Discussion: http://postgr.es/m/CAN1g5_FeDmiA9D8wdG2W6Lkq5CpubxOAqTmd2et9hsinTJtsMQ@mail.gmail.com
This commit is contained in:
Robert Haas 2022-02-11 09:41:42 -05:00
parent dab298471f
commit 751b8d23b7
8 changed files with 606 additions and 19 deletions

View File

@ -417,18 +417,14 @@ PostgreSQL documentation
specify <literal>-Xfetch</literal>.
</para>
<para>
The compression method can be set to <literal>gzip</literal> for
compression with <application>gzip</application>, or
<literal>lz4</literal> for compression with
<application>lz4</application>, or <literal>none</literal> for no
compression. However, <literal>lz4</literal> can be currently only
used with <literal>server</literal>. A compression level can be
optionally specified, by appending the level number after a
colon (<literal>:</literal>). If no level is specified, the default
compression level will be used. If only a level is specified without
mentioning an algorithm, <literal>gzip</literal> compression will
be used if the level is greater than 0, and no compression will be
used if the level is 0.
The compression method can be set to <literal>gzip</literal> or
<literal>lz4</literal>, or <literal>none</literal> for no
compression. A compression level can be optionally specified, by
appending the level number after a colon (<literal>:</literal>). If no
level is specified, the default compression level will be used. If
only a level is specified without mentioning an algorithm,
<literal>gzip</literal> compression will be used if the level is
greater than 0, and no compression will be used if the level is 0.
</para>
<para>
When the tar format is used with <literal>gzip</literal> or
@ -439,6 +435,13 @@ PostgreSQL documentation
compression. If this is done, the server will compress the backup for
transmission, and the client will decompress and extract it.
</para>
<para>
When this option is used in combination with
<literal>-Xstream</literal>, <literal>pg_wal.tar</literal> will
be compressed using <literal>gzip</literal> if client-side gzip
compression is selected, but will not be compressed if server-side
compresion or LZ4 compresion is selected.
</para>
</listitem>
</varlistentry>
</variablelist>

View File

@ -43,6 +43,7 @@ BBOBJS = \
bbstreamer_file.o \
bbstreamer_gzip.o \
bbstreamer_inject.o \
bbstreamer_lz4.o \
bbstreamer_tar.o
all: pg_basebackup pg_receivewal pg_recvlogical

View File

@ -206,6 +206,9 @@ extern bbstreamer *bbstreamer_extractor_new(const char *basepath,
void (*report_output_file) (const char *));
extern bbstreamer *bbstreamer_gzip_decompressor_new(bbstreamer *next);
extern bbstreamer *bbstreamer_lz4_compressor_new(bbstreamer *next,
int compresslevel);
extern bbstreamer *bbstreamer_lz4_decompressor_new(bbstreamer *next);
extern bbstreamer *bbstreamer_tar_parser_new(bbstreamer *next);
extern bbstreamer *bbstreamer_tar_terminator_new(bbstreamer *next);
extern bbstreamer *bbstreamer_tar_archiver_new(bbstreamer *next);

View File

@ -0,0 +1,431 @@
/*-------------------------------------------------------------------------
*
* bbstreamer_lz4.c
*
* Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/bin/pg_basebackup/bbstreamer_lz4.c
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <unistd.h>
#ifdef HAVE_LIBLZ4
#include <lz4frame.h>
#endif
#include "bbstreamer.h"
#include "common/logging.h"
#include "common/file_perm.h"
#include "common/string.h"
#ifdef HAVE_LIBLZ4
typedef struct bbstreamer_lz4_frame
{
bbstreamer base;
LZ4F_compressionContext_t cctx;
LZ4F_decompressionContext_t dctx;
LZ4F_preferences_t prefs;
size_t bytes_written;
bool header_written;
} bbstreamer_lz4_frame;
static void bbstreamer_lz4_compressor_content(bbstreamer *streamer,
bbstreamer_member *member,
const char *data, int len,
bbstreamer_archive_context context);
static void bbstreamer_lz4_compressor_finalize(bbstreamer *streamer);
static void bbstreamer_lz4_compressor_free(bbstreamer *streamer);
const bbstreamer_ops bbstreamer_lz4_compressor_ops = {
.content = bbstreamer_lz4_compressor_content,
.finalize = bbstreamer_lz4_compressor_finalize,
.free = bbstreamer_lz4_compressor_free
};
static void bbstreamer_lz4_decompressor_content(bbstreamer *streamer,
bbstreamer_member *member,
const char *data, int len,
bbstreamer_archive_context context);
static void bbstreamer_lz4_decompressor_finalize(bbstreamer *streamer);
static void bbstreamer_lz4_decompressor_free(bbstreamer *streamer);
const bbstreamer_ops bbstreamer_lz4_decompressor_ops = {
.content = bbstreamer_lz4_decompressor_content,
.finalize = bbstreamer_lz4_decompressor_finalize,
.free = bbstreamer_lz4_decompressor_free
};
#endif
/*
* Create a new base backup streamer that performs lz4 compression of tar
* blocks.
*/
bbstreamer *
bbstreamer_lz4_compressor_new(bbstreamer *next, int compresslevel)
{
#ifdef HAVE_LIBLZ4
bbstreamer_lz4_frame *streamer;
LZ4F_errorCode_t ctxError;
LZ4F_preferences_t *prefs;
size_t compressed_bound;
Assert(next != NULL);
streamer = palloc0(sizeof(bbstreamer_lz4_frame));
*((const bbstreamer_ops **) &streamer->base.bbs_ops) =
&bbstreamer_lz4_compressor_ops;
streamer->base.bbs_next = next;
initStringInfo(&streamer->base.bbs_buffer);
streamer->header_written = false;
/* Initialize stream compression preferences */
prefs = &streamer->prefs;
memset(prefs, 0, sizeof(LZ4F_preferences_t));
prefs->frameInfo.blockSizeID = LZ4F_max256KB;
prefs->compressionLevel = compresslevel;
/*
* Find out the compression bound, it specifies the minimum destination
* capacity required in worst case for the success of compression operation
* (LZ4F_compressUpdate) based on a given source size and preferences.
*/
compressed_bound = LZ4F_compressBound(streamer->base.bbs_buffer.maxlen, prefs);
/* Enlarge buffer if it falls short of compression bound. */
if (streamer->base.bbs_buffer.maxlen <= compressed_bound)
enlargeStringInfo(&streamer->base.bbs_buffer, compressed_bound);
ctxError = LZ4F_createCompressionContext(&streamer->cctx, LZ4F_VERSION);
if (LZ4F_isError(ctxError))
pg_log_error("could not create lz4 compression context: %s",
LZ4F_getErrorName(ctxError));
return &streamer->base;
#else
pg_log_error("this build does not support compression");
exit(1);
#endif
}
#ifdef HAVE_LIBLZ4
/*
* Compress the input data to output buffer.
*
* Find out the compression bound based on input data length for each
* invocation to make sure that output buffer has enough capacity to
* accommodate the compressed data. In case if the output buffer
* capacity falls short of compression bound then forward the content
* of output buffer to next streamer and empty the buffer.
*/
static void
bbstreamer_lz4_compressor_content(bbstreamer *streamer,
bbstreamer_member *member,
const char *data, int len,
bbstreamer_archive_context context)
{
bbstreamer_lz4_frame *mystreamer;
uint8 *next_in,
*next_out;
size_t out_bound,
compressed_size,
avail_out;
mystreamer = (bbstreamer_lz4_frame *) streamer;
next_in = (uint8 *) data;
/* Write header before processing the first input chunk. */
if (!mystreamer->header_written)
{
compressed_size = LZ4F_compressBegin(mystreamer->cctx,
(uint8 *) mystreamer->base.bbs_buffer.data,
mystreamer->base.bbs_buffer.maxlen,
&mystreamer->prefs);
if (LZ4F_isError(compressed_size))
pg_log_error("could not write lz4 header: %s",
LZ4F_getErrorName(compressed_size));
mystreamer->bytes_written += compressed_size;
mystreamer->header_written = true;
}
/*
* Update the offset and capacity of output buffer based on number of bytes
* written to output buffer.
*/
next_out = (uint8 *) mystreamer->base.bbs_buffer.data + mystreamer->bytes_written;
avail_out = mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written;
/*
* Find out the compression bound and make sure that output buffer has the
* required capacity for the success of LZ4F_compressUpdate. If needed
* forward the content to next streamer and empty the buffer.
*/
out_bound = LZ4F_compressBound(len, &mystreamer->prefs);
Assert(mystreamer->base.bbs_buffer.maxlen >= out_bound);
if (avail_out <= out_bound)
{
bbstreamer_content(mystreamer->base.bbs_next, member,
mystreamer->base.bbs_buffer.data,
mystreamer->bytes_written,
context);
avail_out = mystreamer->base.bbs_buffer.maxlen;
mystreamer->bytes_written = 0;
next_out = (uint8 *) mystreamer->base.bbs_buffer.data;
}
/*
* This call compresses the data starting at next_in and generates the
* output starting at next_out. It expects the caller to provide the size
* of input buffer and capacity of output buffer by providing parameters
* len and avail_out.
*
* It returns the number of bytes compressed to output buffer.
*/
compressed_size = LZ4F_compressUpdate(mystreamer->cctx,
next_out, avail_out,
next_in, len, NULL);
if (LZ4F_isError(compressed_size))
pg_log_error("could not compress data: %s",
LZ4F_getErrorName(compressed_size));
mystreamer->bytes_written += compressed_size;
}
/*
* End-of-stream processing.
*/
static void
bbstreamer_lz4_compressor_finalize(bbstreamer *streamer)
{
bbstreamer_lz4_frame *mystreamer;
uint8 *next_out;
size_t footer_bound,
compressed_size,
avail_out;
mystreamer = (bbstreamer_lz4_frame *) streamer;
/* Find out the footer bound and update the output buffer. */
footer_bound = LZ4F_compressBound(0, &mystreamer->prefs);
Assert(mystreamer->base.bbs_buffer.maxlen >= footer_bound);
if ((mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written) <=
footer_bound)
{
bbstreamer_content(mystreamer->base.bbs_next, NULL,
mystreamer->base.bbs_buffer.data,
mystreamer->bytes_written,
BBSTREAMER_UNKNOWN);
avail_out = mystreamer->base.bbs_buffer.maxlen;
mystreamer->bytes_written = 0;
next_out = (uint8 *) mystreamer->base.bbs_buffer.data;
}
else
{
next_out = (uint8 *) mystreamer->base.bbs_buffer.data + mystreamer->bytes_written;
avail_out = mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written;
}
/*
* Finalize the frame and flush whatever data remaining in compression
* context.
*/
compressed_size = LZ4F_compressEnd(mystreamer->cctx,
next_out, avail_out, NULL);
if (LZ4F_isError(compressed_size))
pg_log_error("could not end lz4 compression: %s",
LZ4F_getErrorName(compressed_size));
mystreamer->bytes_written += compressed_size;
bbstreamer_content(mystreamer->base.bbs_next, NULL,
mystreamer->base.bbs_buffer.data,
mystreamer->bytes_written,
BBSTREAMER_UNKNOWN);
bbstreamer_finalize(mystreamer->base.bbs_next);
}
/*
* Free memory.
*/
static void
bbstreamer_lz4_compressor_free(bbstreamer *streamer)
{
bbstreamer_lz4_frame *mystreamer;
mystreamer = (bbstreamer_lz4_frame *) streamer;
bbstreamer_free(streamer->bbs_next);
LZ4F_freeCompressionContext(mystreamer->cctx);
pfree(streamer->bbs_buffer.data);
pfree(streamer);
}
#endif
/*
* Create a new base backup streamer that performs decompression of lz4
* compressed blocks.
*/
bbstreamer *
bbstreamer_lz4_decompressor_new(bbstreamer *next)
{
#ifdef HAVE_LIBLZ4
bbstreamer_lz4_frame *streamer;
LZ4F_errorCode_t ctxError;
Assert(next != NULL);
streamer = palloc0(sizeof(bbstreamer_lz4_frame));
*((const bbstreamer_ops **) &streamer->base.bbs_ops) =
&bbstreamer_lz4_decompressor_ops;
streamer->base.bbs_next = next;
initStringInfo(&streamer->base.bbs_buffer);
/* Initialize internal stream state for decompression */
ctxError = LZ4F_createDecompressionContext(&streamer->dctx, LZ4F_VERSION);
if (LZ4F_isError(ctxError))
{
pg_log_error("could not initialize compression library: %s",
LZ4F_getErrorName(ctxError));
exit(1);
}
return &streamer->base;
#else
pg_log_error("this build does not support compression");
exit(1);
#endif
}
#ifdef HAVE_LIBLZ4
/*
* Decompress the input data to output buffer until we run out of input
* data. Each time the output buffer is full, pass on the decompressed data
* to the next streamer.
*/
static void
bbstreamer_lz4_decompressor_content(bbstreamer *streamer,
bbstreamer_member *member,
const char *data, int len,
bbstreamer_archive_context context)
{
bbstreamer_lz4_frame *mystreamer;
uint8 *next_in,
*next_out;
size_t avail_in,
avail_out;
mystreamer = (bbstreamer_lz4_frame *) streamer;
next_in = (uint8 *) data;
next_out = (uint8 *) mystreamer->base.bbs_buffer.data;
avail_in = len;
avail_out = mystreamer->base.bbs_buffer.maxlen;
while (avail_in > 0)
{
size_t ret,
read_size,
out_size;
read_size = avail_in;
out_size = avail_out;
/*
* This call decompresses the data starting at next_in and generates
* the output data starting at next_out. It expects the caller to
* provide size of the input buffer and total capacity of the output
* buffer by providing the read_size and out_size parameters
* respectively.
*
* Per the documentation of LZ4, parameters read_size and out_size
* behaves as dual parameters. On return, the number of bytes consumed
* from the input buffer will be written back to read_size and the
* number of bytes decompressed to output buffer will be written back
* to out_size respectively.
*/
ret = LZ4F_decompress(mystreamer->dctx,
next_out, &out_size,
next_in, &read_size, NULL);
if (LZ4F_isError(ret))
pg_log_error("could not decompress data: %s",
LZ4F_getErrorName(ret));
/* Update input buffer based on number of bytes consumed */
avail_in -= read_size;
next_in += read_size;
mystreamer->bytes_written += out_size;
/*
* If output buffer is full then forward the content to next streamer and
* update the output buffer.
*/
if (mystreamer->bytes_written >= mystreamer->base.bbs_buffer.maxlen)
{
bbstreamer_content(mystreamer->base.bbs_next, member,
mystreamer->base.bbs_buffer.data,
mystreamer->base.bbs_buffer.maxlen,
context);
avail_out = mystreamer->base.bbs_buffer.maxlen;
mystreamer->bytes_written = 0;
next_out = (uint8 *) mystreamer->base.bbs_buffer.data;
}
else
{
avail_out = mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written;
next_out += mystreamer->bytes_written;
}
}
}
/*
* End-of-stream processing.
*/
static void
bbstreamer_lz4_decompressor_finalize(bbstreamer *streamer)
{
bbstreamer_lz4_frame *mystreamer;
mystreamer = (bbstreamer_lz4_frame *) streamer;
/*
* End of the stream, if there is some pending data in output buffers then
* we must forward it to next streamer.
*/
bbstreamer_content(mystreamer->base.bbs_next, NULL,
mystreamer->base.bbs_buffer.data,
mystreamer->base.bbs_buffer.maxlen,
BBSTREAMER_UNKNOWN);
bbstreamer_finalize(mystreamer->base.bbs_next);
}
/*
* Free memory.
*/
static void
bbstreamer_lz4_decompressor_free(bbstreamer *streamer)
{
bbstreamer_lz4_frame *mystreamer;
mystreamer = (bbstreamer_lz4_frame *) streamer;
bbstreamer_free(streamer->bbs_next);
LZ4F_freeDecompressionContext(mystreamer->dctx);
pfree(streamer->bbs_buffer.data);
pfree(streamer);
}
#endif

View File

@ -560,11 +560,16 @@ LogStreamerMain(logstreamer_param *param)
COMPRESSION_NONE,
compresslevel,
stream.do_sync);
else
else if (compressmethod == COMPRESSION_GZIP)
stream.walmethod = CreateWalTarMethod(param->xlog,
compressmethod,
compresslevel,
stream.do_sync);
else
stream.walmethod = CreateWalTarMethod(param->xlog,
COMPRESSION_NONE,
compresslevel,
stream.do_sync);
if (!ReceiveXlogStream(param->bgconn, &stream))
@ -1003,6 +1008,16 @@ parse_compress_options(char *src, WalCompressionMethod *methodres,
*methodres = COMPRESSION_GZIP;
*locationres = COMPRESS_LOCATION_SERVER;
}
else if (pg_strcasecmp(firstpart, "lz4") == 0)
{
*methodres = COMPRESSION_LZ4;
*locationres = COMPRESS_LOCATION_UNSPECIFIED;
}
else if (pg_strcasecmp(firstpart, "client-lz4") == 0)
{
*methodres = COMPRESSION_LZ4;
*locationres = COMPRESS_LOCATION_CLIENT;
}
else if (pg_strcasecmp(firstpart, "server-lz4") == 0)
{
*methodres = COMPRESSION_LZ4;
@ -1125,7 +1140,8 @@ CreateBackupStreamer(char *archive_name, char *spclocation,
bbstreamer *manifest_inject_streamer = NULL;
bool inject_manifest;
bool is_tar,
is_tar_gz;
is_tar_gz,
is_tar_lz4;
bool must_parse_archive;
int archive_name_len = strlen(archive_name);
@ -1144,6 +1160,10 @@ CreateBackupStreamer(char *archive_name, char *spclocation,
is_tar_gz = (archive_name_len > 8 &&
strcmp(archive_name + archive_name_len - 3, ".gz") == 0);
/* Is this a LZ4 archive? */
is_tar_lz4 = (archive_name_len > 8 &&
strcmp(archive_name + archive_name_len - 4, ".lz4") == 0);
/*
* We have to parse the archive if (1) we're suppose to extract it, or if
* (2) we need to inject backup_manifest or recovery configuration into it.
@ -1153,7 +1173,7 @@ CreateBackupStreamer(char *archive_name, char *spclocation,
(spclocation == NULL && writerecoveryconf));
/* At present, we only know how to parse tar archives. */
if (must_parse_archive && !is_tar && !is_tar_gz)
if (must_parse_archive && !is_tar && !is_tar_gz && !is_tar_lz4)
{
pg_log_error("unable to parse archive: %s", archive_name);
pg_log_info("only tar archives can be parsed");
@ -1217,6 +1237,14 @@ CreateBackupStreamer(char *archive_name, char *spclocation,
archive_file,
compresslevel);
}
else if (compressmethod == COMPRESSION_LZ4)
{
strlcat(archive_filename, ".lz4", sizeof(archive_filename));
streamer = bbstreamer_plain_writer_new(archive_filename,
archive_file);
streamer = bbstreamer_lz4_compressor_new(streamer,
compresslevel);
}
else
{
Assert(false); /* not reachable */
@ -1269,9 +1297,13 @@ CreateBackupStreamer(char *archive_name, char *spclocation,
* If the user has requested a server compressed archive along with archive
* extraction at client then we need to decompress it.
*/
if (format == 'p' && compressmethod == COMPRESSION_GZIP &&
compressloc == COMPRESS_LOCATION_SERVER)
streamer = bbstreamer_gzip_decompressor_new(streamer);
if (format == 'p' && compressloc == COMPRESS_LOCATION_SERVER)
{
if (compressmethod == COMPRESSION_GZIP)
streamer = bbstreamer_gzip_decompressor_new(streamer);
else if (compressmethod == COMPRESSION_LZ4)
streamer = bbstreamer_lz4_decompressor_new(streamer);
}
/* Return the results. */
*manifest_inject_streamer_p = manifest_inject_streamer;

View File

@ -11,7 +11,7 @@ use Config;
use File::Path qw(rmtree);
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More tests => 4;
use Test::More tests => 6;
my $primary = PostgreSQL::Test::Cluster->new('primary');
$primary->init(allows_streaming => 1);
@ -27,6 +27,11 @@ my @test_configuration = (
'compression_method' => 'gzip',
'backup_flags' => ['--compress', 'server-gzip:5'],
'enabled' => check_pg_config("#define HAVE_LIBZ 1")
},
{
'compression_method' => 'lz4',
'backup_flags' => ['--compress', 'server-lz4:5'],
'enabled' => check_pg_config("#define HAVE_LIBLZ4 1")
}
);

View File

@ -0,0 +1,111 @@
# Copyright (c) 2021-2022, PostgreSQL Global Development Group
# This test case aims to verify that client-side backup compression work
# properly, and it also aims to verify that pg_verifybackup can verify a base
# backup that didn't start out in plain format.
use strict;
use warnings;
use Config;
use File::Path qw(rmtree);
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More tests => 9;
my $primary = PostgreSQL::Test::Cluster->new('primary');
$primary->init(allows_streaming => 1);
$primary->start;
my $backup_path = $primary->backup_dir . '/client-backup';
my $extract_path = $primary->backup_dir . '/extracted-backup';
my @test_configuration = (
{
'compression_method' => 'none',
'backup_flags' => [],
'backup_archive' => 'base.tar',
'enabled' => 1
},
{
'compression_method' => 'gzip',
'backup_flags' => ['--compress', 'client-gzip:5'],
'backup_archive' => 'base.tar.gz',
'decompress_program' => $ENV{'GZIP_PROGRAM'},
'decompress_flags' => [ '-d' ],
'enabled' => check_pg_config("#define HAVE_LIBZ 1")
},
{
'compression_method' => 'lz4',
'backup_flags' => ['--compress', 'client-lz4:5'],
'backup_archive' => 'base.tar.lz4',
'decompress_program' => $ENV{'LZ4'},
'decompress_flags' => [ '-d' ],
'output_file' => 'base.tar',
'enabled' => check_pg_config("#define HAVE_LIBLZ4 1")
}
);
for my $tc (@test_configuration)
{
my $method = $tc->{'compression_method'};
SKIP: {
skip "$method compression not supported by this build", 3
if ! $tc->{'enabled'};
skip "no decompressor available for $method", 3
if exists $tc->{'decompress_program'} &&
!defined $tc->{'decompress_program'};
# Take a client-side backup.
my @backup = (
'pg_basebackup', '-D', $backup_path,
'-Xfetch', '--no-sync', '-cfast', '-Ft');
push @backup, @{$tc->{'backup_flags'}};
$primary->command_ok(\@backup,
"client side backup, compression $method");
# Verify that the we got the files we expected.
my $backup_files = join(',',
sort grep { $_ ne '.' && $_ ne '..' } slurp_dir($backup_path));
my $expected_backup_files = join(',',
sort ('backup_manifest', $tc->{'backup_archive'}));
is($backup_files,$expected_backup_files,
"found expected backup files, compression $method");
# Decompress.
if (exists $tc->{'decompress_program'})
{
my @decompress = ($tc->{'decompress_program'});
push @decompress, @{$tc->{'decompress_flags'}}
if $tc->{'decompress_flags'};
push @decompress, $backup_path . '/' . $tc->{'backup_archive'};
push @decompress, $backup_path . '/' . $tc->{'output_file'}
if $tc->{'output_file'};
system_or_bail(@decompress);
}
SKIP: {
my $tar = $ENV{TAR};
# don't check for a working tar here, to accomodate various odd
# cases such as AIX. If tar doesn't work the init_from_backup below
# will fail.
skip "no tar program available", 1
if (!defined $tar || $tar eq '');
# Untar.
mkdir($extract_path);
system_or_bail($tar, 'xf', $backup_path . '/base.tar',
'-C', $extract_path);
# Verify.
$primary->command_ok([ 'pg_verifybackup', '-n',
'-m', "$backup_path/backup_manifest", '-e', $extract_path ],
"verify backup, compression $method");
}
# Cleanup.
rmtree($extract_path);
rmtree($backup_path);
}
}

View File

@ -379,6 +379,7 @@ sub mkvcbuild
$pgbasebackup->AddFile('src/bin/pg_basebackup/bbstreamer_file.c');
$pgbasebackup->AddFile('src/bin/pg_basebackup/bbstreamer_gzip.c');
$pgbasebackup->AddFile('src/bin/pg_basebackup/bbstreamer_inject.c');
$pgbasebackup->AddFile('src/bin/pg_basebackup/bbstreamer_lz4.c');
$pgbasebackup->AddFile('src/bin/pg_basebackup/bbstreamer_tar.c');
$pgbasebackup->AddLibrary('ws2_32.lib');