Skip to content

Commit 4e44502

Browse files
committed
[Issue #280] Expand "--force" flag for incremental restore, now in case of system ID mismatch the destination PGDATA will be deleted; the content of the directory, used as destination for tablespace remapping, is now also deleted. Tablespace map is now validated before reading.
1 parent b16555a commit 4e44502

File tree

12 files changed

+964
-126
lines changed

12 files changed

+964
-126
lines changed

doc/pgprobackup.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3976,6 +3976,10 @@ pg_probackup restore -B <replaceable>backup_dir</replaceable> --instance <replac
39763976
this flag if you need to restore the
39773977
<productname>PostgreSQL</productname> cluster from a corrupt or an invalid backup.
39783978
Use with caution.
3979+
When used with <link linkend="pbk-incremental-restore">incremental restore</link> this flag
3980+
allows to replace already existing PGDATA with different system ID. In case of tablespaces,
3981+
remapped via <literal>--tablespace-mapping</literal> option into not empty directories,
3982+
the old content of such directories will be deleted.
39793983
</para>
39803984
</listitem>
39813985
</varlistentry>

src/backup.c

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -286,12 +286,8 @@ do_backup_instance(PGconn *backup_conn, PGNodeInfo *nodeInfo, bool no_sync, bool
286286
join_path_components(external_prefix, current.root_dir, EXTERNAL_DIR);
287287

288288
/* list files with the logical path. omit $PGDATA */
289-
if (fio_is_remote(FIO_DB_HOST))
290-
fio_list_dir(backup_files_list, instance_config.pgdata,
291-
true, true, false, backup_logs, true, 0);
292-
else
293-
dir_list_file(backup_files_list, instance_config.pgdata,
294-
true, true, false, backup_logs, true, 0, FIO_LOCAL_HOST);
289+
fio_list_dir(backup_files_list, instance_config.pgdata,
290+
true, true, false, backup_logs, true, 0);
295291

296292
/*
297293
* Get database_map (name to oid) for use in partial restore feature.

src/delete.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ do_retention_wal(bool dry_run)
720720
/*
721721
* Delete backup files of the backup and update the status of the backup to
722722
* BACKUP_STATUS_DELETED.
723+
* TODO: delete files on multiple threads
723724
*/
724725
void
725726
delete_backup_files(pgBackup *backup)

src/dir.c

Lines changed: 142 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ static void dir_list_file_internal(parray *files, pgFile *parent, const char *pa
130130
bool skip_hidden, int external_dir_num, fio_location location);
131131
static void opt_path_map(ConfigOption *opt, const char *arg,
132132
TablespaceList *list, const char *type);
133+
static void cleanup_tablespace(const char *path);
133134

134135
/* Tablespace mapping */
135136
static TablespaceList tablespace_dirs = {NULL, NULL};
@@ -518,6 +519,8 @@ db_map_entry_free(void *entry)
518519
*
519520
* When follow_symlink is true, symbolic link is ignored and only file or
520521
* directory linked to will be listed.
522+
*
523+
* TODO: make it strictly local
521524
*/
522525
void
523526
dir_list_file(parray *files, const char *root, bool exclude, bool follow_symlink,
@@ -1088,7 +1091,7 @@ create_data_directories(parray *dest_files, const char *data_dir, const char *ba
10881091
const char *linked_path = get_tablespace_mapping((*link)->linked);
10891092

10901093
if (!is_absolute_path(linked_path))
1091-
elog(ERROR, "Tablespace directory is not an absolute path: %s\n",
1094+
elog(ERROR, "Tablespace directory path must be an absolute path: %s\n",
10921095
linked_path);
10931096

10941097
join_path_components(to_path, data_dir, dir->rel_path);
@@ -1128,7 +1131,7 @@ create_data_directories(parray *dest_files, const char *data_dir, const char *ba
11281131
* tablespace_map or tablespace_map.txt.
11291132
*/
11301133
void
1131-
read_tablespace_map(parray *files, const char *backup_dir)
1134+
read_tablespace_map(parray *links, const char *backup_dir)
11321135
{
11331136
FILE *fp;
11341137
char db_path[MAXPGPATH],
@@ -1138,16 +1141,9 @@ read_tablespace_map(parray *files, const char *backup_dir)
11381141
join_path_components(db_path, backup_dir, DATABASE_DIR);
11391142
join_path_components(map_path, db_path, PG_TABLESPACE_MAP_FILE);
11401143

1141-
/* Exit if database/tablespace_map doesn't exist */
1142-
if (!fileExists(map_path, FIO_BACKUP_HOST))
1143-
{
1144-
elog(LOG, "there is no file tablespace_map");
1145-
return;
1146-
}
1147-
11481144
fp = fio_open_stream(map_path, FIO_BACKUP_HOST);
11491145
if (fp == NULL)
1150-
elog(ERROR, "cannot open \"%s\": %s", map_path, strerror(errno));
1146+
elog(ERROR, "Cannot open tablespace map file \"%s\": %s", map_path, strerror(errno));
11511147

11521148
while (fgets(buf, lengthof(buf), fp))
11531149
{
@@ -1166,7 +1162,7 @@ read_tablespace_map(parray *files, const char *backup_dir)
11661162
file->linked = pgut_strdup(path);
11671163
canonicalize_path(file->linked);
11681164

1169-
parray_append(files, file);
1165+
parray_append(links, file);
11701166
}
11711167

11721168
if (ferror(fp))
@@ -1183,30 +1179,49 @@ read_tablespace_map(parray *files, const char *backup_dir)
11831179
* If tablespace-mapping option is supplied, all OLDDIR entries must have
11841180
* entries in tablespace_map file.
11851181
*
1186-
*
1187-
* TODO: maybe when running incremental restore with tablespace remapping, then
1188-
* new tablespace directory MUST be empty? because there is no way
1182+
* When running incremental restore with tablespace remapping, then
1183+
* new tablespace directory MUST be empty, because there is no way
11891184
* we can be sure, that files laying there belong to our instance.
1185+
* But "force" flag allows to ignore this condition, by wiping out
1186+
* the current content on the directory.
1187+
*
1188+
* Exit codes:
1189+
* 1. backup has no tablespaces
1190+
* 2. backup has tablespaces and they are empty
1191+
* 3. backup has tablespaces and some of them are not empty
11901192
*/
1191-
void
1192-
check_tablespace_mapping(pgBackup *backup, bool incremental, bool *tblspaces_are_empty)
1193+
int
1194+
check_tablespace_mapping(pgBackup *backup, bool incremental, bool force, bool pgdata_is_empty)
11931195
{
1194-
// char this_backup_path[MAXPGPATH];
1195-
parray *links;
1196+
parray *links = parray_new();
11961197
size_t i;
11971198
TablespaceListCell *cell;
11981199
pgFile *tmp_file = pgut_new(pgFile);
1200+
bool tblspaces_are_empty = true;
11991201

1200-
links = parray_new();
1202+
elog(LOG, "Checking tablespace directories of backup %s",
1203+
base36enc(backup->start_time));
1204+
1205+
/* validate tablespace map,
1206+
* if there are no tablespaces, then there is nothing left to do
1207+
*/
1208+
if (!validate_tablespace_map(backup))
1209+
{
1210+
/*
1211+
* Sanity check
1212+
* If there is no tablespaces in backup,
1213+
* then using the '--tablespace-mapping' option is a mistake.
1214+
*/
1215+
if (tablespace_dirs.head != NULL)
1216+
elog(ERROR, "Backup %s has no tablespaceses, nothing to remap "
1217+
"via \"--tablespace-mapping\" option", base36enc(backup->backup_id));
1218+
return NoTblspc;
1219+
}
12011220

1202-
// pgBackupGetPath(backup, this_backup_path, lengthof(this_backup_path), NULL);
12031221
read_tablespace_map(links, backup->root_dir);
12041222
/* Sort links by the path of a linked file*/
12051223
parray_qsort(links, pgFileCompareLinked);
12061224

1207-
elog(LOG, "check tablespace directories of backup %s",
1208-
base36enc(backup->start_time));
1209-
12101225
/* 1 - each OLDDIR must have an entry in tablespace_map file (links) */
12111226
for (cell = tablespace_dirs.head; cell; cell = cell->next)
12121227
{
@@ -1216,52 +1231,109 @@ check_tablespace_mapping(pgBackup *backup, bool incremental, bool *tblspaces_are
12161231
elog(ERROR, "--tablespace-mapping option's old directory "
12171232
"doesn't have an entry in tablespace_map file: \"%s\"",
12181233
cell->old_dir);
1219-
1220-
/* For incremental restore, check that new directory is empty */
1221-
// if (incremental)
1222-
// {
1223-
// if (!is_absolute_path(cell->new_dir))
1224-
// elog(ERROR, "tablespace directory is not an absolute path: %s\n",
1225-
// cell->new_dir);
1226-
//
1227-
// if (!dir_is_empty(cell->new_dir, FIO_DB_HOST))
1228-
// elog(ERROR, "restore tablespace destination is not empty: \"%s\"",
1229-
// cell->new_dir);
1230-
// }
12311234
}
12321235

1236+
/*
1237+
* There is difference between incremental restore of already existing
1238+
* tablespaceses and remapped tablespaceses.
1239+
* Former are allowed to be not empty, because we treat them like an
1240+
* extension of PGDATA.
1241+
* The latter are not, unless "--force" flag is used.
1242+
* in which case the remapped directory is nuked - just to be safe,
1243+
* because it is hard to be sure that there are no some tricky corner
1244+
* cases of pages from different systems having the same crc.
1245+
* This is a strict approach.
1246+
*
1247+
* Why can`t we not nuke it and just let it roll ?
1248+
* What if user just wants to rerun failed restore with the same
1249+
* parameters? Nuking is bad for this case.
1250+
*
1251+
* Consider the example of existing PGDATA:
1252+
* ....
1253+
* pg_tablespace
1254+
* 100500-> /somedirectory
1255+
* ....
1256+
*
1257+
* We want to remap it during restore like that:
1258+
* ....
1259+
* pg_tablespace
1260+
* 100500-> /somedirectory1
1261+
* ....
1262+
*
1263+
* Usually it is required for "/somedirectory1" to be empty, but
1264+
* in case of incremental restore with 'force' flag, which required
1265+
* of us to drop already existing content of "/somedirectory1".
1266+
*
1267+
* TODO: Ideally in case of incremental restore we must also
1268+
* drop the "/somedirectory" directory first, but currently
1269+
* we don`t do that.
1270+
*/
1271+
12331272
/* 2 - all linked directories must be empty */
12341273
for (i = 0; i < parray_num(links); i++)
12351274
{
12361275
pgFile *link = (pgFile *) parray_get(links, i);
12371276
const char *linked_path = link->linked;
12381277
TablespaceListCell *cell;
1278+
bool remapped = false;
12391279

12401280
for (cell = tablespace_dirs.head; cell; cell = cell->next)
12411281
if (strcmp(link->linked, cell->old_dir) == 0)
12421282
{
12431283
linked_path = cell->new_dir;
1284+
remapped = true;
12441285
break;
12451286
}
12461287

12471288
if (!is_absolute_path(linked_path))
1248-
elog(ERROR, "tablespace directory is not an absolute path: %s\n",
1289+
elog(ERROR, "Tablespace directory path must be an absolute path: %s\n",
12491290
linked_path);
12501291

12511292
if (!dir_is_empty(linked_path, FIO_DB_HOST))
12521293
{
1294+
12531295
if (!incremental)
1254-
elog(ERROR, "restore tablespace destination is not empty: \"%s\"",
1255-
linked_path);
1256-
*tblspaces_are_empty = false;
1296+
elog(ERROR, "Restore tablespace destination is not empty: \"%s\"", linked_path);
1297+
1298+
else if (remapped && !force)
1299+
elog(ERROR, "Remapped tablespace destination is not empty: \"%s\". "
1300+
"Use \"--force\" flag if you want to automatically clean up the "
1301+
"content of new tablespace destination",
1302+
linked_path);
1303+
1304+
else if (pgdata_is_empty && !force)
1305+
elog(ERROR, "PGDATA is empty, but tablespace destination is not: \"%s\". "
1306+
"Use \"--force\" flag is you want to automatically clean up the "
1307+
"content of tablespace destination",
1308+
linked_path);
1309+
1310+
/*
1311+
* TODO: compile the list of tblspc Oids to delete later,
1312+
* similar to what we do with database_map.
1313+
*/
1314+
else if (force && (pgdata_is_empty || remapped))
1315+
{
1316+
elog(WARNING, "Cleaning up the content of %s directory: \"%s\"",
1317+
remapped ? "remapped tablespace" : "tablespace", linked_path);
1318+
cleanup_tablespace(linked_path);
1319+
continue;
1320+
}
1321+
1322+
tblspaces_are_empty = false;
12571323
}
12581324
}
12591325

12601326
free(tmp_file);
12611327
parray_walk(links, pgFileFree);
12621328
parray_free(links);
1329+
1330+
if (tblspaces_are_empty)
1331+
return EmptyTblspc;
1332+
1333+
return NotEmptyTblspc;
12631334
}
12641335

1336+
/* TODO: Make it consistent with check_tablespace_mapping */
12651337
void
12661338
check_external_dir_mapping(pgBackup *backup, bool incremental)
12671339
{
@@ -1854,3 +1926,34 @@ read_database_map(pgBackup *backup)
18541926

18551927
return database_map;
18561928
}
1929+
1930+
/*
1931+
* Use it to cleanup tablespaces
1932+
* TODO: Current algorihtm is not very efficient in remote mode,
1933+
* due to round-trip to delete every file.
1934+
*/
1935+
void
1936+
cleanup_tablespace(const char *path)
1937+
{
1938+
int i;
1939+
char fullpath[MAXPGPATH];
1940+
parray *files = parray_new();
1941+
1942+
fio_list_dir(files, path, false, false, false, false, false, 0);
1943+
1944+
/* delete leaf node first */
1945+
parray_qsort(files, pgFileCompareRelPathWithExternalDesc);
1946+
1947+
for (i = 0; i < parray_num(files); i++)
1948+
{
1949+
pgFile *file = (pgFile *) parray_get(files, i);
1950+
1951+
join_path_components(fullpath, path, file->rel_path);
1952+
1953+
fio_delete(file->mode, fullpath, FIO_DB_HOST);
1954+
elog(VERBOSE, "Deleted file \"%s\"", fullpath);
1955+
}
1956+
1957+
parray_walk(files, pgFileFree);
1958+
parray_free(files);
1959+
}

src/pg_probackup.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ typedef struct db_map_entry
132132
char *datname;
133133
} db_map_entry;
134134

135+
/* State of pgdata in the context of its compatibility for incremental restore */
136+
typedef enum DestDirIncrCompatibility
137+
{
138+
POSTMASTER_IS_RUNNING,
139+
SYSTEM_ID_MISMATCH,
140+
BACKUP_LABEL_EXISTS,
141+
DEST_IS_NOT_OK,
142+
DEST_OK
143+
} DestDirIncrCompatibility;
144+
135145
typedef enum IncrRestoreMode
136146
{
137147
INCR_NONE,
@@ -250,6 +260,11 @@ typedef struct page_map_entry
250260
/* Special values of datapagemap_t bitmapsize */
251261
#define PageBitmapIsEmpty 0 /* Used to mark unchanged datafiles */
252262

263+
/* Return codes for check_tablespace_mapping */
264+
#define NoTblspc 0
265+
#define EmptyTblspc 1
266+
#define NotEmptyTblspc 2
267+
253268
/* Current state of backup */
254269
typedef enum BackupStatus
255270
{
@@ -868,6 +883,7 @@ extern int do_validate_all(void);
868883
extern int validate_one_page(Page page, BlockNumber absolute_blkno,
869884
XLogRecPtr stop_lsn, PageState *page_st,
870885
uint32 checksum_version);
886+
extern bool validate_tablespace_map(pgBackup *backup);
871887

872888
/* return codes for validate_one_page */
873889
/* TODO: use enum */
@@ -957,10 +973,10 @@ extern void create_data_directories(parray *dest_files,
957973
bool incremental,
958974
fio_location location);
959975

960-
extern void read_tablespace_map(parray *files, const char *backup_dir);
976+
extern void read_tablespace_map(parray *links, const char *backup_dir);
961977
extern void opt_tablespace_map(ConfigOption *opt, const char *arg);
962978
extern void opt_externaldir_map(ConfigOption *opt, const char *arg);
963-
extern void check_tablespace_mapping(pgBackup *backup, bool incremental, bool *tblspaces_are_empty);
979+
extern int check_tablespace_mapping(pgBackup *backup, bool incremental, bool force, bool pgdata_is_empty);
964980
extern void check_external_dir_mapping(pgBackup *backup, bool incremental);
965981
extern char *get_external_remap(char *current_dir);
966982

0 commit comments

Comments
 (0)