You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-07-05 00:28:52 +02:00
Preserve partial files during block incremental delta restore.
Previously files that were smaller than the expected size were not preserved for block incremental, even though it is possible that block incremental could make use of a partial file. One example is when a restore encounters an error. On retry the partial file can be used as a starting point rather than copying again from the beginning. Another example is restoring a backup where a file is larger than what already exists in the data directory. Preserve any size file when block incremental will be used for the delta in order to reuse partial files when possible. If the file is smaller than expected then disable the whole-file checksum to reduce overhead.
This commit is contained in:
@ -15,6 +15,17 @@
|
|||||||
</release-bug-list>
|
</release-bug-list>
|
||||||
|
|
||||||
<release-improvement-list>
|
<release-improvement-list>
|
||||||
|
<release-item>
|
||||||
|
<github-pull-request id="2254"/>
|
||||||
|
|
||||||
|
<release-item-contributor-list>
|
||||||
|
<release-item-contributor id="david.steele"/>
|
||||||
|
<release-item-reviewer id="stephen.frost"/>
|
||||||
|
</release-item-contributor-list>
|
||||||
|
|
||||||
|
<p>Preserve partial files during block incremental delta restore.</p>
|
||||||
|
</release-item>
|
||||||
|
|
||||||
<release-item>
|
<release-item>
|
||||||
<github-pull-request id="2197"/>
|
<github-pull-request id="2197"/>
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ restoreFile(
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Only continue delta if the file size is as expected or larger
|
// Only continue delta if the file size is as expected or larger
|
||||||
if (info.size >= file->size)
|
if (info.size >= file->size || file->blockIncrMapSize != 0)
|
||||||
{
|
{
|
||||||
const char *const fileName = strZ(storagePathP(storagePg(), file->name));
|
const char *const fileName = strZ(storagePathP(storagePg(), file->name));
|
||||||
|
|
||||||
@ -115,6 +115,12 @@ restoreFile(
|
|||||||
|
|
||||||
// Update info
|
// Update info
|
||||||
info = storageInfoP(storagePg(), file->name, .followLink = true);
|
info = storageInfoP(storagePg(), file->name, .followLink = true);
|
||||||
|
|
||||||
|
// For block incremental it is very important that the file be exactly the expected size, so
|
||||||
|
// make sure the truncate worked as expected
|
||||||
|
CHECK_FMT(
|
||||||
|
FileWriteError, info.size == file->size, "unable to truncate '%s' to %" PRIu64 " bytes",
|
||||||
|
strZ(file->name), file->size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate checksum for the file if size is not zero
|
// Generate checksum for the file if size is not zero
|
||||||
@ -123,7 +129,10 @@ restoreFile(
|
|||||||
if (file->size != 0)
|
if (file->size != 0)
|
||||||
{
|
{
|
||||||
read = storageReadIo(storageNewReadP(storagePg(), file->name));
|
read = storageReadIo(storageNewReadP(storagePg(), file->name));
|
||||||
ioFilterGroupAdd(ioReadFilterGroup(read), cryptoHashNew(hashTypeSha1));
|
|
||||||
|
// Calculate checksum only when size matches
|
||||||
|
if (info.size == file->size)
|
||||||
|
ioFilterGroupAdd(ioReadFilterGroup(read), cryptoHashNew(hashTypeSha1));
|
||||||
|
|
||||||
// Generate block checksum list if block incremental
|
// Generate block checksum list if block incremental
|
||||||
if (file->blockIncrMapSize != 0)
|
if (file->blockIncrMapSize != 0)
|
||||||
@ -136,11 +145,12 @@ restoreFile(
|
|||||||
ioReadDrain(read);
|
ioReadDrain(read);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the checksum is the same (or file is zero size) then no need to copy the file
|
// If size/checksum is the same (or file is zero size) then no need to copy the file
|
||||||
if (file->size == 0 ||
|
if (file->size == 0 ||
|
||||||
bufEq(
|
(info.size == file->size &&
|
||||||
file->checksum,
|
bufEq(
|
||||||
pckReadBinP(ioFilterGroupResultP(ioReadFilterGroup(read), CRYPTO_HASH_FILTER_TYPE))))
|
file->checksum,
|
||||||
|
pckReadBinP(ioFilterGroupResultP(ioReadFilterGroup(read), CRYPTO_HASH_FILTER_TYPE)))))
|
||||||
{
|
{
|
||||||
// If the checksum/size are now the same but the time is not, then set the time back to the
|
// If the checksum/size are now the same but the time is not, then set the time back to the
|
||||||
// backup time. This helps with unit testing, but also presents a pristine version of the
|
// backup time. This helps with unit testing, but also presents a pristine version of the
|
||||||
|
@ -3081,8 +3081,9 @@ testRun(void)
|
|||||||
const String *pgPath = STRDEF(TEST_PATH "/pg");
|
const String *pgPath = STRDEF(TEST_PATH "/pg");
|
||||||
const String *repoPath = STRDEF(TEST_PATH "/repo");
|
const String *repoPath = STRDEF(TEST_PATH "/repo");
|
||||||
|
|
||||||
// Created pg_control
|
// Created pg_control and PG_VERSION
|
||||||
HRN_PG_CONTROL_PUT(storagePgWrite(), PG_VERSION_15, .pageChecksum = false);
|
HRN_PG_CONTROL_PUT(storagePgWrite(), PG_VERSION_15, .pageChecksum = false);
|
||||||
|
HRN_STORAGE_PUT_Z(storagePgWrite(), PG_FILE_PGVERSION, PG_VERSION_15_Z);
|
||||||
|
|
||||||
// Create encrypted stanza
|
// Create encrypted stanza
|
||||||
StringList *argList = strLstNew();
|
StringList *argList = strLstNew();
|
||||||
@ -3172,6 +3173,7 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_STORAGE_LIST(
|
TEST_STORAGE_LIST(
|
||||||
storagePg(), NULL,
|
storagePg(), NULL,
|
||||||
|
"PG_VERSION\n"
|
||||||
"base/\n"
|
"base/\n"
|
||||||
"base/1/\n"
|
"base/1/\n"
|
||||||
"base/1/2\n"
|
"base/1/2\n"
|
||||||
@ -3180,6 +3182,46 @@ testRun(void)
|
|||||||
"global/pg_control\n"
|
"global/pg_control\n"
|
||||||
"postgresql.auto.conf\n",
|
"postgresql.auto.conf\n",
|
||||||
.level = storageInfoLevelType);
|
.level = storageInfoLevelType);
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("delta restore with block incr");
|
||||||
|
|
||||||
|
// Use detail log level to catch block incremental restore message
|
||||||
|
harnessLogLevelSet(logLevelDetail);
|
||||||
|
|
||||||
|
// Shrink file to make sure block incremental delta will reuse it
|
||||||
|
relation = bufNew(128 * 1024);
|
||||||
|
memset(bufPtr(relation), 0, bufSize(relation));
|
||||||
|
bufUsedSet(relation, bufSize(relation));
|
||||||
|
|
||||||
|
HRN_STORAGE_PUT(storagePgWrite(), PG_PATH_BASE "/1/2", relation);
|
||||||
|
|
||||||
|
argList = strLstNew();
|
||||||
|
hrnCfgArgRawZ(argList, cfgOptStanza, "test1");
|
||||||
|
hrnCfgArgRaw(argList, cfgOptRepoPath, repoPath);
|
||||||
|
hrnCfgArgRaw(argList, cfgOptPgPath, pgPath);
|
||||||
|
hrnCfgArgRawZ(argList, cfgOptSpoolPath, TEST_PATH "/spool");
|
||||||
|
hrnCfgArgRawBool(argList, cfgOptDelta, true);
|
||||||
|
hrnCfgArgRawZ(argList, cfgOptRepoCipherType, "aes-256-cbc");
|
||||||
|
hrnCfgEnvRawZ(cfgOptRepoCipherPass, TEST_CIPHER_PASS);
|
||||||
|
HRN_CFG_LOAD(cfgCmdRestore, argList);
|
||||||
|
|
||||||
|
TEST_RESULT_VOID(cmdRestore(), "restore");
|
||||||
|
|
||||||
|
TEST_STORAGE_LIST(
|
||||||
|
storagePg(), NULL,
|
||||||
|
"PG_VERSION\n"
|
||||||
|
"base/\n"
|
||||||
|
"base/1/\n"
|
||||||
|
"base/1/2\n"
|
||||||
|
"base/1/3\n"
|
||||||
|
"global/\n"
|
||||||
|
"global/pg_control\n"
|
||||||
|
"postgresql.auto.conf\n",
|
||||||
|
.level = storageInfoLevelType);
|
||||||
|
|
||||||
|
// Check that file was restored to full size with a partial write
|
||||||
|
TEST_RESULT_LOG_EMPTY_OR_CONTAINS(", bi 128KB/256KB, ");
|
||||||
}
|
}
|
||||||
|
|
||||||
FUNCTION_HARNESS_RETURN_VOID();
|
FUNCTION_HARNESS_RETURN_VOID();
|
||||||
|
Reference in New Issue
Block a user