You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-07-13 01:00:23 +02:00
Allow backups to run concurrently on different repositories.
The prior locking only allowed one backup per stanza, which was required by PostgreSQL <= 9.5 and didn't present a problem when only one stanza could be created. Now that multiple stanzas are allowed relax this restriction so that backups can run concurrently for PostgreSQL > 9.5. To do this, update the locking to be per stanza and repo rather than per stanza. Remotes are not aware of the repos that require locking so send an explicit list of files to be locked to the remote. Also remove the advisory lock for PostgreSQL > 9.5. For info output the running backups are combined for progress output in order to avoid changing the JSON format. It definitely makes sense to have per repo progress as well but that will be left for a future commit.
This commit is contained in:
@ -87,6 +87,7 @@ option:
|
||||
type: string, default: CFGOPTDEF_CONFIG_PATH "/" PROJECT_CONFIG_INCLUDE_PATH, default-literal: true, command: {noop: {}}}
|
||||
job-retry: {type: string, required: false, deprecate: {job-retry-old: {}}, command: {noop: {}}}
|
||||
job-retry-interval: {type: string, required: false, command: {noop: {}}}
|
||||
lock: {type: list, required: false, command: {noop: {}}}
|
||||
log-level-file: {type: string, required: false, command: {noop: {}}}
|
||||
log-level-stderr: {type: string, required: false, command: {noop: {}}}
|
||||
pg: {type: string, required: false, command: {noop: {}}}
|
||||
|
@ -15,6 +15,7 @@
|
||||
<option id="compress-level-network"><summary></summary><text><p></p></text></option>
|
||||
<option id="job-retry"><summary></summary><text><p></p></text></option>
|
||||
<option id="job-retry-interval"><summary></summary><text><p></p></text></option>
|
||||
<option id="lock"><summary></summary><text><p></p></text></option>
|
||||
<option id="log-level-file"><summary></summary><text><p></p></text></option>
|
||||
<option id="log-level-stderr"><summary></summary><text><p></p></text></option>
|
||||
<option id="repo"><summary></summary><text><p></p></text></option>
|
||||
|
@ -23,6 +23,24 @@
|
||||
</release-bug-list>
|
||||
|
||||
<release-improvement-list>
|
||||
<release-item>
|
||||
<commit subject="Refactor lock module.">
|
||||
<github-pull-request id="2371"/>
|
||||
</commit>
|
||||
<commit subject="Add remote locks for stanza commands missed in 31c7824a."/>
|
||||
<commit subject="Allow backups to run concurrently on different repositories.">
|
||||
<github-pull-request id="2389"/>
|
||||
</commit>
|
||||
|
||||
<release-item-contributor-list>
|
||||
<release-item-contributor id="david.steele"/>
|
||||
<release-item-reviewer id="reid.thompson"/>
|
||||
<release-item-reviewer id="stefan.fercot"/>
|
||||
</release-item-contributor-list>
|
||||
|
||||
<p>Allow backups to run concurrently on different repositories.</p>
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<github-pull-request id="2379"/>
|
||||
|
||||
@ -46,21 +64,5 @@
|
||||
<p>Allow alternative WAL segment sizes for PostgreSQL &le; 10.</p>
|
||||
</release-item>
|
||||
</release-improvement-list>
|
||||
|
||||
<release-development-list>
|
||||
<release-item>
|
||||
<commit subject="Refactor lock module.">
|
||||
<github-pull-request id="2371"/>
|
||||
</commit>
|
||||
<commit subject="Add remote locks for stanza commands missed in 31c7824a."/>
|
||||
|
||||
<release-item-contributor-list>
|
||||
<release-item-contributor id="david.steele"/>
|
||||
<release-item-reviewer id="reid.thompson"/>
|
||||
</release-item-contributor-list>
|
||||
|
||||
<p>Refactor lock module.</p>
|
||||
</release-item>
|
||||
</release-development-list>
|
||||
</release-core-list>
|
||||
</release>
|
||||
|
@ -543,6 +543,20 @@ option:
|
||||
local: {}
|
||||
remote: {}
|
||||
|
||||
lock:
|
||||
type: list
|
||||
internal: true
|
||||
required: false
|
||||
command:
|
||||
annotate: {}
|
||||
archive-push: {}
|
||||
backup: {}
|
||||
stanza-create: {}
|
||||
stanza-delete: {}
|
||||
stanza-upgrade: {}
|
||||
command-role:
|
||||
remote: {}
|
||||
|
||||
remote-type:
|
||||
type: string-id
|
||||
internal: true
|
||||
|
@ -49,6 +49,7 @@ cmdStop(void)
|
||||
cfgOptionTest(cfgOptStanza) ? strNewFmt("%s-", strZ(cfgOptionStr(cfgOptStanza))) : NULL;
|
||||
const StringList *const lockPathFileList = strLstSort(
|
||||
storageListP(storageLocal(), lockPath, .errorOnMissing = true), sortOrderAsc);
|
||||
List *const pidList = lstNewP(sizeof(int), .comparator = lstComparatorInt);
|
||||
|
||||
// Find each lock file and send term signals to the processes
|
||||
for (unsigned int lockPathFileIdx = 0; lockPathFileIdx < strLstSize(lockPathFileList); lockPathFileIdx++)
|
||||
@ -69,6 +70,13 @@ cmdStop(void)
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the process has already been killed then skip it. This is possible because a single process may be holding
|
||||
// multiple lock files.
|
||||
if (lstFind(pidList, &lockResult.data.processId) != NULL)
|
||||
continue;
|
||||
|
||||
lstAdd(pidList, &lockResult.data.processId);
|
||||
|
||||
// The lock file is valid so that means there is a running process -- send a term signal to the process
|
||||
if (kill(lockResult.data.processId, SIGTERM) != 0)
|
||||
LOG_WARN_FMT("unable to send term signal to process %d", lockResult.data.processId);
|
||||
|
@ -133,11 +133,9 @@ typedef struct InfoStanzaRepo
|
||||
const String *name; // Name of the stanza
|
||||
uint64_t currentPgSystemId; // Current postgres system id for the stanza
|
||||
unsigned int currentPgVersion; // Current postgres version for the stanza
|
||||
bool backupLockChecked; // Has the check for a backup lock already been performed?
|
||||
bool backupLockHeld; // Is backup lock held on the system where info command is run?
|
||||
const Variant *percentComplete; // Percentage of backup complete * 100 (when not NULL)
|
||||
const Variant *sizeComplete; // Completed size of the backup in bytes
|
||||
const Variant *size; // Total size of the backup in bytes
|
||||
uint64_t sizeComplete; // Completed size of the backup in bytes
|
||||
uint64_t size; // Total size of the backup in bytes
|
||||
InfoRepoData *repoList; // List of configured repositories
|
||||
} InfoStanzaRepo;
|
||||
|
||||
@ -240,14 +238,18 @@ stanzaStatus(const int code, const InfoStanzaRepo *const stanzaData, const Varia
|
||||
KeyValue *const backupLockKv = kvPutKv(lockKv, STATUS_KEY_LOCK_BACKUP_VAR);
|
||||
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_HELD_VAR, VARBOOL(stanzaData->backupLockHeld));
|
||||
|
||||
if (stanzaData->percentComplete != NULL && cfgOptionStrId(cfgOptOutput) != CFGOPTVAL_OUTPUT_JSON)
|
||||
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_PERCENT_COMPLETE_VAR, stanzaData->percentComplete);
|
||||
if (stanzaData->size != 0)
|
||||
{
|
||||
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_SIZE_COMPLETE_VAR, VARUINT64(stanzaData->sizeComplete));
|
||||
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_SIZE_VAR, VARUINT64(stanzaData->size));
|
||||
|
||||
if (stanzaData->sizeComplete != NULL)
|
||||
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_SIZE_COMPLETE_VAR, stanzaData->sizeComplete);
|
||||
|
||||
if (stanzaData->size != NULL)
|
||||
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_SIZE_VAR, stanzaData->size);
|
||||
if (cfgOptionStrId(cfgOptOutput) != CFGOPTVAL_OUTPUT_JSON)
|
||||
{
|
||||
kvPut(
|
||||
backupLockKv, STATUS_KEY_LOCK_BACKUP_PERCENT_COMPLETE_VAR,
|
||||
VARUINT((unsigned int)(((double)stanzaData->sizeComplete / (double)stanzaData->size) * 10000)));
|
||||
}
|
||||
}
|
||||
|
||||
FUNCTION_TEST_RETURN_VOID();
|
||||
}
|
||||
@ -1295,20 +1297,19 @@ infoUpdateStanza(
|
||||
infoPgCipherPass(infoBackupPg(stanzaRepo->repoList[repoIdx].backupInfo)));
|
||||
}
|
||||
|
||||
// If a backup lock check has not already been performed, then do so
|
||||
if (!stanzaRepo->backupLockChecked)
|
||||
{
|
||||
// If there is a valid backup lock for this stanza then backup/expire must be running
|
||||
const LockReadResult lockResult = cmdLockRead(lockTypeBackup, stanzaRepo->name);
|
||||
const LockReadResult lockResult = cmdLockRead(lockTypeBackup, stanzaRepo->name, repoIdx);
|
||||
|
||||
stanzaRepo->backupLockHeld = lockResult.status == lockReadStatusValid;
|
||||
stanzaRepo->backupLockChecked = true;
|
||||
|
||||
if (stanzaRepo->backupLockHeld)
|
||||
if (lockResult.status == lockReadStatusValid)
|
||||
{
|
||||
stanzaRepo->percentComplete = lockResult.data.percentComplete;
|
||||
stanzaRepo->sizeComplete = lockResult.data.sizeComplete;
|
||||
stanzaRepo->size = lockResult.data.size;
|
||||
stanzaRepo->backupLockHeld = true;
|
||||
|
||||
if (lockResult.data.size != NULL)
|
||||
{
|
||||
ASSERT(lockResult.data.size != NULL);
|
||||
|
||||
stanzaRepo->sizeComplete += varUInt64(lockResult.data.sizeComplete);
|
||||
stanzaRepo->size += varUInt64(lockResult.data.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,19 +25,57 @@ static const char *const lockTypeName[] =
|
||||
"backup", // lockTypeBackup
|
||||
};
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
/***********************************************************************************************************************************
|
||||
Generate lock file name
|
||||
***********************************************************************************************************************************/
|
||||
static String *
|
||||
cmdLockFileName(const String *const stanza, const LockType lockType)
|
||||
cmdLockFileName(const String *const stanza, const LockType lockType, const unsigned int repoKey)
|
||||
{
|
||||
FUNCTION_TEST_BEGIN();
|
||||
FUNCTION_TEST_PARAM(STRING, stanza);
|
||||
FUNCTION_TEST_PARAM(ENUM, lockType);
|
||||
FUNCTION_TEST_PARAM(UINT, repoKey);
|
||||
FUNCTION_TEST_END();
|
||||
|
||||
ASSERT(stanza != NULL);
|
||||
ASSERT(lockType < lockTypeAll);
|
||||
ASSERT(repoKey > 0);
|
||||
|
||||
FUNCTION_TEST_RETURN(STRING, strNewFmt("%s-%s" LOCK_FILE_EXT, strZ(stanza), lockTypeName[lockType]));
|
||||
FUNCTION_TEST_RETURN(STRING, strNewFmt("%s-%s-%u" LOCK_FILE_EXT, strZ(stanza), lockTypeName[lockType], repoKey));
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
FN_EXTERN StringList *
|
||||
cmdLockList(void)
|
||||
{
|
||||
FUNCTION_TEST_VOID();
|
||||
|
||||
ASSERT(cfgLockType() != lockTypeNone);
|
||||
|
||||
StringList *const result = strLstNew();
|
||||
|
||||
MEM_CONTEXT_TEMP_BEGIN()
|
||||
{
|
||||
const LockType lockType = cfgLockType();
|
||||
const LockType lockMin = lockType == lockTypeAll ? lockTypeArchive : lockType;
|
||||
const LockType lockMax = lockType == lockTypeAll ? (lockTypeAll - 1) : lockType;
|
||||
|
||||
const bool repoSet = cfgOptionTest(cfgOptRepo);
|
||||
const unsigned int repoMin = repoSet ? cfgOptionGroupIdxDefault(cfgOptGrpRepo) : 0;
|
||||
const unsigned int repoMax = repoSet ? repoMin : cfgOptionGroupIdxTotal(cfgOptGrpRepo) - 1;
|
||||
|
||||
for (LockType lockIdx = lockMin; lockIdx <= lockMax; lockIdx++)
|
||||
{
|
||||
for (unsigned int repoIdx = repoMin; repoIdx <= repoMax; repoIdx++)
|
||||
{
|
||||
strLstAdd(
|
||||
result, cmdLockFileName(cfgOptionStr(cfgOptStanza), lockIdx, cfgOptionGroupIdxToKey(cfgOptGrpRepo, repoIdx)));
|
||||
}
|
||||
}
|
||||
}
|
||||
MEM_CONTEXT_TEMP_END();
|
||||
|
||||
FUNCTION_TEST_RETURN(STRING_LIST, result);
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
@ -51,24 +89,21 @@ cmdLockAcquire(const LockAcquireParam param)
|
||||
bool result = true;
|
||||
|
||||
// Don't allow return on no lock when locking more than one file. This makes cleanup difficult and there are no known use cases.
|
||||
const LockType lockType = cfgLockType();
|
||||
ASSERT(!param.returnOnNoLock || lockType != lockTypeAll);
|
||||
ASSERT(!param.returnOnNoLock || cfgLockType() != lockTypeAll);
|
||||
|
||||
// Don't allow another lock if one is already held
|
||||
if (cmdLockLocal.held)
|
||||
THROW(AssertError, "lock is already held by this process");
|
||||
|
||||
// Lock files
|
||||
const LockType lockMin = lockType == lockTypeAll ? lockTypeArchive : lockType;
|
||||
const LockType lockMax = lockType == lockTypeAll ? (lockTypeAll - 1) : lockType;
|
||||
|
||||
for (LockType lockIdx = lockMin; lockIdx <= lockMax; lockIdx++)
|
||||
MEM_CONTEXT_TEMP_BEGIN()
|
||||
{
|
||||
String *const lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), lockIdx);
|
||||
const StringList *const lockList = cfgOptionTest(cfgOptLock) ? strLstNewVarLst(cfgOptionLst(cfgOptLock)) : cmdLockList();
|
||||
|
||||
result = lockAcquire(lockFileName, param);
|
||||
strFree(lockFileName);
|
||||
for (unsigned int lockListIdx = 0; lockListIdx < strLstSize(lockList); lockListIdx++)
|
||||
result = lockAcquire(strLstGet(lockList, lockListIdx), param);
|
||||
}
|
||||
MEM_CONTEXT_TEMP_END();
|
||||
|
||||
// Set lock held flag
|
||||
cmdLockLocal.held = result;
|
||||
@ -86,7 +121,8 @@ cmdLockWrite(const LockWriteParam param)
|
||||
FUNCTION_LOG_PARAM(VARIANT, param.size);
|
||||
FUNCTION_LOG_END();
|
||||
|
||||
String *const lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), cfgLockType());
|
||||
String *const lockFileName = cmdLockFileName(
|
||||
cfgOptionStr(cfgOptStanza), cfgLockType(), cfgOptionGroupIdxToKey(cfgOptGrpRepo, cfgOptionGroupIdxDefault(cfgOptGrpRepo)));
|
||||
|
||||
lockWrite(lockFileName, param);
|
||||
strFree(lockFileName);
|
||||
@ -96,11 +132,12 @@ cmdLockWrite(const LockWriteParam param)
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
FN_EXTERN LockReadResult
|
||||
cmdLockRead(const LockType lockType, const String *const stanza)
|
||||
cmdLockRead(const LockType lockType, const String *const stanza, const unsigned int repoIdx)
|
||||
{
|
||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||
FUNCTION_LOG_PARAM(ENUM, lockType);
|
||||
FUNCTION_LOG_PARAM(STRING, stanza);
|
||||
FUNCTION_LOG_PARAM(UINT, repoIdx);
|
||||
FUNCTION_LOG_END();
|
||||
|
||||
ASSERT(lockType == lockTypeBackup);
|
||||
@ -112,7 +149,7 @@ cmdLockRead(const LockType lockType, const String *const stanza)
|
||||
|
||||
MEM_CONTEXT_TEMP_BEGIN()
|
||||
{
|
||||
const String *const lockFileName = cmdLockFileName(stanza, lockType);
|
||||
const String *const lockFileName = cmdLockFileName(stanza, lockType, cfgOptionGroupIdxToKey(cfgOptGrpRepo, repoIdx));
|
||||
|
||||
MEM_CONTEXT_PRIOR_BEGIN()
|
||||
{
|
||||
|
@ -10,6 +10,9 @@ Command Lock Handler
|
||||
/***********************************************************************************************************************************
|
||||
Functions
|
||||
***********************************************************************************************************************************/
|
||||
// Get list of required locks
|
||||
FN_EXTERN StringList *cmdLockList(void);
|
||||
|
||||
// Acquire a command lock. This will involve locking one or more files on disk depending on the lock type. Most operations only
|
||||
// acquire a single lock type (archive or backup), but the stanza commands all need to lock both.
|
||||
#define cmdLockAcquireP(...) \
|
||||
@ -24,7 +27,7 @@ FN_EXTERN bool cmdLockAcquire(LockAcquireParam param);
|
||||
FN_EXTERN void cmdLockWrite(LockWriteParam param);
|
||||
|
||||
// Read a command lock file held by another process to get information about what the process is doing
|
||||
FN_EXTERN LockReadResult cmdLockRead(LockType lockType, const String *stanza);
|
||||
FN_EXTERN LockReadResult cmdLockRead(LockType lockType, const String *stanza, unsigned int repoKey);
|
||||
|
||||
// Release command lock(s)
|
||||
#define cmdLockReleaseP(...) \
|
||||
|
@ -87,6 +87,24 @@ lstComparatorStr(const void *const item1, const void *const item2)
|
||||
FUNCTION_TEST_RETURN(INT, strCmp(*(String **)item1, *(String **)item2));
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
FN_EXTERN int
|
||||
lstComparatorInt(const void *const intPtr1, const void *const intPtr2)
|
||||
{
|
||||
FUNCTION_TEST_BEGIN();
|
||||
FUNCTION_TEST_PARAM_P(VOID, intPtr1);
|
||||
FUNCTION_TEST_PARAM_P(VOID, intPtr2);
|
||||
FUNCTION_TEST_END();
|
||||
|
||||
ASSERT(intPtr1 != NULL);
|
||||
ASSERT(intPtr2 != NULL);
|
||||
|
||||
const int int1 = *(int *)intPtr1;
|
||||
const int int2 = *(int *)intPtr2;
|
||||
|
||||
FUNCTION_TEST_RETURN(INT, LST_COMPARATOR_CMP(int1, int2));
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
FN_EXTERN int
|
||||
lstComparatorUInt(const void *const uintPtr1, const void *const uintPtr2)
|
||||
|
@ -49,6 +49,9 @@ FN_EXTERN int lstComparatorStr(const void *item1, const void *item2);
|
||||
// General purpose list comparator for unsigned int or structs with an unsigned int as the first member
|
||||
FN_EXTERN int lstComparatorUInt(const void *item1, const void *item2);
|
||||
|
||||
// General purpose list comparator for int or structs with an int as the first member
|
||||
FN_EXTERN int lstComparatorInt(const void *item1, const void *item2);
|
||||
|
||||
// General purpose list comparator for zero-terminated strings or structs with a zero-terminated string as the first member
|
||||
FN_EXTERN int lstComparatorZ(const void *item1, const void *item2);
|
||||
|
||||
|
@ -85,6 +85,7 @@ Option constants
|
||||
#define CFGOPT_JOB_RETRY_INTERVAL "job-retry-interval"
|
||||
#define CFGOPT_LINK_ALL "link-all"
|
||||
#define CFGOPT_LINK_MAP "link-map"
|
||||
#define CFGOPT_LOCK "lock"
|
||||
#define CFGOPT_LOCK_PATH "lock-path"
|
||||
#define CFGOPT_LOG_LEVEL_CONSOLE "log-level-console"
|
||||
#define CFGOPT_LOG_LEVEL_FILE "log-level-file"
|
||||
@ -136,7 +137,7 @@ Option constants
|
||||
#define CFGOPT_TYPE "type"
|
||||
#define CFGOPT_VERBOSE "verbose"
|
||||
|
||||
#define CFG_OPTION_TOTAL 180
|
||||
#define CFG_OPTION_TOTAL 181
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Option value constants
|
||||
@ -425,6 +426,7 @@ typedef enum
|
||||
cfgOptJobRetryInterval,
|
||||
cfgOptLinkAll,
|
||||
cfgOptLinkMap,
|
||||
cfgOptLock,
|
||||
cfgOptLockPath,
|
||||
cfgOptLogLevelConsole,
|
||||
cfgOptLogLevelFile,
|
||||
|
@ -2455,6 +2455,25 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
|
||||
), // opt/link-map
|
||||
), // opt/link-map
|
||||
// -----------------------------------------------------------------------------------------------------------------------------
|
||||
PARSE_RULE_OPTION // opt/lock
|
||||
( // opt/lock
|
||||
PARSE_RULE_OPTION_NAME("lock"), // opt/lock
|
||||
PARSE_RULE_OPTION_TYPE(List), // opt/lock
|
||||
PARSE_RULE_OPTION_REQUIRED(false), // opt/lock
|
||||
PARSE_RULE_OPTION_SECTION(CommandLine), // opt/lock
|
||||
PARSE_RULE_OPTION_MULTI(true), // opt/lock
|
||||
// opt/lock
|
||||
PARSE_RULE_OPTION_COMMAND_ROLE_REMOTE_VALID_LIST // opt/lock
|
||||
( // opt/lock
|
||||
PARSE_RULE_OPTION_COMMAND(Annotate) // opt/lock
|
||||
PARSE_RULE_OPTION_COMMAND(ArchivePush) // opt/lock
|
||||
PARSE_RULE_OPTION_COMMAND(Backup) // opt/lock
|
||||
PARSE_RULE_OPTION_COMMAND(StanzaCreate) // opt/lock
|
||||
PARSE_RULE_OPTION_COMMAND(StanzaDelete) // opt/lock
|
||||
PARSE_RULE_OPTION_COMMAND(StanzaUpgrade) // opt/lock
|
||||
), // opt/lock
|
||||
), // opt/lock
|
||||
// -----------------------------------------------------------------------------------------------------------------------------
|
||||
PARSE_RULE_OPTION // opt/lock-path
|
||||
( // opt/lock-path
|
||||
PARSE_RULE_OPTION_NAME("lock-path"), // opt/lock-path
|
||||
@ -10846,6 +10865,7 @@ static const uint8_t optionResolveOrder[] =
|
||||
cfgOptJobRetryInterval, // opt-resolve-order
|
||||
cfgOptLinkAll, // opt-resolve-order
|
||||
cfgOptLinkMap, // opt-resolve-order
|
||||
cfgOptLock, // opt-resolve-order
|
||||
cfgOptLockPath, // opt-resolve-order
|
||||
cfgOptLogLevelConsole, // opt-resolve-order
|
||||
cfgOptLogLevelFile, // opt-resolve-order
|
||||
|
@ -382,8 +382,10 @@ dbBackupStart(Db *const this, const bool startFast, const bool stopAuto, const b
|
||||
MEM_CONTEXT_TEMP_BEGIN()
|
||||
{
|
||||
// Acquire the backup advisory lock to make sure that backups are not running from multiple backup servers against the same
|
||||
// database cluster. This lock helps make the stop-auto option safe.
|
||||
if (!pckReadBoolP(dbQueryColumn(this, STRDEF("select pg_catalog.pg_try_advisory_lock(" PG_BACKUP_ADVISORY_LOCK ")::bool"))))
|
||||
// database cluster when PostgreSQL <= 9.5. This lock helps make the stop-auto option safe. On PostgreSQL > 9.5 multiple
|
||||
// backups are allowed on the same cluster.
|
||||
if (dbPgVersion(this) <= PG_VERSION_95 &&
|
||||
!pckReadBoolP(dbQueryColumn(this, STRDEF("select pg_catalog.pg_try_advisory_lock(" PG_BACKUP_ADVISORY_LOCK ")::bool"))))
|
||||
{
|
||||
THROW(
|
||||
LockAcquireError,
|
||||
|
@ -5,6 +5,7 @@ Protocol Helper
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "command/lock.h"
|
||||
#include "common/crypto/common.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/exec.h"
|
||||
@ -599,6 +600,10 @@ protocolRemoteParam(ProtocolStorageType protocolStorageType, unsigned int hostId
|
||||
// Add the remote type
|
||||
kvPut(optionReplace, VARSTRDEF(CFGOPT_REMOTE_TYPE), VARSTR(strIdToStr(protocolStorageType)));
|
||||
|
||||
// Add lock required on the remote
|
||||
if (cfgLockRemoteRequired())
|
||||
kvPut(optionReplace, VARSTRDEF(CFGOPT_LOCK), varNewVarLst(varLstNewStrLst(cmdLockList())));
|
||||
|
||||
MEM_CONTEXT_PRIOR_BEGIN()
|
||||
{
|
||||
result = cfgExecParam(cfgCommand(), cfgCmdRoleRemote, optionReplace, false, true);
|
||||
|
@ -173,6 +173,7 @@ option:
|
||||
type: string, default: CFGOPTDEF_CONFIG_PATH "/" PROJECT_CONFIG_INCLUDE_PATH, default-literal: true, command: {noop: {}}}
|
||||
job-retry: {type: string, required: false, deprecate: {job-retry-old: {}}, command: {noop: {}}}
|
||||
job-retry-interval: {type: string, required: false, command: {noop: {}}}
|
||||
lock: {type: list, required: false, command: {noop: {}}}
|
||||
log-level-file: {type: string, required: false, command: {noop: {}}}
|
||||
log-level-stderr: {type: string, required: false, command: {noop: {}}}
|
||||
pg: {type: string, required: false, command: {noop: {}}}
|
||||
|
@ -15,6 +15,7 @@
|
||||
<option id="compress-level-network"><summary></summary><text><p></p></text></option>
|
||||
<option id="job-retry"><summary></summary><text><p></p></text></option>
|
||||
<option id="job-retry-interval"><summary></summary><text><p></p></text></option>
|
||||
<option id="lock"><summary></summary><text><p></p></text></option>
|
||||
<option id="log-level-file"><summary></summary><text><p></p></text></option>
|
||||
<option id="log-level-stderr"><summary></summary><text><p></p></text></option>
|
||||
<option id="repo"><summary></summary><text><p></p></text></option>
|
||||
|
@ -270,12 +270,12 @@ hrnBackupPqScript(const unsigned int pgVersion, const time_t backupTimeStart, Hr
|
||||
// Get start time
|
||||
HRN_PQ_SCRIPT_ADD(HRN_PQ_SCRIPT_TIME_QUERY(1, (int64_t)backupTimeStart * 1000));
|
||||
|
||||
// Advisory lock
|
||||
HRN_PQ_SCRIPT_ADD(HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true));
|
||||
|
||||
// Check if backup is in progress (only for exclusive backup)
|
||||
// Get advisory lock and check if backup is in progress (only for exclusive backup)
|
||||
if (pgVersion <= PG_VERSION_95)
|
||||
{
|
||||
HRN_PQ_SCRIPT_ADD(HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true));
|
||||
HRN_PQ_SCRIPT_ADD(HRN_PQ_SCRIPT_IS_IN_BACKUP(1, false));
|
||||
}
|
||||
|
||||
// Perform archive check
|
||||
if (!param.noArchiveCheck)
|
||||
|
@ -255,9 +255,11 @@ testRun(void)
|
||||
{
|
||||
HRN_FORK_CHILD_BEGIN()
|
||||
{
|
||||
String *lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), lockTypeArchive);
|
||||
lockInit(STRDEF(HRN_PATH "/lock"), cfgOptionStr(cfgOptExecId));
|
||||
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create archive lock");
|
||||
TEST_RESULT_BOOL(
|
||||
lockAcquireP(cmdLockFileName(cfgOptionStr(cfgOptStanza), lockTypeArchive, 1)), true, "create archive lock");
|
||||
TEST_RESULT_BOOL(
|
||||
lockAcquireP(cmdLockFileName(cfgOptionStr(cfgOptStanza), lockTypeArchive, 2)), true, "create archive lock");
|
||||
|
||||
// Notify parent that lock has been acquired
|
||||
HRN_FORK_CHILD_NOTIFY_PUT();
|
||||
|
@ -238,7 +238,7 @@ testRun(void)
|
||||
{
|
||||
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
|
||||
TEST_RESULT_BOOL(
|
||||
lockAcquireP(cmdLockFileName(STRDEF("stanza1"), lockTypeBackup)), true, "create backup/expire lock");
|
||||
lockAcquireP(cmdLockFileName(STRDEF("stanza1"), lockTypeBackup, 1)), true, "create backup/expire lock");
|
||||
|
||||
// Notify parent that lock has been acquired
|
||||
HRN_FORK_CHILD_NOTIFY_PUT();
|
||||
@ -429,7 +429,7 @@ testRun(void)
|
||||
{
|
||||
HRN_FORK_CHILD_BEGIN()
|
||||
{
|
||||
String *lockFileName = cmdLockFileName(STRDEF("stanza1"), lockTypeBackup);
|
||||
String *lockFileName = cmdLockFileName(STRDEF("stanza1"), lockTypeBackup, 1);
|
||||
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("777-afafafaf"));
|
||||
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
|
||||
TEST_RESULT_VOID(lockWriteP(lockFileName), "write lock data");
|
||||
@ -1039,7 +1039,7 @@ testRun(void)
|
||||
{
|
||||
HRN_FORK_CHILD_BEGIN()
|
||||
{
|
||||
String *lockFileName = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup);
|
||||
String *lockFileName = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup, 1);
|
||||
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
|
||||
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
|
||||
TEST_RESULT_VOID(
|
||||
@ -1483,10 +1483,31 @@ testRun(void)
|
||||
{
|
||||
HRN_FORK_CHILD_BEGIN()
|
||||
{
|
||||
String *lockFileName = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup);
|
||||
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
|
||||
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
|
||||
TEST_RESULT_VOID(lockWriteP(lockFileName, .percentComplete = VARUINT(5555)), "write lock data");
|
||||
|
||||
String *lockFileStanza1Repo1 = cmdLockFileName(STRDEF("stanza1"), lockTypeBackup, 1);
|
||||
TEST_RESULT_BOOL(lockAcquireP(lockFileStanza1Repo1), true, "create backup/expire lock");
|
||||
TEST_RESULT_VOID(
|
||||
lockWriteP(
|
||||
lockFileStanza1Repo1, .size = VARUINT64(3159000), .sizeComplete = VARUINT64(1754830),
|
||||
.percentComplete = VARUINT(5555)),
|
||||
"write lock data");
|
||||
|
||||
String *lockFileStanza1Repo2 = cmdLockFileName(STRDEF("stanza1"), lockTypeBackup, 2);
|
||||
TEST_RESULT_BOOL(lockAcquireP(lockFileStanza1Repo2), true, "create backup/expire lock");
|
||||
TEST_RESULT_VOID(
|
||||
lockWriteP(
|
||||
lockFileStanza1Repo2, .size = VARUINT64(3159000), .sizeComplete = VARUINT64(2369250),
|
||||
.percentComplete = VARUINT(7500)),
|
||||
"write lock data");
|
||||
|
||||
String *lockFileStanza2Repo1 = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup, 1);
|
||||
TEST_RESULT_BOOL(lockAcquireP(lockFileStanza2Repo1), true, "create backup/expire lock");
|
||||
TEST_RESULT_VOID(
|
||||
lockWriteP(
|
||||
lockFileStanza2Repo1, .size = VARUINT64(3159000), .sizeComplete = VARUINT64(1754830),
|
||||
.percentComplete = VARUINT(5555)),
|
||||
"write lock data");
|
||||
|
||||
// Notify parent that lock has been acquired
|
||||
HRN_FORK_CHILD_NOTIFY_PUT();
|
||||
@ -1507,7 +1528,7 @@ testRun(void)
|
||||
TEST_RESULT_STR_Z(
|
||||
infoRender(),
|
||||
"stanza: stanza1\n"
|
||||
" status: ok\n"
|
||||
" status: ok (backup/expire running - 65.27% complete)\n"
|
||||
" cipher: mixed\n"
|
||||
" repo1: none\n"
|
||||
" repo2: aes-256-cbc\n"
|
||||
|
@ -27,12 +27,15 @@ testRun(void)
|
||||
hrnCfgArgRawZ(argList, cfgOptStanza, "test");
|
||||
hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg1");
|
||||
hrnCfgArgRawZ(argList, cfgOptLockPath, TEST_PATH);
|
||||
hrnCfgArgRawZ(argList, cfgOptRepoRetentionFull, "1");
|
||||
hrnCfgArgKeyRawZ(argList, cfgOptRepoRetentionFull, 1, "1");
|
||||
hrnCfgArgKeyRawZ(argList, cfgOptRepoRetentionFull, 2, "1");
|
||||
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, "/repo2");
|
||||
hrnCfgArgRawZ(argList, cfgOptRepo, "1");
|
||||
HRN_CFG_LOAD(cfgCmdBackup, argList, .noStd = true);
|
||||
|
||||
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire backup lock");
|
||||
TEST_ERROR(cmdLockAcquireP(), AssertError, "lock is already held by this process");
|
||||
TEST_STORAGE_LIST(storageTest, NULL, "test-backup.lock\n", .comment = "check lock file");
|
||||
TEST_STORAGE_LIST(storageTest, NULL, "test-backup-1.lock\n", .comment = "check lock file");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
TEST_TITLE("write backup data");
|
||||
@ -45,7 +48,7 @@ testRun(void)
|
||||
TEST_TITLE("read backup data");
|
||||
|
||||
LockReadResult lockReadResult;
|
||||
TEST_ASSIGN(lockReadResult, cmdLockRead(lockTypeBackup, STRDEF("test")), "read backup data");
|
||||
TEST_ASSIGN(lockReadResult, cmdLockRead(lockTypeBackup, STRDEF("test"), 0), "read backup data");
|
||||
TEST_RESULT_UINT(lockReadResult.status, lockReadStatusValid, "check status");
|
||||
TEST_RESULT_UINT(varUInt(lockReadResult.data.percentComplete), 5555, "check percent complete");
|
||||
TEST_RESULT_UINT(varUInt64(lockReadResult.data.sizeComplete), 1754824, "check size complete");
|
||||
@ -67,10 +70,34 @@ testRun(void)
|
||||
hrnCfgArgRawZ(argList, cfgOptStanza, "test");
|
||||
hrnCfgArgRawZ(argList, cfgOptLockPath, TEST_PATH);
|
||||
hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg1");
|
||||
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, "/repo1");
|
||||
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, "/repo2");
|
||||
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList, .noStd = true);
|
||||
|
||||
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire stanza-create lock");
|
||||
TEST_STORAGE_LIST(storageTest, NULL, "test-archive.lock\ntest-backup.lock\n", .comment = "check lock file");
|
||||
TEST_STORAGE_LIST(
|
||||
storageTest, NULL, "test-archive-1.lock\ntest-archive-2.lock\ntest-backup-1.lock\ntest-backup-2.lock\n",
|
||||
.comment = "check lock files");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
TEST_TITLE("lock release");
|
||||
|
||||
TEST_RESULT_VOID(cmdLockReleaseP(), "release locks");
|
||||
TEST_STORAGE_LIST(storageTest, NULL, NULL, .comment = "check lock file does not exist");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
TEST_TITLE("acquire backup:remote command lock");
|
||||
|
||||
argList = strLstNew();
|
||||
hrnCfgArgRawZ(argList, cfgOptStanza, "test");
|
||||
hrnCfgArgRawZ(argList, cfgOptLockPath, TEST_PATH);
|
||||
hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg1");
|
||||
hrnCfgArgRawZ(argList, cfgOptLock, "test-lock-99.lock");
|
||||
hrnCfgArgRawZ(argList, cfgOptLock, "other-lock.lock");
|
||||
HRN_CFG_LOAD(cfgCmdBackup, argList, .role = cfgCmdRoleRemote, .noStd = true);
|
||||
|
||||
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire backup:remote lock");
|
||||
TEST_STORAGE_LIST(storageTest, NULL, "other-lock.lock\ntest-lock-99.lock\n", .comment = "check lock files");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
TEST_TITLE("lock release");
|
||||
|
@ -169,7 +169,7 @@ testRun(void)
|
||||
"create client");
|
||||
protocolClientNoOp(client);
|
||||
|
||||
TEST_STORAGE_EXISTS(hrnStorage, "lock/test-archive" LOCK_FILE_EXT, .comment = "lock exists");
|
||||
TEST_STORAGE_EXISTS(hrnStorage, "lock/test-archive-1" LOCK_FILE_EXT, .comment = "lock exists");
|
||||
|
||||
protocolClientFree(client);
|
||||
}
|
||||
|
@ -190,6 +190,16 @@ testRun(void)
|
||||
TEST_RESULT_BOOL(lstComparatorUInt(&uint1, &uint2) < 0, true, "first uint is less");
|
||||
TEST_RESULT_BOOL(lstComparatorUInt(&uint2, &uint1) > 0, true, "first uint is greater");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
TEST_TITLE("lstComparatorInt()");
|
||||
|
||||
int int1 = -1;
|
||||
int int2 = 2;
|
||||
|
||||
TEST_RESULT_INT(lstComparatorInt(&int1, &int1), 0, "ints are equal");
|
||||
TEST_RESULT_BOOL(lstComparatorInt(&int1, &int2) < 0, true, "first int is less");
|
||||
TEST_RESULT_BOOL(lstComparatorInt(&int2, &int1) > 0, true, "first int is greater");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
TEST_TITLE("lstComparatorZ()");
|
||||
|
||||
|
@ -365,7 +365,6 @@ testRun(void)
|
||||
|
||||
// Start backup with timeline error
|
||||
HRN_PQ_SCRIPT_SET(
|
||||
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
|
||||
HRN_PQ_SCRIPT_CURRENT_WAL_LE_96(1, "000000020000000300000002"),
|
||||
HRN_PQ_SCRIPT_START_BACKUP_96(1, false, "3/3", "000000020000000300000003"));
|
||||
|
||||
@ -374,7 +373,6 @@ testRun(void)
|
||||
|
||||
// Start backup with checkpoint error
|
||||
HRN_PQ_SCRIPT_SET(
|
||||
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
|
||||
HRN_PQ_SCRIPT_CURRENT_WAL_LE_96(1, "000000010000000400000003"),
|
||||
HRN_PQ_SCRIPT_START_BACKUP_96(1, false, "4/4", "000000010000000400000004"));
|
||||
|
||||
@ -384,7 +382,6 @@ testRun(void)
|
||||
|
||||
// Start backup
|
||||
HRN_PQ_SCRIPT_SET(
|
||||
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
|
||||
HRN_PQ_SCRIPT_CURRENT_WAL_LE_96(1, "000000010000000300000002"),
|
||||
HRN_PQ_SCRIPT_START_BACKUP_96(1, false, "3/3", "000000010000000300000003"));
|
||||
|
||||
@ -494,7 +491,6 @@ testRun(void)
|
||||
|
||||
// Start backup
|
||||
HRN_PQ_SCRIPT_SET(
|
||||
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
|
||||
HRN_PQ_SCRIPT_CURRENT_WAL_GE_10(1, "000000050000000500000005"),
|
||||
HRN_PQ_SCRIPT_START_BACKUP_GE_10(1, false, "5/5", "000000050000000500000005"),
|
||||
|
||||
@ -618,7 +614,6 @@ testRun(void)
|
||||
|
||||
// Start backup
|
||||
HRN_PQ_SCRIPT_SET(
|
||||
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
|
||||
HRN_PQ_SCRIPT_CURRENT_WAL_GE_10(1, "000000050000000500000004"),
|
||||
HRN_PQ_SCRIPT_START_BACKUP_GE_10(1, false, "5/5", "000000050000000500000005"));
|
||||
|
||||
@ -651,7 +646,6 @@ testRun(void)
|
||||
|
||||
// Start backup
|
||||
HRN_PQ_SCRIPT_SET(
|
||||
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
|
||||
HRN_PQ_SCRIPT_CURRENT_WAL_GE_10(1, "000000060000000600000005"),
|
||||
HRN_PQ_SCRIPT_START_BACKUP_GE_15(1, false, "6/6", "000000060000000600000006"));
|
||||
|
||||
|
@ -430,8 +430,8 @@ testRun(void)
|
||||
TEST_RESULT_STRLST_Z(
|
||||
protocolRemoteParamSsh(protocolStorageTypePg, 0),
|
||||
"-o\nLogLevel=error\n-o\nCompression=no\n-o\nPasswordAuthentication=no\npostgres@pg1-host\n"
|
||||
TEST_PROJECT_EXE " --exec-id=1-test --log-level-console=off --log-level-file=off --log-level-stderr=error"
|
||||
" --pg1-path=/path/to/1 --process=0 --remote-type=pg --stanza=test1 backup:remote\n",
|
||||
TEST_PROJECT_EXE " --exec-id=1-test --lock=test1-backup-1.lock --log-level-console=off --log-level-file=off"
|
||||
" --log-level-stderr=error --pg1-path=/path/to/1 --process=0 --remote-type=pg --stanza=test1 backup:remote\n",
|
||||
"check config");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
@ -452,8 +452,8 @@ testRun(void)
|
||||
TEST_RESULT_STRLST_Z(
|
||||
protocolRemoteParamSsh(protocolStorageTypePg, 1),
|
||||
"-o\nLogLevel=error\n-o\nCompression=no\n-o\nPasswordAuthentication=no\npostgres@pg2-host\n"
|
||||
TEST_PROJECT_EXE " --exec-id=1-test --log-level-console=off --log-level-file=off --log-level-stderr=error"
|
||||
" --pg1-path=/path/to/2 --process=4 --remote-type=pg --stanza=test1 backup:remote\n",
|
||||
TEST_PROJECT_EXE " --exec-id=1-test --lock=test1-backup-1.lock --log-level-console=off --log-level-file=off"
|
||||
" --log-level-stderr=error --pg1-path=/path/to/2 --process=4 --remote-type=pg --stanza=test1 backup:remote\n",
|
||||
"check config");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
@ -474,9 +474,9 @@ testRun(void)
|
||||
TEST_RESULT_STRLST_Z(
|
||||
protocolRemoteParamSsh(protocolStorageTypePg, 1),
|
||||
"-o\nLogLevel=error\n-o\nCompression=no\n-o\nPasswordAuthentication=no\npostgres@pg3-host\n"
|
||||
TEST_PROJECT_EXE " --exec-id=1-test --log-level-console=off --log-level-file=off --log-level-stderr=error"
|
||||
" --pg1-path=/path/to/3 --pg1-port=3333 --pg1-socket-path=/socket3 --process=4 --remote-type=pg --stanza=test1"
|
||||
" backup:remote\n",
|
||||
TEST_PROJECT_EXE " --exec-id=1-test --lock=test1-backup-1.lock --log-level-console=off --log-level-file=off"
|
||||
" --log-level-stderr=error --pg1-path=/path/to/3 --pg1-port=3333 --pg1-socket-path=/socket3 --process=4"
|
||||
" --remote-type=pg --stanza=test1 backup:remote\n",
|
||||
"check config");
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user