diff --git a/src/archive.c b/src/archive.c index 0f32d9345..6fc920a1b 100644 --- a/src/archive.c +++ b/src/archive.c @@ -15,14 +15,17 @@ static int push_file_internal_uncompressed(const char *wal_file_name, const char *pg_xlog_dir, const char *archive_dir, bool overwrite, bool no_sync, - uint32 archive_timeout); + uint32 archive_timeout, xlogFileType type); #ifdef HAVE_LIBZ static int push_file_internal_gz(const char *wal_file_name, const char *pg_xlog_dir, - const char *archive_dir, bool overwrite, bool no_sync, - int compress_level, uint32 archive_timeout); + const char *archive_dir, bool overwrite, bool no_sync, + int compress_level, uint32 archive_timeout, xlogFileType type); #endif static void *push_files(void *arg); static void *get_files(void *arg); +static bool +get_wal_file_wrapper(const char *filename, const char *archive_root_dir, + const char *to_fullpath, bool prefetch_mode); static bool get_wal_file(const char *filename, const char *from_path, const char *to_path, bool prefetch_mode); static int get_wal_file_internal(const char *from_path, const char *to_path, FILE *out, @@ -89,8 +92,9 @@ typedef struct typedef struct WALSegno { - char name[MAXFNAMELEN]; - volatile pg_atomic_flag lock; + char name[MAXFNAMELEN]; + volatile pg_atomic_flag lock; + xlogFileType type; } WALSegno; static int push_file(WALSegno *xlogfile, const char *archive_status_dir, @@ -101,6 +105,29 @@ static int push_file(WALSegno *xlogfile, const char *archive_status_dir, static parray *setup_push_filelist(const char *archive_status_dir, const char *first_file, int batch_size); +static parray *setup_archive_subdirs(parray *batch_files, const char *archive_dir); + +static xlogFileType +get_xlogFileType(const char *filename) +{ + + if IsXLogFileName(filename) + return SEGMENT; + + else if IsPartialXLogFileName(filename) + return PARTIAL_SEGMENT; + + else if IsBackupHistoryFileName(filename) + return BACKUP_HISTORY_FILE; + + else if IsTLHistoryFileName(filename) + return HISTORY_FILE; + + else if IsBackupHistoryFileName(filename) + return BACKUP_HISTORY_FILE; + + return UNKNOWN; +} /* * At this point, we already done one roundtrip to archive server @@ -137,6 +164,7 @@ do_archive_push(InstanceState *instanceState, InstanceConfig *instance, char *pg /* files to push in multi-thread mode */ parray *batch_files = NULL; + parray *archive_subdirs = NULL; int n_threads; if (!no_ready_rename || batch_size > 1) @@ -160,6 +188,20 @@ do_archive_push(InstanceState *instanceState, InstanceConfig *instance, char *pg parray_num(batch_files), batch_size, is_compress ? "zlib" : "none"); + /* Extract subdirectories */ + archive_subdirs = setup_archive_subdirs(batch_files, instanceState->instance_wal_subdir_path); + if (archive_subdirs) + { + for (i = 0; i < parray_num(archive_subdirs); i++) + { + char *subdir = (char *) parray_get(archive_subdirs, i); + if (fio_mkdir(subdir, DIR_PERMISSION, FIO_BACKUP_HOST) != 0) + elog(ERROR, "Cannot create subdirectory in WAL archive: '%s'", subdir); + pg_free(subdir); + } + parray_free(archive_subdirs); + } + num_threads = n_threads; /* Single-thread push @@ -339,12 +381,12 @@ push_file(WALSegno *xlogfile, const char *archive_status_dir, if (!is_compress) rc = push_file_internal_uncompressed(xlogfile->name, pg_xlog_dir, archive_dir, overwrite, no_sync, - archive_timeout); + archive_timeout, xlogfile->type); #ifdef HAVE_LIBZ else rc = push_file_internal_gz(xlogfile->name, pg_xlog_dir, archive_dir, overwrite, no_sync, compress_level, - archive_timeout); + archive_timeout, xlogfile->type); #endif /* take '--no-ready-rename' flag into account */ @@ -383,13 +425,14 @@ push_file(WALSegno *xlogfile, const char *archive_status_dir, int push_file_internal_uncompressed(const char *wal_file_name, const char *pg_xlog_dir, const char *archive_dir, bool overwrite, bool no_sync, - uint32 archive_timeout) + uint32 archive_timeout, xlogFileType type) { FILE *in = NULL; int out = -1; char *buf = pgut_malloc(OUT_BUF_SIZE); /* 1MB buffer */ char from_fullpath[MAXPGPATH]; char to_fullpath[MAXPGPATH]; + char archive_subdir[MAXPGPATH]; /* partial handling */ struct stat st; char to_fullpath_part[MAXPGPATH]; @@ -402,8 +445,12 @@ push_file_internal_uncompressed(const char *wal_file_name, const char *pg_xlog_d /* from path */ join_path_components(from_fullpath, pg_xlog_dir, wal_file_name); canonicalize_path(from_fullpath); + + /* calculate subdir in WAL archive */ + get_archive_subdir(archive_subdir, archive_dir, wal_file_name, type); + /* to path */ - join_path_components(to_fullpath, archive_dir, wal_file_name); + join_path_components(to_fullpath, archive_subdir, wal_file_name); canonicalize_path(to_fullpath); /* Open source file for read */ @@ -622,7 +669,7 @@ push_file_internal_uncompressed(const char *wal_file_name, const char *pg_xlog_d int push_file_internal_gz(const char *wal_file_name, const char *pg_xlog_dir, const char *archive_dir, bool overwrite, bool no_sync, - int compress_level, uint32 archive_timeout) + int compress_level, uint32 archive_timeout, xlogFileType type) { FILE *in = NULL; gzFile out = NULL; @@ -630,6 +677,7 @@ push_file_internal_gz(const char *wal_file_name, const char *pg_xlog_dir, char from_fullpath[MAXPGPATH]; char to_fullpath[MAXPGPATH]; char to_fullpath_gz[MAXPGPATH]; + char archive_subdir[MAXPGPATH]; /* partial handling */ struct stat st; @@ -644,8 +692,12 @@ push_file_internal_gz(const char *wal_file_name, const char *pg_xlog_dir, /* from path */ join_path_components(from_fullpath, pg_xlog_dir, wal_file_name); canonicalize_path(from_fullpath); + + /* calculate subdir in WAL archive */ + get_archive_subdir(archive_subdir, archive_dir, wal_file_name, type); + /* to path */ - join_path_components(to_fullpath, archive_dir, wal_file_name); + join_path_components(to_fullpath, archive_subdir, wal_file_name); canonicalize_path(to_fullpath); /* destination file with .gz suffix */ @@ -915,8 +967,8 @@ setup_push_filelist(const char *archive_status_dir, const char *first_file, { int i; WALSegno *xlogfile = NULL; - parray *status_files = NULL; - parray *batch_files = parray_new(); + parray *status_files = NULL; + parray *batch_files = parray_new(); /* guarantee that first filename is in batch list */ xlogfile = palloc(sizeof(WALSegno)); @@ -924,6 +976,8 @@ setup_push_filelist(const char *archive_status_dir, const char *first_file, snprintf(xlogfile->name, MAXFNAMELEN, "%s", first_file); parray_append(batch_files, xlogfile); + xlogfile->type = get_xlogFileType(xlogfile->name); + if (batch_size < 2) return batch_files; @@ -955,6 +1009,8 @@ setup_push_filelist(const char *archive_status_dir, const char *first_file, pg_atomic_init_flag(&xlogfile->lock); snprintf(xlogfile->name, MAXFNAMELEN, "%s", filename); + + xlogfile->type = get_xlogFileType(xlogfile->name); parray_append(batch_files, xlogfile); if (parray_num(batch_files) >= batch_size) @@ -1023,7 +1079,7 @@ do_archive_get(InstanceState *instanceState, InstanceConfig *instance, const cha /* full filepath to WAL file in archive directory. * $BACKUP_PATH/wal/instance_name/000000010000000000000001 */ - join_path_components(backup_wal_file_path, instanceState->instance_wal_subdir_path, wal_file_name); + //join_path_components(backup_wal_file_path, instanceState->instance_wal_subdir_path, wal_file_name); INSTR_TIME_SET_CURRENT(start_time); if (num_threads > batch_size) @@ -1152,7 +1208,7 @@ do_archive_get(InstanceState *instanceState, InstanceConfig *instance, const cha while (fail_count < 3) { - if (get_wal_file(wal_file_name, backup_wal_file_path, absolute_wal_file_path, false)) + if (get_wal_file_wrapper(wal_file_name, instanceState->instance_wal_subdir_path, absolute_wal_file_path, false)) { fail_count = 0; elog(INFO, "pg_probackup archive-get copied WAL file %s", wal_file_name); @@ -1235,7 +1291,7 @@ uint32 run_wal_prefetch(const char *prefetch_dir, const char *archive_dir, /* It is ok, maybe requested batch is greater than the number of available * files in the archive */ - if (!get_wal_file(xlogfile->name, from_fullpath, to_fullpath, true)) + if (!get_wal_file_wrapper(xlogfile->name, archive_dir, to_fullpath, true)) { elog(LOG, "Thread [%d]: Failed to prefetch WAL segment %s", 0, xlogfile->name); break; @@ -1309,7 +1365,7 @@ get_files(void *arg) join_path_components(from_fullpath, args->archive_dir, xlogfile->name); join_path_components(to_fullpath, args->prefetch_dir, xlogfile->name); - if (!get_wal_file(xlogfile->name, from_fullpath, to_fullpath, true)) + if (!get_wal_file_wrapper(xlogfile->name, args->archive_dir, to_fullpath, true)) { /* It is ok, maybe requested batch is greater than the number of available * files in the archive @@ -1328,6 +1384,38 @@ get_files(void *arg) return NULL; } +/* + * First we try to copy from WAL archive subdirectory: + * Failing that, try WAL archive root directory + */ +bool +get_wal_file_wrapper(const char *filename, const char *archive_root_dir, + const char *to_fullpath, bool prefetch_mode) +{ + bool success = false; + char archive_subdir[MAXPGPATH]; + char from_fullpath[MAXPGPATH]; + xlogFileType type = get_xlogFileType(filename); + + if (type == SEGMENT || type == PARTIAL_SEGMENT || type == BACKUP_HISTORY_FILE) + { + /* first try subdir ... */ + get_archive_subdir(archive_subdir, archive_root_dir, filename, type); + join_path_components(from_fullpath, archive_subdir, filename); + + success = get_wal_file(filename, from_fullpath, to_fullpath, prefetch_mode); + } + + if (!success) + { + /* ... fallback to archive dir for backward compatibility purposes */ + join_path_components(from_fullpath, archive_root_dir, filename); + success = get_wal_file(filename, from_fullpath, to_fullpath, prefetch_mode); + } + + return success; +} + /* * Copy WAL segment from archive catalog to pgdata with possible decompression. * When running in prefetch mode, we should not error out. @@ -1730,3 +1818,68 @@ uint32 maintain_prefetch(const char *prefetch_dir, XLogSegNo first_segno, uint32 return n_files; } + +/* Calculate subdir path in WAL archive directory. Example: + * 000000010000000200000013 -> 00000002 + */ +void +get_archive_subdir(char *archive_subdir, const char *archive_dir, const char *wal_file_name, xlogFileType type) +{ + if (type == SEGMENT || type == PARTIAL_SEGMENT || type == BACKUP_HISTORY_FILE) + { + int rc = 0; + char tli[MAXFNAMELEN]; + char log[MAXFNAMELEN]; + char suffix[MAXFNAMELEN]; + + rc = sscanf(wal_file_name, "%08s%08s%s", + (char *) &tli, (char *) &log, (char *) &suffix); + + if (rc == 3) + { + join_path_components(archive_subdir, archive_dir, log); + return; + } + } + + /* for all other files just use root directory of WAL archive */ + strcpy(archive_subdir, archive_dir); +} + +/* Extract array of WAL archive subdirs using push filelist */ +parray* +setup_archive_subdirs(parray *batch_files, const char *archive_dir) +{ + int i; + parray *subdirs = NULL; + char *cur_subdir = NULL; + + /* + * - Do we need to sort batch_files? + * - No, we rely on sorting of status files + */ + + for (i = 0; i < parray_num(batch_files); i++) + { + WALSegno *xlogfile = (WALSegno *) parray_get(batch_files, i); + + if (xlogfile->type == SEGMENT || xlogfile->type == PARTIAL_SEGMENT || xlogfile->type == BACKUP_HISTORY_FILE) + { + char subdir[MAXPGPATH]; + + if (!subdirs) + subdirs = parray_new(); + + get_archive_subdir(subdir, archive_dir, xlogfile->name, xlogfile->type); + + /* do not append the same subdir twice */ + if (cur_subdir && strcmp(cur_subdir, subdir) == 0) + continue; + + cur_subdir = pgut_strdup(subdir); + parray_append(subdirs, cur_subdir); + } + } + + return subdirs; +} diff --git a/src/backup.c b/src/backup.c index c575865c4..af45cac92 100644 --- a/src/backup.c +++ b/src/backup.c @@ -1237,17 +1237,12 @@ wait_wal_lsn(const char *wal_segment_dir, XLogRecPtr target_lsn, bool is_start_l int timeout_elevel, bool in_stream_dir) { XLogSegNo targetSegNo; - char wal_segment_path[MAXPGPATH], + char wal_segment_path[MAXPGPATH], /* used only for reporting */ wal_segment[MAXFNAMELEN]; - bool file_exists = false; uint32 try_count = 0, timeout; char *wal_delivery_str = in_stream_dir ? "streamed":"archived"; -#ifdef HAVE_LIBZ - char gz_wal_segment_path[MAXPGPATH]; -#endif - /* Compute the name of the WAL file containing requested LSN */ GetXLogSegNo(target_lsn, targetSegNo, instance_config.xlog_seg_size); if (in_prev_segment) @@ -1255,7 +1250,16 @@ wait_wal_lsn(const char *wal_segment_dir, XLogRecPtr target_lsn, bool is_start_l GetXLogFileName(wal_segment, tli, targetSegNo, instance_config.xlog_seg_size); - join_path_components(wal_segment_path, wal_segment_dir, wal_segment); + // obtain WAL archive subdir for ARCHIVE backup + if (in_stream_dir) + join_path_components(wal_segment_path, wal_segment_dir, wal_segment); + else + { + char wal_segment_subdir[MAXPGPATH]; + get_archive_subdir(wal_segment_subdir, wal_segment_dir, wal_segment, SEGMENT); + join_path_components(wal_segment_path, wal_segment_subdir, wal_segment); + } + /* * In pg_start_backup we wait for 'target_lsn' in 'pg_wal' directory if it is * stream and non-page backup. Page backup needs archived WAL files, so we @@ -1276,30 +1280,10 @@ wait_wal_lsn(const char *wal_segment_dir, XLogRecPtr target_lsn, bool is_start_l elog(LOG, "Looking for LSN %X/%X in segment: %s", (uint32) (target_lsn >> 32), (uint32) target_lsn, wal_segment); -#ifdef HAVE_LIBZ - snprintf(gz_wal_segment_path, sizeof(gz_wal_segment_path), "%s.gz", - wal_segment_path); -#endif - /* Wait until target LSN is archived or streamed */ while (true) { - if (!file_exists) - { - file_exists = fileExists(wal_segment_path, FIO_BACKUP_HOST); - - /* Try to find compressed WAL file */ - if (!file_exists) - { -#ifdef HAVE_LIBZ - file_exists = fileExists(gz_wal_segment_path, FIO_BACKUP_HOST); - if (file_exists) - elog(LOG, "Found compressed WAL segment: %s", wal_segment_path); -#endif - } - else - elog(LOG, "Found WAL segment: %s", wal_segment_path); - } + bool file_exists = IsWalFileExists(wal_segment, wal_segment_dir, in_stream_dir); if (file_exists) { @@ -1312,7 +1296,7 @@ wait_wal_lsn(const char *wal_segment_dir, XLogRecPtr target_lsn, bool is_start_l */ if (!XRecOffIsNull(target_lsn) && wal_contains_lsn(wal_segment_dir, target_lsn, tli, - instance_config.xlog_seg_size)) + instance_config.xlog_seg_size, !in_stream_dir)) /* Target LSN was found */ { elog(LOG, "Found LSN: %X/%X", (uint32) (target_lsn >> 32), (uint32) target_lsn); @@ -1908,7 +1892,8 @@ pg_stop_backup(InstanceState *instanceState, pgBackup *backup, PGconn *pg_startb if (!read_recovery_info(xlog_path, backup->tli, instance_config.xlog_seg_size, backup->start_lsn, backup->stop_lsn, - &backup->recovery_time)) + &backup->recovery_time, + !backup->stream)) { elog(LOG, "Failed to find Recovery Time in WAL, forced to trust current_timestamp"); backup->recovery_time = stop_backup_result.invocation_time; diff --git a/src/catalog.c b/src/catalog.c index b4ed8c189..d82694a07 100644 --- a/src/catalog.c +++ b/src/catalog.c @@ -1628,6 +1628,9 @@ catalog_get_timelines(InstanceState *instanceState, InstanceConfig *instance) else if (strcmp(suffix, "gz") != 0) { elog(WARNING, "unexpected WAL file name \"%s\"", file->name); + pgFileFree(file); + parray_remove(xlog_files_list, i); + i--; continue; } } @@ -1724,8 +1727,23 @@ catalog_get_timelines(InstanceState *instanceState, InstanceConfig *instance) parray_walk(timelines, pfree); parray_free(timelines); } + /* + * Add WAL archive subdirectories to filelist (used only in delete) + * TODO: currently only directory with 8-character name is treated as WAL subdir, is it ok? + */ + else if (S_ISDIR(file->mode) && strspn(file->rel_path, "0123456789ABCDEF") == 8) + { + if (instanceState->wal_archive_subdirs == NULL) + instanceState->wal_archive_subdirs = parray_new(); + parray_append(instanceState->wal_archive_subdirs, file); + } else + { elog(WARNING, "unexpected WAL file name \"%s\"", file->name); + pgFileFree(file); + parray_remove(xlog_files_list, i); + i--; + } } /* save information about backups belonging to each timeline */ @@ -1745,6 +1763,9 @@ catalog_get_timelines(InstanceState *instanceState, InstanceConfig *instance) parray_append(tlinfo->backups, backup); } } + + /* setup locks */ + xlogfilearray_clear_locks(tlinfo->xlog_filelist); } /* determine oldest backup and closest backup for every timeline */ diff --git a/src/delete.c b/src/delete.c index 6c70ff81e..91a14892a 100644 --- a/src/delete.c +++ b/src/delete.c @@ -29,6 +29,23 @@ static bool backup_deleted = false; /* At least one backup was deleted */ static bool backup_merged = false; /* At least one merge was enacted */ static bool wal_deleted = false; /* At least one WAL segments was deleted */ +typedef struct +{ + parray *xlog_filelist; + int thread_num; + bool purge_all; + XLogSegNo OldestToKeepSegNo; + const char *archive_root_dir; + + /* + * Return value from the thread. + * 0 means there is no error, 1 - there is an error. + */ + int ret; +} delete_files_arg; + +static void *delete_walfiles_in_tli_internal(void *arg); + void do_delete(InstanceState *instanceState, time_t backup_id) { @@ -782,7 +799,7 @@ delete_backup_files(pgBackup *backup) elog(INFO, "Progress: (%zd/%zd). Delete file \"%s\"", i + 1, num_files, full_path); - pgFileDelete(file->mode, full_path); + pgFileDelete(file->mode, full_path, ERROR); } parray_walk(files, pgFileFree); @@ -826,6 +843,10 @@ delete_walfiles_in_tli(InstanceState *instanceState, XLogRecPtr keep_lsn, timeli size_t wal_size_actual = 0; char wal_pretty_size[20]; bool purge_all = false; + // multi-thread stuff + pthread_t *threads; + delete_files_arg *threads_args; + bool delete_isok = true; /* Timeline is completely empty */ @@ -925,21 +946,105 @@ delete_walfiles_in_tli(InstanceState *instanceState, XLogRecPtr keep_lsn, timeli if (dry_run) return; + /* init thread args with own file lists */ + threads = (pthread_t *) palloc(sizeof(pthread_t) * num_threads); + threads_args = (delete_files_arg *) palloc(sizeof(delete_files_arg)*num_threads); + + for (i = 0; i < num_threads; i++) + { + delete_files_arg *arg = &(threads_args[i]); + + arg->purge_all = purge_all; + arg->OldestToKeepSegNo = OldestToKeepSegNo; + arg->archive_root_dir = instanceState->instance_wal_subdir_path; + arg->xlog_filelist = tlinfo->xlog_filelist; + arg->thread_num = i+1; + /* By default there are some error */ + arg->ret = 1; + } + + /* Run threads */ + thread_interrupted = false; + for (i = 0; i < num_threads; i++) + { + delete_files_arg *arg = &(threads_args[i]); + + elog(VERBOSE, "Start thread num: %i", i); + pthread_create(&threads[i], NULL, delete_walfiles_in_tli_internal, arg); + } + + /* Wait threads */ + for (i = 0; i < num_threads; i++) + { + pthread_join(threads[i], NULL); + if (threads_args[i].ret == 1) + delete_isok = false; + } + + /* TODO: */ + //if delete_isok + + /* cleanup */ for (i = 0; i < parray_num(tlinfo->xlog_filelist); i++) { xlogFile *wal_file = (xlogFile *) parray_get(tlinfo->xlog_filelist, i); - if (interrupted) + if (wal_file->deleted) + { + pgXlogFileFree(wal_file); + parray_remove(tlinfo->xlog_filelist, i); + i--; + } + } + pg_free(threads); + pg_free(threads_args); + + /* Remove empty subdirectories */ + if (!instanceState->wal_archive_subdirs) + return; + + for (i = 0; i < parray_num(instanceState->wal_archive_subdirs); i++) + { + char fullpath[MAXPGPATH]; + pgFile *file = (pgFile *) parray_get(instanceState->wal_archive_subdirs, i); + + join_path_components(fullpath, instanceState->instance_wal_subdir_path, file->name); + + if (dir_is_empty(fullpath, FIO_LOCAL_HOST)) + { + pgFileDelete(file->mode, fullpath, WARNING); /* WARNING (not ERROR) due to possible race condition */ + pgFileFree(file); + parray_remove(instanceState->wal_archive_subdirs, i); + i--; + } + } +} + +void * +delete_walfiles_in_tli_internal(void *arg) +{ + int i; + delete_files_arg *args = (delete_files_arg *) arg; + + for (i = 0; i < parray_num(args->xlog_filelist); i++) + { + xlogFile *wal_file = (xlogFile *) parray_get(args->xlog_filelist, i); + + if (interrupted || thread_interrupted) elog(ERROR, "interrupted during WAL archive purge"); - /* Any segment equal or greater than EndSegNo must be kept + if (!pg_atomic_test_set_flag(&wal_file->lock)) + continue; + + /* + * Any segment equal or greater than EndSegNo must be kept * unless it`s a 'purge all' scenario. */ - if (purge_all || wal_file->segno < OldestToKeepSegNo) + if (args->purge_all || wal_file->segno < args->OldestToKeepSegNo) { char wal_fullpath[MAXPGPATH]; - join_path_components(wal_fullpath, instanceState->instance_wal_subdir_path, wal_file->file.name); + join_path_components(wal_fullpath, args->archive_root_dir, wal_file->file.rel_path); /* save segment from purging */ if (instance_config.wal_depth >= 0 && wal_file->keep) @@ -953,8 +1058,8 @@ delete_walfiles_in_tli(InstanceState *instanceState, XLogRecPtr keep_lsn, timeli { /* Missing file is not considered as error condition */ if (errno != ENOENT) - elog(ERROR, "Could not remove file \"%s\": %s", - wal_fullpath, strerror(errno)); + elog(ERROR, "[Thread: %d] Could not remove file \"%s\": %s", + args->thread_num, wal_fullpath, strerror(errno)); } else { @@ -969,8 +1074,11 @@ delete_walfiles_in_tli(InstanceState *instanceState, XLogRecPtr keep_lsn, timeli } wal_deleted = true; + wal_file->deleted = true; } } + + return NULL; } diff --git a/src/dir.c b/src/dir.c index 4ebe0939b..f114c6282 100644 --- a/src/dir.c +++ b/src/dir.c @@ -231,7 +231,7 @@ pgFileInit(const char *rel_path) * If the pgFile points directory, the directory must be empty. */ void -pgFileDelete(mode_t mode, const char *full_path) +pgFileDelete(mode_t mode, const char *full_path, int elevel) { if (S_ISDIR(mode)) { @@ -242,7 +242,7 @@ pgFileDelete(mode_t mode, const char *full_path) else if (errno == ENOTDIR) /* could be symbolic link */ goto delete_file; - elog(ERROR, "Cannot remove directory \"%s\": %s", + elog(elevel, "Cannot remove directory \"%s\": %s", full_path, strerror(errno)); } return; @@ -253,7 +253,7 @@ pgFileDelete(mode_t mode, const char *full_path) { if (errno == ENOENT) return; - elog(ERROR, "Cannot remove file \"%s\": %s", full_path, + elog(elevel, "Cannot remove file \"%s\": %s", full_path, strerror(errno)); } } @@ -405,6 +405,19 @@ pgFileFree(void *file) pfree(file); } +void +pgXlogFileFree(void *xlogfile) +{ + xlogFile *xlogfile_ptr; + + if (xlogfile == NULL) + return; + + xlogfile_ptr = (xlogFile *) xlogfile; + + pg_free(xlogfile_ptr); +} + /* Compare two pgFile with their path in ascending order of ASCII code. */ int pgFileMapComparePath(const void *f1, const void *f2) @@ -813,6 +826,179 @@ dir_check_file(pgFile *file, bool backup_logs) return CHECK_TRUE; } +/* + * List files, symbolic links and directories in the directory "root" and add + * pgFile objects to "files". We add "root" to "files" if add_root is true. + * + * When follow_symlink is true, symbolic link is ignored and only file or + * directory linked to will be listed. + * + * TODO: make it strictly local + */ +//void +//dir_list_archive(parray *files, const char *root, fio_location location) +//{ +// int rc = 0; +// pgFile *root_file = NULL; +// bool follow_symlink = true; +// bool skip_hidden = true +// const char *errormsg = NULL; +// +// root_file = pgFileNew(root, "", follow_symlink, 0, location); +// +// /* directory was deleted */ +// if (file == NULL) +// return; +// +// if fio_is_remote(fio_location) +// rc = fio_dir_list_archive_internal(files, root_file, root, follow_symlink, +// skip_hidden, location); +// else +// rc = dir_list_archive_internal(files, root_file, root, follow_symlink, +// skip_hidden, location); +// +// pgFileFree(file); +//} + +/* + * Get content of root dir, separate dirs into array + * walk dirs on parallel threads. + * Return codes: + * -1 ERROR encountrered + */ +//int +//dir_list_archive_internal(parray *files, pgFile *parent, const char *parent_dir, +// bool follow_symlink, bool skip_hidden, int external_dir_num, +// fio_location location, char **errormsg) +//{ +// DIR *dir; +// struct dirent *dent; +// parray *dirs; +// +// /* Open directory and list contents */ +// dir = opendir(parent_dir); +// if (dir == NULL) +// { +// if (errno == ENOENT) +// { +// /* Maybe the directory was removed */ +// return; +// } +// +// *errormsg = pgut_malloc(ERRMSG_MAX_LEN); +// snprintf(*errormsg, ERRMSG_MAX_LEN, "Cannot open directory \"%s\": %s", parent_dir, strerror(errno)); +// return -1; +// } +// +// errno = 0; +// while ((dent = readdir(dir))) +// { +// pgFile *file; +// char child[MAXPGPATH]; +// char rel_child[MAXPGPATH]; +// char check_res; +// +// join_path_components(child, parent_dir, dent->d_name); +// join_path_components(rel_child, parent->rel_path, dent->d_name); +// +// file = pgFileNew(child, rel_child, follow_symlink, external_dir_num, +// location); +// if (file == NULL) +// continue; +// +// /* Skip entries point current dir or parent dir */ +// if (S_ISDIR(file->mode) && +// (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)) +// { +// pgFileFree(file); +// continue; +// } +// +// /* skip hidden files and directories */ +// if (skip_hidden && file->name[0] == '.') +// { +// //elog(WARNING, "Skip hidden file: '%s'", child); +// pgFileFree(file); +// continue; +// } +// +// /* +// * Add only files, directories and links. Skip sockets and other +// * unexpected file formats. +// */ +// if (!S_ISDIR(file->mode) && !S_ISREG(file->mode)) +// { +// pgFileFree(file); +// continue; +// } +// +// parray_append(files, file); +// +// /* +// * If the entry is a directory call dir_list_file_internal() +// * recursively. +// */ +// if (S_ISDIR(file->mode)) +// parray_append(dirs, file); +// } +// +// if (errno && errno != ENOENT) +// { +// +// *errormsg = pgut_malloc(ERRMSG_MAX_LEN); +// snprintf(*errormsg, ERRMSG_MAX_LEN, "Cannot read directory \"%s\": %s", +// parent_dir, strerror(errno_tmp)); +// +// closedir(dir); +// return -1; +// } +// +// closedir(dir); +// +// /* parse directories on multiple parallel threads */ +// /* init thread args with own file lists */ +// threads = (pthread_t *) palloc(sizeof(pthread_t) * num_threads); +// threads_args = (backup_files_arg *) palloc(sizeof(backup_files_arg)*num_threads); +// +// for (i = 0; i < num_threads; i++) +// { +// backup_files_arg *arg = &(threads_args[i]); +// +// arg->nodeInfo = nodeInfo; +// arg->from_root = instance_config.pgdata; +// arg->to_root = current.database_dir; +// arg->external_prefix = external_prefix; +// arg->external_dirs = external_dirs; +// arg->files_list = backup_files_list; +// arg->prev_filelist = prev_backup_filelist; +// arg->prev_start_lsn = prev_backup_start_lsn; +// arg->hdr_map = &(current.hdr_map); +// arg->thread_num = i+1; +// /* By default there are some error */ +// arg->ret = 1; +// } +// +// /* Run threads */ +// thread_interrupted = false; +// elog(INFO, "Start transferring data files"); +// time(&start_time); +// for (i = 0; i < num_threads; i++) +// { +// backup_files_arg *arg = &(threads_args[i]); +// +// elog(VERBOSE, "Start thread num: %i", i); +// pthread_create(&threads[i], NULL, backup_files, arg); +// } +// +// /* Wait threads */ +// for (i = 0; i < num_threads; i++) +// { +// pthread_join(threads[i], NULL); +// if (threads_args[i].ret == 1) +// backup_isok = false; +// } +//} + /* * List files in parent->path directory. If "exclude" is true do not add into * "files" files from pgdata_exclude_files and directories from @@ -1909,3 +2095,17 @@ pfilearray_clear_locks(parray *file_list) pg_atomic_clear_flag(&file->lock); } } + +/* + * Clear the synchronisation locks in a parray of (xlogFile *)'s + */ +void +xlogfilearray_clear_locks(parray *xlog_list) +{ + int i; + for (i = 0; i < parray_num(xlog_list); i++) + { + xlogFile *file = (xlogFile *) parray_get(xlog_list, i); + pg_atomic_clear_flag(&file->lock); + } +} diff --git a/src/merge.c b/src/merge.c index ff39c2510..96a193e82 100644 --- a/src/merge.c +++ b/src/merge.c @@ -809,7 +809,7 @@ merge_chain(InstanceState *instanceState, /* We need full path, file object has relative path */ join_path_components(full_file_path, full_database_dir, full_file->rel_path); - pgFileDelete(full_file->mode, full_file_path); + pgFileDelete(full_file->mode, full_file_path, ERROR); elog(VERBOSE, "Deleted \"%s\"", full_file_path); } } @@ -1143,7 +1143,7 @@ remove_dir_with_files(const char *path) join_path_components(full_path, path, file->rel_path); - pgFileDelete(file->mode, full_path); + pgFileDelete(file->mode, full_path, ERROR); elog(VERBOSE, "Deleted \"%s\"", full_path); } diff --git a/src/parsexlog.c b/src/parsexlog.c index 7f1ca9c75..28266c211 100644 --- a/src/parsexlog.c +++ b/src/parsexlog.c @@ -115,6 +115,7 @@ typedef struct XLogReaderData gzFile gz_xlogfile; char gz_xlogpath[MAXPGPATH]; #endif + bool honor_subdirs; } XLogReaderData; /* Function to process a WAL record */ @@ -172,7 +173,8 @@ static bool RunXLogThreads(const char *archivedir, bool consistent_read, xlog_record_function process_record, XLogRecTarget *last_rec, - bool inclusive_endpoint); + bool inclusive_endpoint, + bool honor_subdirs); //static XLogReaderState *InitXLogThreadRead(xlog_thread_arg *arg); static bool SwitchThreadToNextWal(XLogReaderState *xlogreader, xlog_thread_arg *arg); @@ -254,7 +256,7 @@ extractPageMap(const char *archivedir, uint32 wal_seg_size, extract_isok = RunXLogThreads(archivedir, 0, InvalidTransactionId, InvalidXLogRecPtr, end_tli, wal_seg_size, startpoint, endpoint, false, extractPageInfo, - NULL, true); + NULL, true, true); else { /* We have to process WAL located on several different xlog intervals, @@ -348,7 +350,7 @@ extractPageMap(const char *archivedir, uint32 wal_seg_size, extract_isok = RunXLogThreads(archivedir, 0, InvalidTransactionId, InvalidXLogRecPtr, tmp_interval->tli, wal_seg_size, tmp_interval->begin_lsn, tmp_interval->end_lsn, - false, extractPageInfo, NULL, inclusive_endpoint); + false, extractPageInfo, NULL, inclusive_endpoint, true); if (!extract_isok) break; @@ -377,7 +379,7 @@ validate_backup_wal_from_start_to_stop(pgBackup *backup, got_endpoint = RunXLogThreads(archivedir, 0, InvalidTransactionId, InvalidXLogRecPtr, tli, xlog_seg_size, backup->start_lsn, backup->stop_lsn, - false, NULL, NULL, true); + false, NULL, NULL, true, !backup->stream); if (!got_endpoint) { @@ -450,6 +452,7 @@ validate_wal(pgBackup *backup, const char *archivedir, elog(WARNING, "Backup %s WAL segments are corrupted", backup_id); return; } + /* * If recovery target is provided check that we can restore backup to a * recovery target time or xid. @@ -490,7 +493,8 @@ validate_wal(pgBackup *backup, const char *archivedir, all_wal = all_wal || RunXLogThreads(archivedir, target_time, target_xid, target_lsn, tli, wal_seg_size, backup->stop_lsn, - InvalidXLogRecPtr, true, validateXLogRecord, &last_rec, true); + InvalidXLogRecPtr, true, validateXLogRecord, &last_rec, true, + true); if (last_rec.rec_time > 0) time2iso(last_timestamp, lengthof(last_timestamp), timestamptz_to_time_t(last_rec.rec_time), false); @@ -532,7 +536,7 @@ validate_wal(pgBackup *backup, const char *archivedir, bool read_recovery_info(const char *archivedir, TimeLineID tli, uint32 wal_seg_size, XLogRecPtr start_lsn, XLogRecPtr stop_lsn, - time_t *recovery_time) + time_t *recovery_time, bool honor_subdirs) { XLogRecPtr startpoint = stop_lsn; XLogReaderState *xlogreader; @@ -549,6 +553,7 @@ read_recovery_info(const char *archivedir, TimeLineID tli, uint32 wal_seg_size, xlogreader = InitXLogPageRead(&reader_data, archivedir, tli, wal_seg_size, false, true, true); + reader_data.honor_subdirs = honor_subdirs; /* Read records from stop_lsn down to start_lsn */ do @@ -608,7 +613,7 @@ read_recovery_info(const char *archivedir, TimeLineID tli, uint32 wal_seg_size, */ bool wal_contains_lsn(const char *archivedir, XLogRecPtr target_lsn, - TimeLineID target_tli, uint32 wal_seg_size) + TimeLineID target_tli, uint32 wal_seg_size, bool honor_subdirs) { XLogReaderState *xlogreader; XLogReaderData reader_data; @@ -626,6 +631,7 @@ wal_contains_lsn(const char *archivedir, XLogRecPtr target_lsn, elog(ERROR, "Out of memory"); xlogreader->system_identifier = instance_config.system_identifier; + reader_data.honor_subdirs = honor_subdirs; #if PG_VERSION_NUM >= 130000 if (XLogRecPtrIsInvalid(target_lsn)) @@ -1012,34 +1018,124 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, /* Try to switch to the next WAL segment */ if (!reader_data->xlogexists) { - char xlogfname[MAXFNAMELEN]; - char partial_file[MAXPGPATH]; + bool compressed = false; + char xlogfname[MAXFNAMELEN]; +// char partial_file[MAXPGPATH]; + char fullpath[MAXPGPATH]; + char fullpath_gz[MAXPGPATH]; + char fullpath_partial_gz[MAXPGPATH]; GetXLogFileName(xlogfname, reader_data->tli, reader_data->xlogsegno, wal_seg_size); - join_path_components(reader_data->xlogpath, wal_archivedir, xlogfname); - snprintf(reader_data->gz_xlogpath, MAXPGPATH, "%s.gz", reader_data->xlogpath); + /* obtain WAL archive subdir for ARCHIVE backup */ + // TODO: move to separate function and rewrite it + if (reader_data->honor_subdirs) + { + char archive_subdir[MAXPGPATH]; + get_archive_subdir(archive_subdir, wal_archivedir, xlogfname, SEGMENT); + + /* default value for xlogpath for error message */ + snprintf(reader_data->xlogpath, MAXPGPATH, "%s/%s", archive_subdir, xlogfname); + + /* check existence of wal_dir/xlogid/segment.gz file ... */ + snprintf(fullpath_gz, MAXPGPATH, "%s/%s.gz", archive_subdir, xlogfname); + + //TODO: rewrite it to something less ugly +#ifdef HAVE_LIBZ + if (fileExists(fullpath_gz, FIO_LOCAL_HOST)) + { + snprintf(reader_data->xlogpath, MAXPGPATH, "%s/%s", archive_subdir, xlogfname); + snprintf(reader_data->gz_xlogpath, MAXPGPATH, "%s", fullpath_gz); + compressed = true; + goto file_found; + } + + /* ... failing that check existence of wal_dir/xlogid/segment.partial.gz ... */ + snprintf(fullpath_partial_gz, MAXPGPATH, "%s/%s.partial.gz", archive_subdir, xlogfname); + if (fileExists(fullpath_partial_gz, FIO_LOCAL_HOST)) + { + snprintf(reader_data->xlogpath, MAXPGPATH, "%s/%s.partial", archive_subdir, xlogfname); + snprintf(reader_data->gz_xlogpath, MAXPGPATH, "%s", fullpath_partial_gz); + compressed = true; + goto file_found; + } +#endif + /* ... failing that check existence of wal_dir/xlogid/segment ... */ + snprintf(fullpath, MAXPGPATH, "%s/%s", archive_subdir, xlogfname); + if (fileExists(fullpath, FIO_LOCAL_HOST)) + { + snprintf(reader_data->xlogpath, MAXPGPATH, "%s", fullpath); + goto file_found; + } + + goto archive_dir; + } + /* use directory as-is */ + else + { + /* default value for xlogpath for error message */ + snprintf(reader_data->xlogpath, MAXPGPATH, "%s/%s", wal_archivedir, xlogfname); +archive_dir: +#ifdef HAVE_LIBZ + /* ... failing that check existence of wal_dir/segment.gz ... */ + snprintf(fullpath_gz, MAXPGPATH, "%s/%s.gz", wal_archivedir, xlogfname); + if (fileExists(fullpath_gz, FIO_LOCAL_HOST)) + { + snprintf(reader_data->gz_xlogpath, MAXPGPATH, "%s", fullpath_gz); + snprintf(reader_data->xlogpath, MAXPGPATH, "%s/%s", wal_archivedir, xlogfname); + compressed = true; + + goto file_found; + } + + /* ... failing that check existence of wal_dir/segment.partial.gz ... */ + snprintf(fullpath_partial_gz, MAXPGPATH, "%s/%s.partial.gz", wal_archivedir, xlogfname); + if (fileExists(wal_archivedir, FIO_LOCAL_HOST)) + { + snprintf(reader_data->xlogpath, MAXPGPATH, "%s/%s.partial", wal_archivedir, xlogfname); + snprintf(reader_data->gz_xlogpath, MAXPGPATH, "%s", fullpath_partial_gz); + compressed = true; + goto file_found; + } +#endif + /* ... failing that check existence of wal_dir/segment ... */ + snprintf(fullpath, MAXPGPATH, "%s/%s", wal_archivedir, xlogfname); + if (fileExists(fullpath, FIO_LOCAL_HOST)) + { + snprintf(reader_data->xlogpath, MAXPGPATH, "%s", fullpath); + goto file_found; + } + } + +file_found: + canonicalize_path(reader_data->xlogpath); + +#ifdef HAVE_LIBZ + if (compressed) + canonicalize_path(reader_data->gz_xlogpath); +#endif + +// snprintf(reader_data->gz_xlogpath, MAXPGPATH, "%s.gz", reader_data->xlogpath); /* We fall back to using .partial segment in case if we are running * multi-timeline incremental backup right after standby promotion. * TODO: it should be explicitly enabled. */ - snprintf(partial_file, MAXPGPATH, "%s.partial", reader_data->xlogpath); +// snprintf(partial_file, MAXPGPATH, "%s.partial", reader_data->xlogpath); /* If segment do not exists, but the same * segment with '.partial' suffix does, use it instead */ - if (!fileExists(reader_data->xlogpath, FIO_LOCAL_HOST) && - fileExists(partial_file, FIO_LOCAL_HOST)) - { - snprintf(reader_data->xlogpath, MAXPGPATH, "%s", partial_file); - } +// if (!fileExists(reader_data->xlogpath, FIO_LOCAL_HOST) && +// fileExists(partial_file, FIO_LOCAL_HOST)) +// { +// snprintf(reader_data->xlogpath, MAXPGPATH, "%s", partial_file); +// } - if (fileExists(reader_data->xlogpath, FIO_LOCAL_HOST)) + if (!compressed) { elog(LOG, "Thread [%d]: Opening WAL segment \"%s\"", reader_data->thread_num, reader_data->xlogpath); - reader_data->xlogexists = true; reader_data->xlogfile = fio_open(reader_data->xlogpath, O_RDONLY | PG_BINARY, FIO_LOCAL_HOST); @@ -1050,15 +1146,16 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, strerror(errno)); return -1; } + else + reader_data->xlogexists = true; } #ifdef HAVE_LIBZ /* Try to open compressed WAL segment */ - else if (fileExists(reader_data->gz_xlogpath, FIO_LOCAL_HOST)) + else { elog(LOG, "Thread [%d]: Opening compressed WAL segment \"%s\"", reader_data->thread_num, reader_data->gz_xlogpath); - reader_data->xlogexists = true; reader_data->gz_xlogfile = fio_gzopen(reader_data->gz_xlogpath, "rb", -1, FIO_LOCAL_HOST); if (reader_data->gz_xlogfile == NULL) @@ -1068,6 +1165,8 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, strerror(errno)); return -1; } + else + reader_data->xlogexists = true; } #endif /* Exit without error if WAL segment doesn't exist */ @@ -1191,7 +1290,7 @@ RunXLogThreads(const char *archivedir, time_t target_time, TransactionId target_xid, XLogRecPtr target_lsn, TimeLineID tli, uint32 segment_size, XLogRecPtr startpoint, XLogRecPtr endpoint, bool consistent_read, xlog_record_function process_record, - XLogRecTarget *last_rec, bool inclusive_endpoint) + XLogRecTarget *last_rec, bool inclusive_endpoint, bool honor_subdirs) { pthread_t *threads; xlog_thread_arg *thread_args; @@ -1255,6 +1354,7 @@ RunXLogThreads(const char *archivedir, time_t target_time, consistent_read, false); arg->reader_data.xlogsegno = segno_next; arg->reader_data.thread_num = i + 1; + arg->reader_data.honor_subdirs = honor_subdirs; arg->process_record = process_record; arg->startpoint = startpoint; arg->endpoint = endpoint; @@ -1482,7 +1582,7 @@ XLogThreadWorker(void *arg) reader_data->thread_num, (uint32) (errptr >> 32), (uint32) (errptr)); - /* In we failed to read record located at endpoint position, + /* If we failed to read record located at endpoint position, * and endpoint is not inclusive, do not consider this as an error. */ if (!thread_arg->inclusive_endpoint && @@ -1509,6 +1609,7 @@ XLogThreadWorker(void *arg) if (thread_arg->process_record) thread_arg->process_record(xlogreader, reader_data, &stop_reading); + if (stop_reading) { thread_arg->got_target = true; @@ -1915,7 +2016,7 @@ bool validate_wal_segment(TimeLineID tli, XLogSegNo segno, const char *prefetch_ rc = RunXLogThreads(prefetch_dir, 0, InvalidTransactionId, InvalidXLogRecPtr, tli, wal_seg_size, - startpoint, endpoint, false, NULL, NULL, true); + startpoint, endpoint, false, NULL, NULL, true, false); num_threads = tmp_num_threads; @@ -1946,4 +2047,65 @@ static XLogReaderState* WalReaderAllocate(uint32 wal_seg_size, XLogReaderData *r #else return XLogReaderAllocate(&SimpleXLogPageRead, reader_data); #endif -} \ No newline at end of file +} + +/* + * Is WAL file exists in archive directory + * for stream backup check uncompressed segment in wal_root_dir + * for archive backup first check subdirectory, then fallback to archive directory + */ +bool IsWalFileExists(const char *wal_segment_name, const char *wal_root_dir, bool in_stream_dir) +{ + char wal_file_fullpath[MAXPGPATH]; + char wal_file_fullpath_gz[MAXPGPATH]; + char wal_segment_subdir[MAXPGPATH]; + + if (in_stream_dir) + { + join_path_components(wal_file_fullpath, wal_root_dir, wal_segment_name); + if (fileExists(wal_file_fullpath, FIO_BACKUP_HOST)) + goto found_uncompressed_file; + + goto not_found; + } + + /* obtain subdir in WAL archive */ + get_archive_subdir(wal_segment_subdir, wal_root_dir, wal_segment_name, SEGMENT); + + /* first try uncompressed segment in WAL archive subdir ... */ + join_path_components(wal_file_fullpath, wal_segment_subdir, wal_segment_name); + if (fileExists(wal_file_fullpath, FIO_BACKUP_HOST)) + goto found_uncompressed_file; + +#ifdef HAVE_LIBZ + /* ... fallback to compressed segment in WAL archive subdir ... */ + snprintf(wal_file_fullpath_gz, MAXPGPATH, "%s.gz", wal_file_fullpath); + if (fileExists(wal_file_fullpath_gz, FIO_BACKUP_HOST)) + goto found_compressed_file; +#endif + + /* ... fallback to uncompressed segment in archive dir ... */ + join_path_components(wal_file_fullpath, wal_root_dir, wal_segment_name); + if (fileExists(wal_file_fullpath, FIO_BACKUP_HOST)) + goto found_uncompressed_file; + + /* ... fallback to compressed segment in archive dir */ +#ifdef HAVE_LIBZ + snprintf(wal_file_fullpath_gz, MAXPGPATH, "%s.gz", wal_file_fullpath); + if (fileExists(wal_file_fullpath_gz, FIO_BACKUP_HOST)) + goto found_compressed_file; +#endif + + goto not_found; + +found_compressed_file: + elog(LOG, "Found compressed WAL segment: %s", wal_file_fullpath); + return true; + +found_uncompressed_file: + elog(LOG, "Found WAL segment: %s", wal_file_fullpath_gz); + return true; + +not_found: + return false; +} diff --git a/src/pg_probackup.c b/src/pg_probackup.c index 49e226ace..d7351b939 100644 --- a/src/pg_probackup.c +++ b/src/pg_probackup.c @@ -495,6 +495,7 @@ main(int argc, char *argv[]) catalogState->wal_subdir_path, instanceState->instance_name); join_path_components(instanceState->instance_config_path, instanceState->instance_backup_subdir_path, BACKUP_CATALOG_CONF_FILE); + instanceState->wal_archive_subdirs = NULL; } /* ===== instanceState (END) ======*/ diff --git a/src/pg_probackup.h b/src/pg_probackup.h index b828343dc..dce4dd4d9 100644 --- a/src/pg_probackup.h +++ b/src/pg_probackup.h @@ -338,7 +338,7 @@ typedef enum ShowFormat #define BYTES_INVALID (-1) /* file didn`t changed since previous backup, DELTA backup do not rely on it */ #define FILE_NOT_FOUND (-2) /* file disappeared during backup */ #define BLOCKNUM_INVALID (-1) -#define PROGRAM_VERSION "2.5.3" +#define PROGRAM_VERSION "2.5.4" /* update when remote agent API or behaviour changes */ #define AGENT_PROTOCOL_VERSION 20501 @@ -647,9 +647,11 @@ typedef struct lsnInterval typedef enum xlogFileType { + UNKNOWN, SEGMENT, - TEMP_SEGMENT, + TEMP_SEGMENT, // '.part' segment created by archive-push PARTIAL_SEGMENT, + HISTORY_FILE, BACKUP_HISTORY_FILE } xlogFileType; @@ -660,6 +662,8 @@ typedef struct xlogFile xlogFileType type; bool keep; /* Used to prevent removal of WAL segments * required by ARCHIVE backups. */ + bool deleted; + volatile pg_atomic_flag lock;/* lock for synchronization of parallel threads */ } xlogFile; @@ -814,6 +818,8 @@ typedef struct InstanceState /* $BACKUP_PATH/backups/instance_name */ char instance_wal_subdir_path[MAXPGPATH]; // previously global var arclog_path + parray *wal_archive_subdirs; + /* TODO: Make it more specific */ PGconn *conn; @@ -894,6 +900,7 @@ extern void do_archive_push(InstanceState *instanceState, InstanceConfig *instan bool no_sync, bool no_ready_rename); extern void do_archive_get(InstanceState *instanceState, InstanceConfig *instance, const char *prefetch_dir_arg, char *wal_file_path, char *wal_file_name, int batch_size, bool validate_wal); +extern void get_archive_subdir(char *archive_subdir, const char * archive_dir, const char *wal_file_name, xlogFileType type); /* in configure.c */ extern void do_show_config(void); @@ -1048,16 +1055,18 @@ extern int dir_create_dir(const char *path, mode_t mode, bool strict); extern bool dir_is_empty(const char *path, fio_location location); extern bool fileExists(const char *path, fio_location location); +extern bool IsWalFileExists(const char *wal_segment_name, const char *archive_dir, bool in_stream_dir); extern size_t pgFileSize(const char *path); extern pgFile *pgFileNew(const char *path, const char *rel_path, bool follow_symlink, int external_dir_num, fio_location location); extern pgFile *pgFileInit(const char *rel_path); -extern void pgFileDelete(mode_t mode, const char *full_path); +extern void pgFileDelete(mode_t mode, const char *full_path, int elevel); extern void fio_pgFileDelete(pgFile *file, const char *full_path); extern void pgFileFree(void *file); +extern void pgXlogFileFree(void *xlogfile); extern pg_crc32 pgFileGetCRC(const char *file_path, bool use_crc32c, bool missing_ok); extern pg_crc32 pgFileGetCRCgz(const char *file_path, bool use_crc32c, bool missing_ok); @@ -1075,6 +1084,7 @@ extern int pgCompareString(const void *str1, const void *str2); extern int pgPrefixCompareString(const void *str1, const void *str2); extern int pgCompareOid(const void *f1, const void *f2); extern void pfilearray_clear_locks(parray *file_list); +extern void xlogfilearray_clear_locks(parray *xlog_list); /* in data.c */ extern bool check_data_file(ConnectionArgs *arguments, pgFile *file, @@ -1137,9 +1147,9 @@ extern bool validate_wal_segment(TimeLineID tli, XLogSegNo segno, extern bool read_recovery_info(const char *archivedir, TimeLineID tli, uint32 seg_size, XLogRecPtr start_lsn, XLogRecPtr stop_lsn, - time_t *recovery_time); + time_t *recovery_time, bool honor_subdirs); extern bool wal_contains_lsn(const char *archivedir, XLogRecPtr target_lsn, - TimeLineID target_tli, uint32 seg_size); + TimeLineID target_tli, uint32 seg_size, bool honor_subdirs); extern XLogRecPtr get_prior_record_lsn(const char *archivedir, XLogRecPtr start_lsn, XLogRecPtr stop_lsn, TimeLineID tli, bool seek_prev_segment, uint32 seg_size); diff --git a/src/utils/file.c b/src/utils/file.c index 7d1df554b..004a5f563 100644 --- a/src/utils/file.c +++ b/src/utils/file.c @@ -3196,7 +3196,7 @@ fio_delete(mode_t mode, const char *fullpath, fio_location location) } else - pgFileDelete(mode, fullpath); + pgFileDelete(mode, fullpath, ERROR); } static void @@ -3204,7 +3204,7 @@ fio_delete_impl(mode_t mode, char *buf) { char *fullpath = (char*) buf; - pgFileDelete(mode, fullpath); + pgFileDelete(mode, fullpath, ERROR); /* TODO: must return rc, not error out internally */ } /* Execute commands at remote host */ diff --git a/tests/archive.py b/tests/archive.py index 22b9d8693..53300c574 100644 --- a/tests/archive.py +++ b/tests/archive.py @@ -369,7 +369,7 @@ def test_archive_push_file_exists(self): self.add_instance(backup_dir, 'node', node) self.set_archiving(backup_dir, 'node', node) - wals_dir = os.path.join(backup_dir, 'wal', 'node') + wals_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') if self.archive_compress: filename = '000000010000000000000001.gz' file = os.path.join(wals_dir, filename) @@ -377,6 +377,8 @@ def test_archive_push_file_exists(self): filename = '000000010000000000000001' file = os.path.join(wals_dir, filename) + os.makedirs(wals_dir) + with open(file, 'a+b') as f: f.write(b"blablablaadssaaaaaaaaaaaaaaa") f.flush() @@ -461,7 +463,7 @@ def test_archive_push_file_exists_overwrite(self): self.add_instance(backup_dir, 'node', node) self.set_archiving(backup_dir, 'node', node) - wals_dir = os.path.join(backup_dir, 'wal', 'node') + wals_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') if self.archive_compress: filename = '000000010000000000000001.gz' file = os.path.join(wals_dir, filename) @@ -469,6 +471,8 @@ def test_archive_push_file_exists_overwrite(self): filename = '000000010000000000000001' file = os.path.join(wals_dir, filename) + os.makedirs(wals_dir) + with open(file, 'a+b') as f: f.write(b"blablablaadssaaaaaaaaaaaaaaa") f.flush() @@ -565,7 +569,7 @@ def test_archive_push_partial_file_exists(self): filename_orig = filename_orig.decode('utf-8') # form up path to next .part WAL segment - wals_dir = os.path.join(backup_dir, 'wal', 'node') + wals_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') if self.archive_compress: filename = filename_orig + '.gz' + '.part' file = os.path.join(wals_dir, filename) @@ -573,6 +577,8 @@ def test_archive_push_partial_file_exists(self): filename = filename_orig + '.part' file = os.path.join(wals_dir, filename) +# os.makedirs(wals_dir) + # emulate stale .part file with open(file, 'a+b') as f: f.write(b"blahblah") @@ -1111,6 +1117,7 @@ def test_archive_pg_receivexlog(self): self.init_pb(backup_dir) self.add_instance(backup_dir, 'node', node) node.slow_start() + if self.get_version(node) < 100000: pg_receivexlog_path = self.get_bin_path('pg_receivexlog') else: @@ -1462,7 +1469,7 @@ def test_archive_catalog(self): self.assertTrue(timeline['status'], 'OK') # create holes in t3 - wals_dir = os.path.join(backup_dir, 'wal', 'replica') + wals_dir = os.path.join(backup_dir, 'wal', 'replica', '00000000') wals = [ f for f in os.listdir(wals_dir) if os.path.isfile(os.path.join(wals_dir, f)) and not f.endswith('.backup') and not f.endswith('.history') and f.startswith('00000003') @@ -1472,17 +1479,17 @@ def test_archive_catalog(self): # check that t3 is ok self.show_archive(backup_dir) - file = os.path.join(backup_dir, 'wal', 'replica', '000000030000000000000017') + file = os.path.join(wals_dir, '000000030000000000000017') if self.archive_compress: file = file + '.gz' os.remove(file) - file = os.path.join(backup_dir, 'wal', 'replica', '000000030000000000000012') + file = os.path.join(wals_dir, '000000030000000000000012') if self.archive_compress: file = file + '.gz' os.remove(file) - file = os.path.join(backup_dir, 'wal', 'replica', '000000030000000000000013') + file = os.path.join(wals_dir, '000000030000000000000013') if self.archive_compress: file = file + '.gz' os.remove(file) @@ -1597,7 +1604,7 @@ def test_archive_catalog_1(self): self.backup_node(backup_dir, 'node', node) node.pgbench_init(scale=2) - wals_dir = os.path.join(backup_dir, 'wal', 'node') + wals_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') original_file = os.path.join(wals_dir, '000000010000000000000001.gz') tmp_file = os.path.join(wals_dir, '000000010000000000000001') @@ -1652,7 +1659,7 @@ def test_archive_catalog_2(self): self.backup_node(backup_dir, 'node', node) node.pgbench_init(scale=2) - wals_dir = os.path.join(backup_dir, 'wal', 'node') + wals_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') original_file = os.path.join(wals_dir, '000000010000000000000001.gz') tmp_file = os.path.join(wals_dir, '000000010000000000000001') @@ -2503,7 +2510,7 @@ def test_archive_get_prefetch_corruption(self): sleep(20) # now copy WAL files into prefetch directory and corrupt some of them - archive_dir = os.path.join(backup_dir, 'wal', 'node') + archive_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') files = os.listdir(archive_dir) files.sort() @@ -2589,7 +2596,7 @@ def test_archive_show_partial_files_handling(self): self.backup_node(backup_dir, 'node', node) - wals_dir = os.path.join(backup_dir, 'wal', 'node') + wals_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') # .part file node.safe_psql( diff --git a/tests/compatibility.py b/tests/compatibility.py index e274c22be..36a626a7c 100644 --- a/tests/compatibility.py +++ b/tests/compatibility.py @@ -615,6 +615,11 @@ def test_backward_compatibility_merge_1(self): merge them with new binary. old binary version =< 2.2.7 """ + if self.version_to_num(self.old_probackup_version) > self.version_to_num('2.2.7'): + self.assertTrue( + False, + 'You need pg_probackup old_binary =< 2.2.7 for this test') + fname = self.id().split('.')[3] backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup') node = self.make_simple_node( @@ -1475,10 +1480,78 @@ def test_compatibility_tablespace(self): if self.paranoia: pgdata = self.pgdata_content(node.data_dir) + pgdata_restored = self.pgdata_content(node_restored.data_dir) + self.compare_pgdata(pgdata, pgdata_restored) + + # Clean after yourself + self.del_test_dir(module_name, fname) + + # @unittest.expectedFailure + # @unittest.skip("skip") + def test_archive_subdir(self): + """ + https://github.com/postgrespro/pg_probackup/issues/449 + + Make sure that our WAL reader can fallback from subdir to archive dir + old binary version =< 2.5.2 + """ + if self.version_to_num(self.old_probackup_version) > self.version_to_num('2.5.2'): + self.assertTrue( + False, 'OLD pg_probackup binary must be =< 2.5.2 for this test') + + self.assertNotEqual( + self.version_to_num(self.old_probackup_version), + self.version_to_num(self.probackup_version)) + + fname = self.id().split('.')[3] + backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup') + node = self.make_simple_node( + base_dir=os.path.join(module_name, fname, 'node'), + set_replication=True, + initdb_params=['--data-checksums']) + + self.init_pb(backup_dir) + self.add_instance(backup_dir, 'node', node) + self.set_archiving(backup_dir, 'node', node, old_binary=True) + node.slow_start() + + # generate data using old binary + node.pgbench_init(scale=10) + + # TAKE FULL ARCHIVE BACKUP + self.backup_node(backup_dir, 'node', node, old_binary=True) + + # generate some more WAL using old binary + node.pgbench_init(scale=10) + + # generate some WAL using new binary + self.set_archiving(backup_dir, 'node', node) + node.reload() + + # generate some WAL using old binary + pgbench = node.pgbench( + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + options=["-j", "4", "-T", "50"]) + pgbench.wait() + pgbench.stdout.close() + + # TAKE PAGE ARCHIVE BACKUP + self.backup_node(backup_dir, 'node', node, backup_type='page', options=['--archive-timeout=10s']) if self.paranoia: - pgdata_restored = self.pgdata_content(node_restored.data_dir) + pgdata = self.pgdata_content(node.data_dir) + + node.cleanup() + + self.restore_node(backup_dir, 'node', node, options=["-j", "4"]) + + if self.paranoia: + pgdata_restored = self.pgdata_content(node.data_dir) self.compare_pgdata(pgdata, pgdata_restored) + node.slow_start() + node.stop() + # Clean after yourself self.del_test_dir(module_name, fname) diff --git a/tests/delete.py b/tests/delete.py index 345a70284..993e63734 100644 --- a/tests/delete.py +++ b/tests/delete.py @@ -203,9 +203,10 @@ def test_delete_increment_ptrack(self): self.set_archiving(backup_dir, 'node', node) node.slow_start() - node.safe_psql( - 'postgres', - 'CREATE EXTENSION ptrack') + if node.major_version >= 12: + node.safe_psql( + 'postgres', + 'CREATE EXTENSION ptrack') # full backup mode self.backup_node(backup_dir, 'node', node) @@ -263,7 +264,7 @@ def test_delete_orphaned_wal_segments(self): node.stop() # Check wals - wals_dir = os.path.join(backup_dir, 'wal', 'node') + wals_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') wals = [f for f in os.listdir(wals_dir) if os.path.isfile(os.path.join(wals_dir, f))] original_wal_quantity = len(wals) @@ -299,8 +300,7 @@ def test_delete_orphaned_wal_segments(self): # Delete last backup self.delete_pb(backup_dir, 'node', backup_3_id, options=['--wal']) - wals = [f for f in os.listdir(wals_dir) if os.path.isfile(os.path.join(wals_dir, f))] - self.assertEqual (0, len(wals), "Number of wals should be equal to 0") + self.assertFalse(os.path.exists(wals_dir), "Number of wals should be equal to 0") # Clean after yourself self.del_test_dir(module_name, fname) diff --git a/tests/expected/option_version.out b/tests/expected/option_version.out index 8b212ac1f..a69cee03d 100644 --- a/tests/expected/option_version.out +++ b/tests/expected/option_version.out @@ -1 +1 @@ -pg_probackup 2.5.3 \ No newline at end of file +pg_probackup 2.5.4 \ No newline at end of file diff --git a/tests/helpers/ptrack_helpers.py b/tests/helpers/ptrack_helpers.py index 3b14b7170..a6636f538 100644 --- a/tests/helpers/ptrack_helpers.py +++ b/tests/helpers/ptrack_helpers.py @@ -1239,7 +1239,8 @@ def delete_pb( options=[], old_binary=False, gdb=False, asynchronous=False): cmd_list = [ 'delete', - '-B', backup_dir + '-B', backup_dir, + '-j', '10' ] cmd_list += ['--instance={0}'.format(instance)] @@ -1253,7 +1254,8 @@ def delete_expired( cmd_list = [ 'delete', '-B', backup_dir, - '--instance={0}'.format(instance) + '--instance={0}'.format(instance), + '-j', '10' ] return self.run_pb(cmd_list + options, old_binary=old_binary) @@ -1308,9 +1310,14 @@ def set_archiving( options['archive_mode'] = 'on' if custom_archive_command is None: + if old_binary: + binary_path = self.probackup_old_path + else: + binary_path = self.probackup_path + if os.name == 'posix': options['archive_command'] = '"{0}" archive-push -B {1} --instance={2} '.format( - self.probackup_path, backup_dir, instance) + binary_path, backup_dir, instance) elif os.name == 'nt': options['archive_command'] = '"{0}" archive-push -B {1} --instance={2} '.format( diff --git a/tests/retention.py b/tests/retention.py index 19204807b..906c7833f 100644 --- a/tests/retention.py +++ b/tests/retention.py @@ -59,7 +59,7 @@ def test_retention_redundancy_1(self): min_wal = output_after['min-segno'] max_wal = output_after['max-segno'] - for wal_name in os.listdir(os.path.join(backup_dir, 'wal', 'node')): + for wal_name in os.listdir(os.path.join(backup_dir, 'wal', 'node', '00000000')): if not wal_name.endswith(".backup"): if self.archive_compress: diff --git a/tests/validate.py b/tests/validate.py index 0b04d92fe..22212879c 100644 --- a/tests/validate.py +++ b/tests/validate.py @@ -1545,7 +1545,7 @@ def test_validate_corrupt_wal_1(self): backup_id_2 = self.backup_node(backup_dir, 'node', node) # Corrupt WAL - wals_dir = os.path.join(backup_dir, 'wal', 'node') + wals_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') wals = [f for f in os.listdir(wals_dir) if os.path.isfile(os.path.join(wals_dir, f)) and not f.endswith('.backup')] wals.sort() for wal in wals: @@ -1610,7 +1610,7 @@ def test_validate_corrupt_wal_2(self): target_xid = res[0][0] # Corrupt WAL - wals_dir = os.path.join(backup_dir, 'wal', 'node') + wals_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') wals = [f for f in os.listdir(wals_dir) if os.path.isfile(os.path.join(wals_dir, f)) and not f.endswith('.backup')] wals.sort() for wal in wals: @@ -1673,10 +1673,10 @@ def test_validate_wal_lost_segment_1(self): backup_id = self.backup_node(backup_dir, 'node', node) # Delete wal segment - wals_dir = os.path.join(backup_dir, 'wal', 'node') + wals_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') wals = [f for f in os.listdir(wals_dir) if os.path.isfile(os.path.join(wals_dir, f)) and not f.endswith('.backup')] wals.sort() - file = os.path.join(backup_dir, 'wal', 'node', wals[-1]) + file = os.path.join(wals_dir, wals[-1]) os.remove(file) # cut out '.gz' @@ -1778,7 +1778,7 @@ def test_validate_corrupt_wal_between_backups(self): self.backup_node(backup_dir, 'node', node) # Corrupt WAL - wals_dir = os.path.join(backup_dir, 'wal', 'node') + wals_dir = os.path.join(backup_dir, 'wal', 'node', '00000000') with open(os.path.join(wals_dir, walfile), "rb+", 0) as f: f.seek(9000) f.write(b"b")
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: