Skip to content

Commit 1b61ca8

Browse files
committed
Merge branch 'pgpro-1457'
2 parents 59b5145 + e492b4e commit 1b61ca8

File tree

10 files changed

+1368
-40
lines changed

10 files changed

+1368
-40
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Using `pg_probackup`, you can take full or incremental backups:
2727
* `Full` backups contain all the data files required to restore the database cluster from scratch.
2828
* `Incremental` backups only store the data that has changed since the previous backup. It allows to decrease the backup size and speed up backup operations. `pg_probackup` supports the following modes of incremental backups:
2929
* `PAGE` backup. In this mode, `pg_probackup` scans all WAL files in the archive from the moment the previous full or incremental backup was taken. Newly created backups contain only the pages that were mentioned in WAL records. This requires all the WAL files since the previous backup to be present in the WAL archive. If the size of these files is comparable to the total size of the database cluster files, speedup is smaller, but the backup still takes less space.
30+
* `DELTA` backup. In this mode, `pg_probackup` read all data files in PGDATA directory and only those pages, that where changed since previous backup, are copied. Continuous archiving is not necessary for it to operate. Also this mode impose I/O stress equal to `Full` backup and takes the same amount of time.
3031
* `PTRACK` backup. In this mode, PostgreSQL tracks page changes on the fly. Continuous archiving is not necessary for it to operate. Each time a relation page is updated, this page is marked in a special `PTRACK` bitmap for this relation. As one page requires just one bit in the `PTRACK` fork, such bitmaps are quite small. Tracking implies some minor overhead on the database server operation, but speeds up incremental backups significantly.
3132

3233
Regardless of the chosen backup type, all backups taken with `pg_probackup` support the following archiving strategies:

src/backup.c

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,8 @@ do_backup_instance(void)
479479
* backup on current timeline exists and get its filelist.
480480
*/
481481
if (current.backup_mode == BACKUP_MODE_DIFF_PAGE ||
482-
current.backup_mode == BACKUP_MODE_DIFF_PTRACK)
482+
current.backup_mode == BACKUP_MODE_DIFF_PTRACK ||
483+
current.backup_mode == BACKUP_MODE_DIFF_DELTA)
483484
{
484485
parray *backup_list;
485486
/* get list of backups already taken */
@@ -495,7 +496,8 @@ do_backup_instance(void)
495496

496497
pgBackupGetPath(prev_backup, prev_backup_filelist_path, lengthof(prev_backup_filelist_path),
497498
DATABASE_FILE_LIST);
498-
prev_backup_filelist = dir_read_file_list(pgdata, prev_backup_filelist_path);
499+
/* Files of previous backup needed by DELTA backup */
500+
prev_backup_filelist = dir_read_file_list(NULL, prev_backup_filelist_path);
499501

500502
/* If lsn is not NULL, only pages with higher lsn will be copied. */
501503
prev_backup_start_lsn = prev_backup->start_lsn;
@@ -1945,6 +1947,25 @@ backup_files(void *arg)
19451947

19461948
if (S_ISREG(buf.st_mode))
19471949
{
1950+
/* Check that file exist in previous backup */
1951+
if (current.backup_mode == BACKUP_MODE_DIFF_DELTA)
1952+
{
1953+
int p;
1954+
char *relative;
1955+
int n_prev_backup_files_list = parray_num(arguments->prev_backup_filelist);
1956+
relative = GetRelativePath(file->path, arguments->from_root);
1957+
for (p = 0; p < n_prev_backup_files_list; p++)
1958+
{
1959+
pgFile *prev_file = (pgFile *) parray_get(arguments->prev_backup_filelist, p);
1960+
if (strcmp(relative, prev_file->path) == 0)
1961+
{
1962+
/* File exists in previous backup */
1963+
file->exists_in_prev = true;
1964+
elog(INFO, "File exists at the time of previous backup %s", relative);
1965+
break;
1966+
}
1967+
}
1968+
}
19481969
/* copy the file into backup */
19491970
if (file->is_datafile && !file->is_cfs)
19501971
{

src/catalog.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
#include <time.h>
2222
#include <unistd.h>
2323

24-
static const char *backupModes[] = {"", "PAGE", "PTRACK", "FULL"};
24+
static const char *backupModes[] = {"", "PAGE", "PTRACK", "DELTA", "FULL"};
2525
static pgBackup *readBackupControlFile(const char *path);
2626

2727
static bool exit_hook_registered = false;
@@ -593,6 +593,8 @@ parse_backup_mode(const char *value)
593593
return BACKUP_MODE_DIFF_PAGE;
594594
else if (len > 0 && pg_strncasecmp("ptrack", v, len) == 0)
595595
return BACKUP_MODE_DIFF_PTRACK;
596+
else if (len > 0 && pg_strncasecmp("delta", v, len) == 0)
597+
return BACKUP_MODE_DIFF_DELTA;
596598

597599
/* Backup mode is invalid, so leave with an error */
598600
elog(ERROR, "invalid backup-mode \"%s\"", value);
@@ -610,6 +612,8 @@ deparse_backup_mode(BackupMode mode)
610612
return "page";
611613
case BACKUP_MODE_DIFF_PTRACK:
612614
return "ptrack";
615+
case BACKUP_MODE_DIFF_DELTA:
616+
return "delta";
613617
case BACKUP_MODE_INVALID:
614618
return "invalid";
615619
}

src/data.c

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,10 @@ parse_page(Page page, XLogRecPtr *lsn)
132132
*/
133133
static int
134134
read_page_from_file(pgFile *file, BlockNumber blknum,
135-
FILE *in, Page page)
135+
FILE *in, Page page, XLogRecPtr *page_lsn)
136136
{
137137
off_t offset = blknum*BLCKSZ;
138138
size_t read_len = 0;
139-
XLogRecPtr page_lsn;
140139

141140
/* read the block */
142141
if (fseek(in, offset, SEEK_SET) != 0)
@@ -167,7 +166,7 @@ read_page_from_file(pgFile *file, BlockNumber blknum,
167166
* If after several attempts page header is still invalid, throw an error.
168167
* The same idea is applied to checksum verification.
169168
*/
170-
if (!parse_page(page, &page_lsn))
169+
if (!parse_page(page, page_lsn))
171170
{
172171
int i;
173172
/* Check if the page is zeroed. */
@@ -232,6 +231,7 @@ backup_data_page(backup_files_args *arguments,
232231
BackupPageHeader header;
233232
Page page = malloc(BLCKSZ);
234233
Page compressed_page = NULL;
234+
XLogRecPtr page_lsn = 0;
235235
size_t write_buffer_size;
236236
char write_buffer[BLCKSZ+sizeof(header)];
237237

@@ -252,7 +252,7 @@ backup_data_page(backup_files_args *arguments,
252252
while(!page_is_valid && try_again)
253253
{
254254
int result = read_page_from_file(file, blknum,
255-
in, page);
255+
in, page, &page_lsn);
256256

257257
try_again--;
258258
if (result == 0)
@@ -316,6 +316,25 @@ backup_data_page(backup_files_args *arguments,
316316
if (is_checksum_enabled)
317317
((PageHeader) page)->pd_checksum = pg_checksum_page(page, absolute_blknum);
318318
}
319+
/* get lsn from page, provided by pg_ptrack_get_block() */
320+
if (backup_mode == BACKUP_MODE_DIFF_DELTA &&
321+
file->exists_in_prev &&
322+
header.compressed_size != PageIsTruncated &&
323+
!parse_page(page, &page_lsn))
324+
elog(ERROR, "Cannot parse page after pg_ptrack_get_block. "
325+
"Possible risk of a memory corruption");
326+
327+
}
328+
329+
if (backup_mode == BACKUP_MODE_DIFF_DELTA &&
330+
file->exists_in_prev &&
331+
header.compressed_size != PageIsTruncated &&
332+
page_lsn < prev_backup_start_lsn)
333+
{
334+
elog(VERBOSE, "Skipping blknum: %u in file: %s", blknum, file->path);
335+
(*n_skipped)++;
336+
free(page);
337+
return;
319338
}
320339

321340
if (header.compressed_size != PageIsTruncated)
@@ -478,6 +497,8 @@ backup_data_file(backup_files_args* arguments,
478497
&n_blocks_skipped, backup_mode);
479498
n_blocks_read++;
480499
}
500+
if (backup_mode == BACKUP_MODE_DIFF_DELTA)
501+
file->n_blocks = n_blocks_read;
481502
}
482503
/* If page map is not empty we scan only changed blocks, */
483504
else
@@ -541,17 +562,22 @@ restore_data_file(const char *from_root,
541562
pgBackup *backup)
542563
{
543564
char to_path[MAXPGPATH];
544-
FILE *in;
545-
FILE *out;
565+
FILE *in = NULL;
566+
FILE *out = NULL;
546567
BackupPageHeader header;
547568
BlockNumber blknum;
569+
size_t file_size;
548570

549-
/* open backup mode file for read */
550-
in = fopen(file->path, "r");
551-
if (in == NULL)
571+
/* BYTES_INVALID allowed only in case of restoring file from DELTA backup */
572+
if (file->write_size != BYTES_INVALID)
552573
{
553-
elog(ERROR, "cannot open backup file \"%s\": %s", file->path,
554-
strerror(errno));
574+
/* open backup mode file for read */
575+
in = fopen(file->path, "r");
576+
if (in == NULL)
577+
{
578+
elog(ERROR, "cannot open backup file \"%s\": %s", file->path,
579+
strerror(errno));
580+
}
555581
}
556582

557583
/*
@@ -577,6 +603,10 @@ restore_data_file(const char *from_root,
577603
DataPage compressed_page; /* used as read buffer */
578604
DataPage page;
579605

606+
/* File didn`t changed. Nothig to copy */
607+
if (file->write_size == BYTES_INVALID)
608+
break;
609+
580610
/* read BackupPageHeader */
581611
read_len = fread(&header, 1, sizeof(header), in);
582612
if (read_len != sizeof(header))
@@ -652,12 +682,38 @@ restore_data_file(const char *from_root,
652682
}
653683
}
654684

685+
/*
686+
* DELTA backup have no knowledge about truncated blocks as PAGE or PTRACK do
687+
* But during DELTA backup we read every file in PGDATA and thus DELTA backup
688+
* knows exact size of every file at the time of backup.
689+
* So when restoring file from DELTA backup we, knowning it`s size at
690+
* a time of a backup, can truncate file to this size.
691+
*/
692+
693+
if (backup->backup_mode == BACKUP_MODE_DIFF_DELTA)
694+
{
695+
/* get file current size */
696+
fseek(out, 0, SEEK_END);
697+
file_size = ftell(out);
698+
if (file_size > file->n_blocks * BLCKSZ)
699+
{
700+
/*
701+
* Truncate file to this length.
702+
*/
703+
if (ftruncate(fileno(out), file->n_blocks * BLCKSZ) != 0)
704+
elog(ERROR, "cannot truncate \"%s\": %s",
705+
file->path, strerror(errno));
706+
elog(INFO, "Delta truncate file %s to block %u", file->path, file->n_blocks);
707+
}
708+
}
709+
655710
/* update file permission */
656711
if (chmod(to_path, file->mode) == -1)
657712
{
658713
int errno_tmp = errno;
659714

660-
fclose(in);
715+
if (in)
716+
fclose(in);
661717
fclose(out);
662718
elog(ERROR, "cannot change mode of \"%s\": %s", to_path,
663719
strerror(errno_tmp));
@@ -667,7 +723,8 @@ restore_data_file(const char *from_root,
667723
fsync(fileno(out)) != 0 ||
668724
fclose(out))
669725
elog(ERROR, "cannot write \"%s\": %s", to_path, strerror(errno));
670-
fclose(in);
726+
if (in)
727+
fclose(in);
671728
}
672729

673730
/*

src/dir.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ pgFileInit(const char *path)
166166
file->path = pgut_malloc(strlen(path) + 1);
167167
strcpy(file->path, path); /* enough buffer size guaranteed */
168168
file->is_cfs = false;
169+
file->exists_in_prev = false; /* can change only in Incremental backup. */
170+
file->n_blocks = -1; /* can change only in DELTA backup. Number of blocks readed during backup */
169171
file->compress_alg = NOT_DEFINED_COMPRESS;
170172
return file;
171173
}
@@ -721,6 +723,9 @@ print_file_list(FILE *out, const parray *files, const char *root)
721723
if (S_ISLNK(file->mode))
722724
fprintf(out, ",\"linked\":\"%s\"", file->linked);
723725

726+
if (file->n_blocks != -1)
727+
fprintf(out, ",\"n_blocks\":\"%i\"", file->n_blocks);
728+
724729
fprintf(out, "}\n");
725730
}
726731
}
@@ -888,7 +893,8 @@ dir_read_file_list(const char *root, const char *file_txt)
888893
is_datafile,
889894
is_cfs,
890895
crc,
891-
segno;
896+
segno,
897+
n_blocks;
892898
pgFile *file;
893899

894900
get_control_value(buf, "path", path, NULL, true);
@@ -902,6 +908,7 @@ dir_read_file_list(const char *root, const char *file_txt)
902908
get_control_value(buf, "linked", linked, NULL, false);
903909
get_control_value(buf, "segno", NULL, &segno, false);
904910
get_control_value(buf, "compress_alg", compress_alg_string, NULL, false);
911+
get_control_value(buf, "n_blocks", NULL, &n_blocks, false);
905912

906913
if (root)
907914
join_path_components(filepath, root, path);
@@ -919,6 +926,7 @@ dir_read_file_list(const char *root, const char *file_txt)
919926
if (linked[0])
920927
file->linked = pgut_strdup(linked);
921928
file->segno = (int) segno;
929+
file->n_blocks = (int) n_blocks;
922930

923931
parray_append(files, file);
924932
}

src/help.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ help_backup(void)
190190
printf(_(" [--replica-timeout=timeout]\n\n"));
191191

192192
printf(_(" -B, --backup-path=backup-path location of the backup storage area\n"));
193-
printf(_(" -b, --backup-mode=backup-mode backup mode=FULL|PAGE|PTRACK\n"));
193+
printf(_(" -b, --backup-mode=backup-mode backup mode=FULL|PAGE|DELTA|PTRACK\n"));
194194
printf(_(" --instance=instance_name name of the instance\n"));
195195
printf(_(" -C, --smooth-checkpoint do smooth checkpoint before backup\n"));
196196
printf(_(" --stream stream the transaction log and include it in the backup\n"));

src/pg_probackup.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,10 @@ typedef struct pgFile
9797
Oid relOid; /* relOid extracted from path, if applicable */
9898
char *forkName; /* forkName extracted from path, if applicable */
9999
int segno; /* Segment number for ptrack */
100+
int n_blocks; /* size of the file in blocks, readed during DELTA backup */
100101
bool is_cfs; /* Flag to distinguish files compressed by CFS*/
101102
bool is_database;
103+
bool exists_in_prev; /* Mark files, both data and regular, that exists in previous backup */
102104
CompressAlg compress_alg; /* compression algorithm applied to the file */
103105
volatile uint32 lock; /* lock for synchronization of parallel threads */
104106
datapagemap_t pagemap; /* bitmap of pages updated since previous backup */
@@ -126,7 +128,8 @@ typedef enum BackupMode
126128
{
127129
BACKUP_MODE_INVALID = 0,
128130
BACKUP_MODE_DIFF_PAGE, /* incremental page backup */
129-
BACKUP_MODE_DIFF_PTRACK, /* incremental page backup with ptrack system*/
131+
BACKUP_MODE_DIFF_PTRACK, /* incremental page backup with ptrack system */
132+
BACKUP_MODE_DIFF_DELTA, /* incremental page backup with lsn comparison */
130133
BACKUP_MODE_FULL /* full backup */
131134
} BackupMode;
132135

src/restore.c

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -378,17 +378,6 @@ restore_backup(pgBackup *backup)
378378
pgBackupGetPath(backup, database_path, lengthof(database_path), DATABASE_DIR);
379379
pgBackupGetPath(backup, list_path, lengthof(list_path), DATABASE_FILE_LIST);
380380
files = dir_read_file_list(database_path, list_path);
381-
for (i = parray_num(files) - 1; i >= 0; i--)
382-
{
383-
pgFile *file = (pgFile *) parray_get(files, i);
384-
385-
/*
386-
* Remove files which haven't changed since previous backup
387-
* and was not backed up
388-
*/
389-
if (file->write_size == BYTES_INVALID)
390-
pgFileFree(parray_remove(files, i));
391-
}
392381

393382
/* setup threads */
394383
for (i = 0; i < parray_num(files); i++)
@@ -716,24 +705,31 @@ restore_files(void *arg)
716705
i + 1, (unsigned long) parray_num(arguments->files), rel_path);
717706

718707

719-
/* Directories was created before */
720-
if (S_ISDIR(file->mode))
708+
/*
709+
* For PAGE and PTRACK backups skip files which haven't changed
710+
* since previous backup and thus were not backed up.
711+
* We cannot do the same when restoring DELTA backup because we need information
712+
* about every file to correctly truncate them.
713+
*/
714+
if (file->write_size == BYTES_INVALID &&
715+
(arguments->backup->backup_mode == BACKUP_MODE_DIFF_PAGE
716+
|| arguments->backup->backup_mode == BACKUP_MODE_DIFF_PTRACK))
721717
{
722-
elog(LOG, "directory, skip");
718+
elog(VERBOSE, "The file didn`t changed. Skip restore: %s", file->path);
723719
continue;
724720
}
725721

726-
/* not backed up */
727-
if (file->write_size == BYTES_INVALID)
722+
/* Directories was created before */
723+
if (S_ISDIR(file->mode))
728724
{
729-
elog(LOG, "not backed up, skip");
725+
elog(VERBOSE, "directory, skip");
730726
continue;
731727
}
732728

733729
/* Do not restore tablespace_map file */
734730
if (path_is_prefix_of_path(PG_TABLESPACE_MAP_FILE, rel_path))
735731
{
736-
elog(LOG, "skip tablespace_map");
732+
elog(VERBOSE, "skip tablespace_map");
737733
continue;
738734
}
739735

@@ -750,8 +746,9 @@ restore_files(void *arg)
750746
copy_file(from_root, pgdata, file);
751747

752748
/* print size of restored file */
753-
elog(LOG, "Restored file %s : %lu bytes",
754-
file->path, (unsigned long) file->write_size);
749+
if (file->write_size != BYTES_INVALID)
750+
elog(LOG, "Restored file %s : %lu bytes",
751+
file->path, (unsigned long) file->write_size);
755752
}
756753
}
757754

src/validate.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ pgBackupValidate(pgBackup *backup)
5151

5252
if (backup->backup_mode != BACKUP_MODE_FULL &&
5353
backup->backup_mode != BACKUP_MODE_DIFF_PAGE &&
54-
backup->backup_mode != BACKUP_MODE_DIFF_PTRACK)
54+
backup->backup_mode != BACKUP_MODE_DIFF_PTRACK &&
55+
backup->backup_mode != BACKUP_MODE_DIFF_DELTA)
5556
elog(INFO, "Invalid backup_mode of backup %s", base36enc(backup->start_time));
5657

5758
pgBackupGetPath(backup, base_path, lengthof(base_path), DATABASE_DIR);

0 commit comments

Comments
 (0)