1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

pg_basebackup: Report correct size estimate for incremental backups

Progress reporting in pg_basebackup and pg_stat_progress_basebackup used
the estimated size of a full backup even when taking an incremental backup.
The size estimation pass did not account for incremental files and counted
every file at its full size. Progress could therefore remain at only a few
percent even after the incremental backup completed successfully.

Fix this by making the size estimation pass account for incremental files,
counting them at the size they will occupy in the archive.

Backpatch to 17, where incremental backup was introduced.

Author: Daria Lepikhova <daria.n.lepikhova@gmail.com>
Reviewed-by: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
Reviewed-by: Surya Poondla <suryapoondla4@gmail.com>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/CAK49S1ASwk7L-7LYUUz1MU_+uQnZ75srva2XRR4r-w0HCVZeUQ@mail.gmail.com
Backpatch-through: 17
This commit is contained in:
Fujii Masao
2026-10-14 19:54:45 +09:00
parent 4a04576cae
commit 97fa9bdc5a
5 changed files with 102 additions and 4 deletions

View File

@@ -3200,7 +3200,9 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
before the transfer is even started, and might as such have a
negative impact on the performance. In particular, it might take
longer before the first data
is streamed. Since the database files can change during the backup,
is streamed. For an incremental backup, each file is counted at the
size it will occupy in the archive rather than its size on disk.
Since the database files can change during the backup,
the size is only approximate and might both grow and shrink between
the time of approximation and the sending of the actual files.
The default is false.
@@ -3429,6 +3431,11 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
tablespace, in kilobytes (1024 bytes), if progress report has been
requested; otherwise it's null. This size is measured before any
server-side compression.
For an incremental backup this can be much smaller than the
size of the tablespace, since a relation file that has not changed
since the prior backup, or has changed only in part, can be replaced
by a smaller incremental file containing just the blocks that have
changed.
</para>
</listitem>
</varlistentry>

View File

@@ -313,10 +313,10 @@ perform_base_backup(basebackup_options *opt, bbsink *sink,
if (tmp->path == NULL)
tmp->size = sendDir(sink, ".", 1, true, state.tablespaces,
true, NULL, InvalidOid, NULL);
true, NULL, InvalidOid, ib);
else
tmp->size = sendTablespace(sink, tmp->path, tmp->oid, true,
NULL, NULL);
NULL, ib);
state.bytes_total += tmp->size;
}
state.bytes_total_is_valid = true;
@@ -1175,7 +1175,8 @@ sendTablespace(bbsink *sink, char *path, Oid spcoid, bool sizeonly,
/*
* Include all files from the given directory in the output tar stream. If
* 'sizeonly' is true, we just calculate a total length and return it, without
* actually sending anything.
* actually sending anything. A file that will be sent incrementally is
* counted at the size of the incremental file.
*
* Omit any directory in the tablespaces list, to avoid backing up
* tablespaces twice when they were created inside PGDATA.

View File

@@ -727,7 +727,11 @@ GetFileBackupMethod(IncrementalBackupInfo *ib, const char *path,
ipath = GetIncrementalFilePath(dboid, spcoid, relfilenumber,
forknum, segno);
if (backup_file_lookup(ib->manifest_files, ipath) == NULL)
{
pfree(ipath);
return BACK_UP_FILE_FULLY;
}
pfree(ipath);
}
/*

View File

@@ -100,6 +100,7 @@ tests += {
'tests': [
't/010_pg_basebackup.pl',
't/011_in_place_tablespace.pl',
't/012_incremental_progress.pl',
't/020_pg_receivewal.pl',
't/030_pg_recvlogical.pl',
't/040_pg_createsubscriber.pl',

View File

@@ -0,0 +1,85 @@
# Copyright (c) 2026, PostgreSQL Global Development Group
# Verify that incremental backup size estimates account for files sent in
# incremental format, rather than counting every file at its full size.
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
my $node = PostgreSQL::Test::Cluster->new('primary');
$node->init(allows_streaming => 1);
$node->append_conf('postgresql.conf', 'summarize_wal = on');
$node->append_conf('postgresql.conf', 'autovacuum = off');
$node->start;
# Put unchanged data in a separate tablespace to exercise size estimation
# for both PGDATA and user-defined tablespaces.
$node->safe_psql('postgres', <<EOM);
SET allow_in_place_tablespaces = on;
CREATE TABLESPACE inplace LOCATION '';
CREATE TABLE unchanged TABLESPACE inplace AS
SELECT g AS i, repeat('x', 500) AS pad FROM generate_series(1, 20000) g;
CREATE TABLE changed AS SELECT 1 AS i;
EOM
# Run a backup with --progress and return the streamed and total amounts
# from the final progress report, in kB.
sub backup_with_progress
{
my ($path, @extra) = @_;
my ($stdout, $stderr) = ('', '');
my $result = run_log(
[
'pg_basebackup',
'--host' => $node->host,
'--port' => $node->port,
'--pgdata' => $path,
'--format' => 'tar',
'--wal-method' => 'none',
'--no-sync',
'--progress',
'--checkpoint' => 'fast',
@extra
],
'>' => \$stdout,
'2>' => \$stderr);
ok($result, "backup into $path succeeded")
or die "pg_basebackup into $path failed: $stderr";
# Use the last progress line, allowing either newline or carriage return.
my @lines = grep { /kB/ } split(/[\r\n]+/, $stderr);
my $line = $lines[-1] // '';
my ($streamed, $total) = $line =~ m{^\s*(\d+)/(\d+) kB\b};
ok(defined($total), "progress was reported for $path")
or die "could not parse progress output for $path: $stderr";
note "backup into $path streamed $streamed of $total kB";
return ($streamed, $total);
}
my $backup_dir = $node->backup_dir;
my (undef, $full_total) = backup_with_progress("$backup_dir/full");
# Make a small change before taking the incremental backup.
$node->safe_psql('postgres',
'INSERT INTO changed SELECT g FROM generate_series(1, 1000) g');
my ($incr_streamed, $incr_total) = backup_with_progress("$backup_dir/incr",
'--incremental' => "$backup_dir/full/backup_manifest");
# The incremental backup estimate should be much smaller than the full one.
cmp_ok($incr_total, '<', $full_total / 2,
'incremental backup estimate is much smaller than a full backup estimate'
);
# Check for a substantial overestimate. pg_basebackup raises the displayed
# total when the amount streamed exceeds the original estimate.
cmp_ok(
$incr_total, '<',
$incr_streamed * 2,
'incremental backup estimate is not substantially overestimated');
done_testing();