diff --git a/src/backup.c b/src/backup.c index c8d6cb08..1b94a238 100644 --- a/src/backup.c +++ b/src/backup.c @@ -2161,7 +2161,7 @@ backup_files(void *arg) arguments->nodeInfo->checksum_version, arguments->nodeInfo->ptrack_version_num, arguments->nodeInfo->ptrack_schema, - arguments->hdr_map, true); + arguments->hdr_map, false); } else { diff --git a/src/catalog.c b/src/catalog.c index 3a98b764..185ba28b 100644 --- a/src/catalog.c +++ b/src/catalog.c @@ -474,9 +474,11 @@ catalog_get_backup_list(const char *instance_name, time_t requested_backup_id) backup->database_dir = pgut_malloc(MAXPGPATH); join_path_components(backup->database_dir, backup->root_dir, DATABASE_DIR); - /* block header map */ + /* block header map, TODO: move to separate function */ backup->hdr_map.path = pgut_malloc(MAXPGPATH); join_path_components(backup->hdr_map.path, backup->database_dir, HEADER_MAP); + backup->hdr_map.path_tmp = pgut_malloc(MAXPGPATH); + join_path_components(backup->hdr_map.path_tmp, backup->database_dir, HEADER_MAP_TMP); backup->hdr_map.fp = NULL; /* TODO: save encoded backup id */ @@ -855,6 +857,8 @@ pgBackupCreateDir(pgBackup *backup) /* block header map */ backup->hdr_map.path = pgut_malloc(MAXPGPATH); join_path_components(backup->hdr_map.path, backup->database_dir, HEADER_MAP); + backup->hdr_map.path_tmp = pgut_malloc(MAXPGPATH); + join_path_components(backup->hdr_map.path_tmp, backup->database_dir, HEADER_MAP_TMP); backup->hdr_map.fp = NULL; /* create directories for actual backup files */ @@ -2282,6 +2286,7 @@ pgBackupInit(pgBackup *backup) backup->content_crc = 0; backup->hdr_map.path = NULL; + backup->hdr_map.path_tmp = NULL; backup->hdr_map.fp = NULL; backup->hdr_map.mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; } @@ -2298,6 +2303,7 @@ pgBackupFree(void *backup) pg_free(b->database_dir); pg_free(b->note); pg_free(b->hdr_map.path); + pg_free(b->hdr_map.path_tmp); pg_free(backup); } diff --git a/src/data.c b/src/data.c index 28230b7e..132255af 100644 --- a/src/data.c +++ b/src/data.c @@ -31,8 +31,6 @@ typedef struct DataPage char data[BLCKSZ]; } DataPage; -static BackupPageHeader2* get_data_file_headers(HeaderMap *hdr_map, pgFile *file, uint32 backup_version); -static void write_page_headers(BackupPageHeader2 *headers, pgFile *file, HeaderMap *hdr_map); static bool get_compressed_page_meta(FILE *in, const char *fullpath, BackupPageHeader* bph, pg_crc32 *crc, bool use_crc32c); @@ -538,7 +536,7 @@ backup_data_file(ConnectionArgs* conn_arg, pgFile *file, XLogRecPtr prev_backup_start_lsn, BackupMode backup_mode, CompressAlg calg, int clevel, uint32 checksum_version, int ptrack_version_num, const char *ptrack_schema, - HeaderMap *hdr_map, bool missing_ok) + HeaderMap *hdr_map, bool is_merge) { int rc; bool use_pagemap; @@ -629,7 +627,7 @@ backup_data_file(ConnectionArgs* conn_arg, pgFile *file, /* check for errors */ if (rc == FILE_MISSING) { - elog(LOG, "File \"%s\" is not found", from_fullpath); + elog(is_merge ? ERROR : LOG, "File not found: \"%s\"", from_fullpath); file->write_size = FILE_NOT_FOUND; goto cleanup; } @@ -685,7 +683,7 @@ cleanup: FIN_FILE_CRC32(true, file->crc); /* dump page headers */ - write_page_headers(headers, file, hdr_map); + write_page_headers(headers, file, hdr_map, is_merge); pg_free(errmsg); pg_free(file->pagemap.bitmap); @@ -818,8 +816,12 @@ restore_data_file(parray *parent_chain, pgFile *dest_file, FILE *out, /* set stdio buffering for input data file */ setvbuf(in, in_buf, _IOFBF, STDIO_BUFSIZE); +// elog(INFO, "N_HEADERS: %i", tmp_file->n_headers); +// elog(INFO, "File: %s", tmp_file->rel_path); +// elog(INFO, "Backup: %s", base36enc(backup->start_time)); + /* get headers for this file */ - if (use_headers) + if (use_headers && tmp_file->n_headers > 0) headers = get_data_file_headers(&(backup->hdr_map), tmp_file, parse_program_version(backup->program_version)); @@ -2163,8 +2165,8 @@ get_data_file_headers(HeaderMap *hdr_map, pgFile *file, uint32 backup_version) FIN_FILE_CRC32(true, hdr_crc); if (hdr_crc != file->hdr_crc) - elog(ERROR, "Header file crc mismatch \"%s\", current: %u, expected: %u", - hdr_map->path, hdr_crc, file->hdr_crc); + elog(ERROR, "Header map for file \"%s\" crc mismatch \"%s\" offset: %lu, len: %lu, current: %u, expected: %u", + file->rel_path, hdr_map->path, file->hdr_off, read_len, hdr_crc, file->hdr_crc); if (fclose(in)) elog(ERROR, "Cannot close header file \"%s\": %s", hdr_map->path, strerror(errno)); @@ -2173,29 +2175,35 @@ get_data_file_headers(HeaderMap *hdr_map, pgFile *file, uint32 backup_version) } void -write_page_headers(BackupPageHeader2 *headers, pgFile *file, HeaderMap *hdr_map) +write_page_headers(BackupPageHeader2 *headers, pgFile *file, HeaderMap *hdr_map, bool is_merge) { - size_t read_len = 0; + size_t read_len = 0; + char *map_path = NULL; if (file->n_headers <= 0) return; + /* when running merge we must save headers into the temp map */ + map_path = (is_merge) ? hdr_map->path_tmp : hdr_map->path; + /* writing to header map must be serialized */ pthread_lock(&(hdr_map->mutex)); /* what if we crash while trying to obtain mutex? */ if (!hdr_map->fp) { - hdr_map->fp = fopen(hdr_map->path, PG_BINARY_W); + elog(LOG, "Creating page header map \"%s\"", map_path); + + hdr_map->fp = fopen(map_path, PG_BINARY_W); if (hdr_map->fp == NULL) elog(ERROR, "Cannot open header file \"%s\": %s", - hdr_map->path, strerror(errno)); + map_path, strerror(errno)); /* disable buffering for header file */ setvbuf(hdr_map->fp, NULL, _IONBF, BUFSIZ); /* update file permission */ - if (chmod(hdr_map->path, FILE_PERMISSION) == -1) - elog(ERROR, "Cannot change mode of \"%s\": %s", hdr_map->path, + if (chmod(map_path, FILE_PERMISSION) == -1) + elog(ERROR, "Cannot change mode of \"%s\": %s", map_path, strerror(errno)); file->hdr_off = 0; @@ -2211,7 +2219,10 @@ write_page_headers(BackupPageHeader2 *headers, pgFile *file, HeaderMap *hdr_map) FIN_FILE_CRC32(true, file->hdr_crc); if (fwrite(headers, 1, read_len, hdr_map->fp) != read_len) - elog(ERROR, "Cannot write to file \"%s\": %s", hdr_map->path, strerror(errno)); + elog(ERROR, "Cannot write to file \"%s\": %s", map_path, strerror(errno)); + + elog(VERBOSE, "Writing header map for file \"%s\" offset: %lu, len: %lu, crc: %u", + file->rel_path, file->hdr_off, read_len, file->hdr_crc); pthread_mutex_unlock(&(hdr_map->mutex)); } diff --git a/src/merge.c b/src/merge.c index 9548c308..a51756df 100644 --- a/src/merge.c +++ b/src/merge.c @@ -550,11 +550,11 @@ merge_chain(parray *parent_chain, pgBackup *full_backup, pgBackup *dest_backup) elog(INFO, "Validate parent chain for backup %s", base36enc(dest_backup->start_time)); - /* forbid merge retry for failed merges between 2.4.0 and any + /* Forbid merge retry for failed merges between 2.4.0 and any * older version. Several format changes makes it impossible * to determine the exact format any speific file is got. */ - if (full_backup->status == BACKUP_STATUS_MERGING && + if (is_retry && parse_program_version(dest_backup->program_version) >= 20400 && parse_program_version(full_backup->program_version) < 20400) { @@ -705,6 +705,23 @@ merge_chain(parray *parent_chain, pgBackup *full_backup, pgBackup *dest_backup) elog(ERROR, "Backup files merging failed, time elapsed: %s", pretty_time); + /* If temp header map descriptor is open, then close it and make rename */ + if (full_backup->hdr_map.fp) + { + if (fclose(full_backup->hdr_map.fp)) + elog(ERROR, "Cannot close file \"%s\"", full_backup->hdr_map.path); + + /* sync new header map to dist */ + if (fio_sync(full_backup->hdr_map.path_tmp, FIO_BACKUP_HOST) != 0) + elog(ERROR, "Cannot sync temp header map \"%s\": %s", + full_backup->hdr_map.path_tmp, strerror(errno)); + + /* Replace old header map with new one */ + if (rename(full_backup->hdr_map.path_tmp, full_backup->hdr_map.path) == -1) + elog(ERROR, "Could not rename file \"%s\" to \"%s\": %s", + full_backup->hdr_map.path_tmp, full_backup->hdr_map.path, strerror(errno)); + } + /* * Update FULL backup metadata. * We cannot set backup status to OK just yet, @@ -847,6 +864,12 @@ merge_rename: full_backup->root_dir = pgut_strdup(destination_path); } + /* Reinit some path variables */ + join_path_components(full_backup->database_dir, full_backup->root_dir, DATABASE_DIR); + join_path_components(full_backup->hdr_map.path, full_backup->database_dir, HEADER_MAP); + join_path_components(full_backup->hdr_map.path_tmp, full_backup->database_dir, HEADER_MAP_TMP); + full_backup->hdr_map.fp = NULL; + /* If we crash here, it will produce full backup in MERGED * status, located in directory with wrong backup id. * It should not be a problem. @@ -1033,6 +1056,7 @@ merge_files(void *arg) if (file && file->n_blocks == dest_file->n_blocks) { + BackupPageHeader2 *headers = NULL; elog(VERBOSE, "The file didn`t changed since FULL backup, skip merge: \"%s\"", file->rel_path); @@ -1052,6 +1076,18 @@ merge_files(void *arg) else tmp_file->uncompressed_size = tmp_file->write_size; + /* Copy header metadata from old map into a new one */ + tmp_file->n_headers = file->n_headers; + headers = get_data_file_headers(&(arguments->full_backup->hdr_map), file, + parse_program_version(arguments->full_backup->program_version)); + + /* sanity */ + if (!headers && file->n_headers > 0) + elog(ERROR, "Failed to get headers for file \"%s\"", file->rel_path); + + write_page_headers(headers, tmp_file, &(arguments->full_backup->hdr_map), true); + pg_free(headers); + //TODO: report in_place merge bytes. goto done; } @@ -1205,16 +1241,14 @@ merge_data_file(parray *parent_chain, pgBackup *full_backup, * 16KB. * TODO: maybe we should just trust dest_file->n_blocks? * No, we can`t, because current binary can be used to merge - * 2 backups of old versions, were n_blocks is missing. + * 2 backups of old versions, where n_blocks is missing. */ backup_data_file(NULL, tmp_file, to_fullpath_tmp1, to_fullpath_tmp2, InvalidXLogRecPtr, BACKUP_MODE_FULL, dest_backup->compress_alg, dest_backup->compress_level, dest_backup->checksum_version, 0, NULL, - - /* TODO: add header map */ - NULL, false); + &(full_backup->hdr_map), true); /* drop restored temp file */ if (unlink(to_fullpath_tmp1) == -1) diff --git a/src/pg_probackup.h b/src/pg_probackup.h index 73266e51..a3a06d68 100644 --- a/src/pg_probackup.h +++ b/src/pg_probackup.h @@ -67,6 +67,7 @@ extern const char *PROGRAM_EMAIL; #define EXTERNAL_DIR "external_directories/externaldir" #define DATABASE_MAP "database_map" #define HEADER_MAP "block_header_map" +#define HEADER_MAP_TMP "block_header_map_tmp" /* Timeout defaults */ #define ARCHIVE_TIMEOUT_DEFAULT 300 @@ -363,6 +364,7 @@ typedef struct PGNodeInfo typedef struct HeaderMap { char *path; + char *path_tmp; /* used only in merge */ FILE *fp; off_t offset; pthread_mutex_t mutex; @@ -1014,6 +1016,9 @@ extern pid_t check_postmaster(const char *pgdata); extern bool validate_file_pages(pgFile *file, const char *fullpath, XLogRecPtr stop_lsn, uint32 checksum_version, uint32 backup_version, HeaderMap *hdr_map); + +extern BackupPageHeader2* get_data_file_headers(HeaderMap *hdr_map, pgFile *file, uint32 backup_version); +extern void write_page_headers(BackupPageHeader2 *headers, pgFile *file, HeaderMap *hdr_map, bool is_merge); /* parsexlog.c */ extern bool extractPageMap(const char *archivedir, uint32 wal_seg_size, XLogRecPtr startpoint, TimeLineID start_tli,