2018-01-17 15:52:00 -05:00
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Archive Push Command
|
|
|
|
***********************************************************************************************************************************/
|
2019-04-26 08:08:23 -04:00
|
|
|
#include "build.auto.h"
|
|
|
|
|
2019-04-16 13:39:58 -04:00
|
|
|
#include <string.h>
|
2018-01-17 15:52:00 -05:00
|
|
|
#include <unistd.h>
|
|
|
|
|
2018-04-29 10:16:59 -04:00
|
|
|
#include "command/archive/common.h"
|
2019-03-29 13:26:33 +00:00
|
|
|
#include "command/archive/push/file.h"
|
|
|
|
#include "command/archive/push/protocol.h"
|
2018-04-12 20:42:26 -04:00
|
|
|
#include "command/command.h"
|
2019-07-31 11:35:58 -04:00
|
|
|
#include "command/control/common.h"
|
2018-05-18 11:57:32 -04:00
|
|
|
#include "common/debug.h"
|
2018-04-12 20:42:26 -04:00
|
|
|
#include "common/fork.h"
|
2018-01-17 15:52:00 -05:00
|
|
|
#include "common/log.h"
|
|
|
|
#include "common/memContext.h"
|
|
|
|
#include "common/wait.h"
|
|
|
|
#include "config/config.h"
|
2019-03-14 13:38:55 +04:00
|
|
|
#include "config/exec.h"
|
2019-03-29 13:26:33 +00:00
|
|
|
#include "info/infoArchive.h"
|
|
|
|
#include "postgres/interface.h"
|
|
|
|
#include "protocol/helper.h"
|
|
|
|
#include "protocol/parallel.h"
|
2018-01-17 15:52:00 -05:00
|
|
|
#include "storage/helper.h"
|
|
|
|
|
2019-03-29 13:26:33 +00:00
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Ready file extension constants
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
#define STATUS_EXT_READY ".ready"
|
|
|
|
#define STATUS_EXT_READY_SIZE (sizeof(STATUS_EXT_READY) - 1)
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Format the warning when a file is dropped
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
static String *
|
2019-04-19 11:40:39 -04:00
|
|
|
archivePushDropWarning(const String *walFile, uint64_t queueMax)
|
2019-03-29 13:26:33 +00:00
|
|
|
{
|
|
|
|
FUNCTION_TEST_BEGIN();
|
|
|
|
FUNCTION_TEST_PARAM(STRING, walFile);
|
2019-04-19 11:40:39 -04:00
|
|
|
FUNCTION_TEST_PARAM(UINT64, queueMax);
|
2019-03-29 13:26:33 +00:00
|
|
|
FUNCTION_TEST_END();
|
|
|
|
|
|
|
|
FUNCTION_TEST_RETURN(
|
2019-04-19 11:40:39 -04:00
|
|
|
strNewFmt("dropped WAL file '%s' because archive queue exceeded %s", strPtr(walFile), strPtr(strSizeFormat(queueMax))));
|
2019-03-29 13:26:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Determine if the WAL process list has become large enough to drop
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
static bool
|
|
|
|
archivePushDrop(const String *walPath, const StringList *const processList)
|
|
|
|
{
|
|
|
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
|
|
|
FUNCTION_LOG_PARAM(STRING, walPath);
|
|
|
|
FUNCTION_LOG_PARAM(STRING_LIST, processList);
|
|
|
|
FUNCTION_LOG_END();
|
|
|
|
|
2019-04-19 11:40:39 -04:00
|
|
|
const uint64_t queueMax = cfgOptionUInt64(cfgOptArchivePushQueueMax);
|
2019-03-29 13:26:33 +00:00
|
|
|
uint64_t queueSize = 0;
|
|
|
|
bool result = false;
|
|
|
|
|
|
|
|
for (unsigned int processIdx = 0; processIdx < strLstSize(processList); processIdx++)
|
|
|
|
{
|
2019-11-17 15:10:40 -05:00
|
|
|
queueSize += storageInfoP(
|
2019-03-29 13:26:33 +00:00
|
|
|
storagePg(), strNewFmt("%s/%s", strPtr(walPath), strPtr(strLstGet(processList, processIdx)))).size;
|
|
|
|
|
|
|
|
if (queueSize > queueMax)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FUNCTION_LOG_RETURN(BOOL, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Get the list of WAL files ready to be pushed according to PostgreSQL
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
static StringList *
|
|
|
|
archivePushReadyList(const String *walPath)
|
|
|
|
{
|
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(STRING, walPath);
|
|
|
|
FUNCTION_LOG_END();
|
|
|
|
|
|
|
|
ASSERT(walPath != NULL);
|
|
|
|
|
|
|
|
StringList *result = NULL;
|
|
|
|
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
result = strLstNew();
|
|
|
|
|
|
|
|
// Read the ready files from the archive_status directory
|
|
|
|
StringList *readyListRaw = strLstSort(
|
|
|
|
storageListP(
|
|
|
|
storagePg(), strNewFmt("%s/" PG_PATH_ARCHIVE_STATUS, strPtr(walPath)),
|
2019-04-16 13:39:58 -04:00
|
|
|
.expression = STRDEF("\\" STATUS_EXT_READY "$"), .errorOnMissing = true),
|
2019-03-29 13:26:33 +00:00
|
|
|
sortOrderAsc);
|
|
|
|
|
|
|
|
for (unsigned int readyIdx = 0; readyIdx < strLstSize(readyListRaw); readyIdx++)
|
|
|
|
{
|
|
|
|
strLstAdd(
|
|
|
|
result,
|
|
|
|
strSubN(strLstGet(readyListRaw, readyIdx), 0, strSize(strLstGet(readyListRaw, readyIdx)) - STATUS_EXT_READY_SIZE));
|
|
|
|
}
|
|
|
|
|
2020-01-17 13:29:49 -07:00
|
|
|
strLstMove(result, memContextPrior());
|
2019-03-29 13:26:33 +00:00
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
|
|
|
|
FUNCTION_LOG_RETURN(STRING_LIST, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Determine which WAL files need to be pushed to the archive when in async mode
|
|
|
|
|
|
|
|
This is the heart of the "look ahead" functionality in async archiving. Any files in the out directory that do not end in ok are
|
|
|
|
removed and any ok files that do not have a corresponding ready file in archive_status (meaning it has been acknowledged by
|
|
|
|
PostgreSQL) are removed. Then all ready files that do not have a corresponding ok file (meaning it has already been processed) are
|
|
|
|
returned for processing.
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
static StringList *
|
|
|
|
archivePushProcessList(const String *walPath)
|
|
|
|
{
|
|
|
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
|
|
FUNCTION_LOG_PARAM(STRING, walPath);
|
|
|
|
FUNCTION_LOG_END();
|
|
|
|
|
2019-04-03 17:48:45 +01:00
|
|
|
ASSERT(walPath != NULL);
|
|
|
|
|
2019-03-29 13:26:33 +00:00
|
|
|
StringList *result = NULL;
|
|
|
|
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
// Create the spool out path if it does not already exist
|
2019-11-17 15:10:40 -05:00
|
|
|
storagePathCreateP(storageSpoolWrite(), STORAGE_SPOOL_ARCHIVE_OUT_STR);
|
2019-03-29 13:26:33 +00:00
|
|
|
|
|
|
|
// Read the status files from the spool directory, then remove any files that do not end in ok and create a list of the
|
|
|
|
// ok files for further processing
|
|
|
|
StringList *statusList = strLstSort(
|
2019-04-16 13:39:58 -04:00
|
|
|
storageListP(storageSpool(), STORAGE_SPOOL_ARCHIVE_OUT_STR, .errorOnMissing = true), sortOrderAsc);
|
2019-03-29 13:26:33 +00:00
|
|
|
|
|
|
|
StringList *okList = strLstNew();
|
|
|
|
|
|
|
|
for (unsigned int statusIdx = 0; statusIdx < strLstSize(statusList); statusIdx++)
|
|
|
|
{
|
|
|
|
const String *statusFile = strLstGet(statusList, statusIdx);
|
|
|
|
|
|
|
|
if (strEndsWithZ(statusFile, STATUS_EXT_OK))
|
|
|
|
strLstAdd(okList, strSubN(statusFile, 0, strSize(statusFile) - STATUS_EXT_OK_SIZE));
|
|
|
|
else
|
|
|
|
{
|
|
|
|
storageRemoveP(
|
|
|
|
storageSpoolWrite(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s", strPtr(statusFile)), .errorOnMissing = true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the ready files from the archive_status directory
|
|
|
|
StringList *readyList = archivePushReadyList(walPath);
|
|
|
|
|
|
|
|
// Remove ok files that are not in the ready list
|
|
|
|
StringList *okRemoveList = strLstMergeAnti(okList, readyList);
|
|
|
|
|
|
|
|
for (unsigned int okRemoveIdx = 0; okRemoveIdx < strLstSize(okRemoveList); okRemoveIdx++)
|
|
|
|
{
|
|
|
|
storageRemoveP(
|
|
|
|
storageSpoolWrite(),
|
|
|
|
strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s" STATUS_EXT_OK, strPtr(strLstGet(okRemoveList, okRemoveIdx))),
|
|
|
|
.errorOnMissing = true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return all ready files that are not in the ok list
|
2020-01-17 13:29:49 -07:00
|
|
|
result = strLstMove(strLstMergeAnti(readyList, okList), memContextPrior());
|
2019-03-29 13:26:33 +00:00
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
|
|
|
|
FUNCTION_LOG_RETURN(STRING_LIST, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Check that pg_control and archive.info match and get the archive id and archive cipher passphrase (if present)
|
|
|
|
|
|
|
|
As much information as possible is collected here so that async archiving has as little work as possible to do for each file. Sync
|
|
|
|
archiving does not benefit but it makes sense to use the same function.
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
#define FUNCTION_LOG_ARCHIVE_PUSH_CHECK_RESULT_TYPE \
|
|
|
|
ArchivePushCheckResult
|
|
|
|
#define FUNCTION_LOG_ARCHIVE_PUSH_CHECK_RESULT_FORMAT(value, buffer, bufferSize) \
|
|
|
|
objToLog(&value, "ArchivePushCheckResult", buffer, bufferSize)
|
|
|
|
|
|
|
|
typedef struct ArchivePushCheckResult
|
|
|
|
{
|
|
|
|
unsigned int pgVersion; // PostgreSQL version
|
|
|
|
uint64_t pgSystemId; // PostgreSQL system id
|
|
|
|
unsigned int pgWalSegmentSize; // PostgreSQL WAL segment size
|
|
|
|
String *archiveId; // Archive id for current pg version
|
|
|
|
String *archiveCipherPass; // Archive cipher passphrase
|
|
|
|
} ArchivePushCheckResult;
|
|
|
|
|
|
|
|
static ArchivePushCheckResult
|
|
|
|
archivePushCheck(CipherType cipherType, const String *cipherPass)
|
|
|
|
{
|
|
|
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
|
|
|
FUNCTION_LOG_PARAM(ENUM, cipherType);
|
|
|
|
FUNCTION_TEST_PARAM(STRING, cipherPass);
|
|
|
|
FUNCTION_LOG_END();
|
|
|
|
|
|
|
|
ArchivePushCheckResult result = {0};
|
|
|
|
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
// Get info from pg_control
|
2019-10-03 11:14:22 -04:00
|
|
|
PgControl controlInfo = pgControlFromFile(storagePg());
|
2019-03-29 13:26:33 +00:00
|
|
|
|
|
|
|
// Attempt to load the archive info file
|
2019-09-06 13:48:28 -04:00
|
|
|
InfoArchive *info = infoArchiveLoadFile(storageRepo(), INFO_ARCHIVE_PATH_FILE_STR, cipherType, cipherPass);
|
2019-03-29 13:26:33 +00:00
|
|
|
|
|
|
|
// Get archive id for the most recent version -- archive-push will only operate against the most recent version
|
|
|
|
String *archiveId = infoPgArchiveId(infoArchivePg(info), infoPgDataCurrentId(infoArchivePg(info)));
|
|
|
|
InfoPgData archiveInfo = infoPgData(infoArchivePg(info), infoPgDataCurrentId(infoArchivePg(info)));
|
|
|
|
|
|
|
|
// Ensure that the version and system identifier match
|
|
|
|
if (controlInfo.version != archiveInfo.version || controlInfo.systemId != archiveInfo.systemId)
|
|
|
|
{
|
|
|
|
THROW_FMT(
|
|
|
|
ArchiveMismatchError,
|
|
|
|
"PostgreSQL version %s, system-id %" PRIu64 " do not match stanza version %s, system-id %" PRIu64
|
|
|
|
"\nHINT: are you archiving to the correct stanza?",
|
|
|
|
strPtr(pgVersionToStr(controlInfo.version)), controlInfo.systemId, strPtr(pgVersionToStr(archiveInfo.version)),
|
|
|
|
archiveInfo.systemId);
|
|
|
|
}
|
|
|
|
|
2020-01-17 13:29:49 -07:00
|
|
|
MEM_CONTEXT_PRIOR_BEGIN()
|
|
|
|
{
|
|
|
|
result.pgVersion = controlInfo.version;
|
|
|
|
result.pgSystemId = controlInfo.systemId;
|
|
|
|
result.pgWalSegmentSize = controlInfo.walSegmentSize;
|
|
|
|
result.archiveId = strDup(archiveId);
|
|
|
|
result.archiveCipherPass = strDup(infoArchiveCipherPass(info));
|
|
|
|
}
|
|
|
|
MEM_CONTEXT_PRIOR_END();
|
2019-03-29 13:26:33 +00:00
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
|
|
|
|
FUNCTION_LOG_RETURN(ARCHIVE_PUSH_CHECK_RESULT, result);
|
|
|
|
}
|
|
|
|
|
2018-01-17 15:52:00 -05:00
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Push a WAL segment to the repository
|
|
|
|
***********************************************************************************************************************************/
|
|
|
|
void
|
2018-08-03 19:19:14 -04:00
|
|
|
cmdArchivePush(void)
|
2018-01-17 15:52:00 -05:00
|
|
|
{
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_VOID(logLevelDebug);
|
2018-05-18 11:57:32 -04:00
|
|
|
|
2019-03-29 13:26:33 +00:00
|
|
|
ASSERT(cfgCommand() == cfgCmdArchivePush);
|
|
|
|
|
2018-01-17 15:52:00 -05:00
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
// Make sure there is a parameter to retrieve the WAL segment from
|
|
|
|
const StringList *commandParam = cfgCommandParam();
|
|
|
|
|
|
|
|
if (strLstSize(commandParam) != 1)
|
|
|
|
THROW(ParamRequiredError, "WAL segment to push required");
|
|
|
|
|
2019-03-29 13:26:33 +00:00
|
|
|
// Test for stop file
|
2019-07-10 12:04:25 -04:00
|
|
|
lockStopTest();
|
2019-03-29 13:26:33 +00:00
|
|
|
|
2018-01-17 15:52:00 -05:00
|
|
|
// Get the segment name
|
2019-12-11 14:36:39 -05:00
|
|
|
String *walFile = walPath(strLstGet(commandParam, 0), cfgOptionStr(cfgOptPgPath), STR(cfgCommandName(cfgCommand())));
|
2019-03-29 13:26:33 +00:00
|
|
|
String *archiveFile = strBase(walFile);
|
2018-01-17 15:52:00 -05:00
|
|
|
|
|
|
|
if (cfgOptionBool(cfgOptArchiveAsync))
|
|
|
|
{
|
|
|
|
bool pushed = false; // Has the WAL segment been pushed yet?
|
2018-04-12 20:42:26 -04:00
|
|
|
bool forked = false; // Has the async process been forked yet?
|
2018-01-17 15:52:00 -05:00
|
|
|
bool confessOnError = false; // Should we confess errors?
|
|
|
|
|
|
|
|
// Loop and wait for the WAL segment to be pushed
|
2018-11-08 08:37:57 -05:00
|
|
|
Wait *wait = waitNew((TimeMSec)(cfgOptionDbl(cfgOptArchiveTimeout) * MSEC_PER_SEC));
|
2018-01-17 15:52:00 -05:00
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
// Check if the WAL segment has been pushed. Errors will not be confessed on the first try to allow the async
|
|
|
|
// process a chance to fix them.
|
2019-03-29 13:26:33 +00:00
|
|
|
pushed = archiveAsyncStatus(archiveModePush, archiveFile, confessOnError);
|
2018-01-17 15:52:00 -05:00
|
|
|
|
2018-04-12 20:42:26 -04:00
|
|
|
// If the WAL segment has not already been pushed then start the async process to push it. There's no point in
|
|
|
|
// forking the async process off more than once so track that as well. Use an archive lock to prevent more than
|
|
|
|
// one async process being launched.
|
|
|
|
if (!pushed && !forked &&
|
|
|
|
lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 0, false))
|
2018-01-17 15:52:00 -05:00
|
|
|
{
|
2019-03-14 13:38:55 +04:00
|
|
|
// The async process should not output on the console at all
|
|
|
|
KeyValue *optionReplace = kvNew();
|
2018-04-12 20:42:26 -04:00
|
|
|
|
2019-04-17 08:04:22 -04:00
|
|
|
kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_CONSOLE_STR), VARSTRDEF("off"));
|
|
|
|
kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_STDERR_STR), VARSTRDEF("off"));
|
2018-04-12 20:42:26 -04:00
|
|
|
|
2019-03-14 13:38:55 +04:00
|
|
|
// Generate command options
|
2020-01-15 12:24:58 -07:00
|
|
|
StringList *commandExec = cfgExecParam(cfgCmdArchivePush, cfgCmdRoleAsync, optionReplace, true, false);
|
2019-03-14 13:38:55 +04:00
|
|
|
strLstInsert(commandExec, 0, cfgExe());
|
2019-03-29 13:26:33 +00:00
|
|
|
strLstAdd(commandExec, strPath(walFile));
|
2018-04-12 20:42:26 -04:00
|
|
|
|
2019-04-02 13:50:02 +01:00
|
|
|
// Release the lock so the child process can acquire it
|
2019-03-14 13:38:55 +04:00
|
|
|
lockRelease(true);
|
2018-04-12 20:42:26 -04:00
|
|
|
|
2019-03-14 13:38:55 +04:00
|
|
|
// Fork off the async process
|
2019-04-02 13:50:02 +01:00
|
|
|
if (forkSafe() == 0)
|
2019-03-14 13:38:55 +04:00
|
|
|
{
|
2019-04-07 21:09:29 -04:00
|
|
|
// Disable logging and close log file
|
|
|
|
logClose();
|
|
|
|
|
2018-04-12 20:42:26 -04:00
|
|
|
// Detach from parent process
|
|
|
|
forkDetach();
|
|
|
|
|
2019-03-14 13:38:55 +04:00
|
|
|
// Execute the binary. This statement will not return if it is successful.
|
2019-04-12 09:03:34 -04:00
|
|
|
THROW_ON_SYS_ERROR(
|
2020-01-15 12:24:58 -07:00
|
|
|
execvp(strPtr(cfgExe()), (char ** const)strLstPtr(commandExec)) == -1, ExecuteError,
|
|
|
|
"unable to execute asynchronous '" CFGCMD_ARCHIVE_PUSH "'");
|
2018-01-17 15:52:00 -05:00
|
|
|
}
|
2019-04-02 13:50:02 +01:00
|
|
|
|
|
|
|
// Mark the async process as forked so it doesn't get forked again. A single run of the async process should be
|
|
|
|
// enough to do the job, running it again won't help anything.
|
|
|
|
forked = true;
|
2018-01-17 15:52:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now that the async process has been launched, confess any errors that are found
|
|
|
|
confessOnError = true;
|
|
|
|
}
|
2019-03-14 13:38:55 +04:00
|
|
|
while (!pushed && waitMore(wait));
|
2018-01-17 15:52:00 -05:00
|
|
|
|
2019-03-14 13:38:55 +04:00
|
|
|
// If the WAL segment was not pushed then error
|
|
|
|
if (!pushed)
|
2018-01-17 15:52:00 -05:00
|
|
|
{
|
2019-03-14 13:38:55 +04:00
|
|
|
THROW_FMT(
|
2019-03-29 13:26:33 +00:00
|
|
|
ArchiveTimeoutError, "unable to push WAL file '%s' to the archive asynchronously after %lg second(s)",
|
|
|
|
strPtr(archiveFile), cfgOptionDbl(cfgOptArchiveTimeout));
|
2018-04-12 20:42:26 -04:00
|
|
|
}
|
2019-03-14 13:38:55 +04:00
|
|
|
|
|
|
|
// Log success
|
2019-11-22 13:33:26 -05:00
|
|
|
LOG_INFO_FMT("pushed WAL file '%s' to the archive asynchronously", strPtr(archiveFile));
|
2018-01-17 15:52:00 -05:00
|
|
|
}
|
|
|
|
else
|
2019-03-29 13:26:33 +00:00
|
|
|
{
|
|
|
|
// Get the repo storage in case it is remote and encryption settings need to be pulled down
|
|
|
|
storageRepo();
|
|
|
|
|
|
|
|
// Get archive info
|
|
|
|
ArchivePushCheckResult archiveInfo = archivePushCheck(
|
|
|
|
cipherType(cfgOptionStr(cfgOptRepoCipherType)), cfgOptionStr(cfgOptRepoCipherPass));
|
|
|
|
|
|
|
|
// Check if the push queue has been exceeded
|
|
|
|
if (cfgOptionTest(cfgOptArchivePushQueueMax) &&
|
|
|
|
archivePushDrop(strPath(walFile), archivePushReadyList(strPath(walFile))))
|
|
|
|
{
|
2019-04-19 11:40:39 -04:00
|
|
|
LOG_WARN(strPtr(archivePushDropWarning(archiveFile, cfgOptionUInt64(cfgOptArchivePushQueueMax))));
|
2019-03-29 13:26:33 +00:00
|
|
|
}
|
|
|
|
// Else push the file
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Push the file to the archive
|
|
|
|
String *warning = archivePushFile(
|
|
|
|
walFile, archiveInfo.archiveId, archiveInfo.pgVersion, archiveInfo.pgSystemId, archiveFile,
|
|
|
|
cipherType(cfgOptionStr(cfgOptRepoCipherType)), archiveInfo.archiveCipherPass,
|
|
|
|
cfgOptionBool(cfgOptCompress), cfgOptionInt(cfgOptCompressLevel));
|
|
|
|
|
|
|
|
// If a warning was returned then log it
|
|
|
|
if (warning != NULL)
|
|
|
|
LOG_WARN(strPtr(warning));
|
|
|
|
|
|
|
|
// Log success
|
2019-11-22 13:33:26 -05:00
|
|
|
LOG_INFO_FMT("pushed WAL file '%s' to the archive", strPtr(archiveFile));
|
2019-03-29 13:26:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
|
|
|
|
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************************************************
|
|
|
|
Async version of archive push that runs in parallel for performance
|
|
|
|
***********************************************************************************************************************************/
|
2019-09-18 07:15:16 -04:00
|
|
|
typedef struct ArchivePushAsyncData
|
|
|
|
{
|
|
|
|
const String *walPath; // Path to pg_wal/pg_xlog
|
|
|
|
const StringList *walFileList; // List of wal files to process
|
|
|
|
unsigned int walFileIdx; // Current index in the list to be processed
|
|
|
|
CipherType cipherType; // Cipher type
|
|
|
|
bool compress; // Compress wal files
|
|
|
|
int compressLevel; // Compression level for wal files
|
|
|
|
ArchivePushCheckResult archiveInfo; // Archive info
|
|
|
|
} ArchivePushAsyncData;
|
|
|
|
|
|
|
|
static ProtocolParallelJob *archivePushAsyncCallback(void *data, unsigned int clientIdx)
|
|
|
|
{
|
|
|
|
FUNCTION_TEST_BEGIN();
|
|
|
|
FUNCTION_TEST_PARAM_P(VOID, data);
|
|
|
|
FUNCTION_TEST_PARAM(UINT, clientIdx);
|
|
|
|
FUNCTION_TEST_END();
|
|
|
|
|
|
|
|
// No special logic based on the client, we'll just get the next job
|
|
|
|
(void)clientIdx;
|
|
|
|
|
|
|
|
// Get a new job if there are any left
|
|
|
|
ArchivePushAsyncData *jobData = data;
|
|
|
|
|
|
|
|
if (jobData->walFileIdx < strLstSize(jobData->walFileList))
|
|
|
|
{
|
|
|
|
const String *walFile = strLstGet(jobData->walFileList, jobData->walFileIdx);
|
|
|
|
jobData->walFileIdx++;
|
|
|
|
|
|
|
|
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_ARCHIVE_PUSH_STR);
|
|
|
|
protocolCommandParamAdd(command, VARSTR(strNewFmt("%s/%s", strPtr(jobData->walPath), strPtr(walFile))));
|
|
|
|
protocolCommandParamAdd(command, VARSTR(jobData->archiveInfo.archiveId));
|
|
|
|
protocolCommandParamAdd(command, VARUINT(jobData->archiveInfo.pgVersion));
|
|
|
|
protocolCommandParamAdd(command, VARUINT64(jobData->archiveInfo.pgSystemId));
|
|
|
|
protocolCommandParamAdd(command, VARSTR(walFile));
|
|
|
|
protocolCommandParamAdd(command, VARUINT(jobData->cipherType));
|
|
|
|
protocolCommandParamAdd(command, VARSTR(jobData->archiveInfo.archiveCipherPass));
|
|
|
|
protocolCommandParamAdd(command, VARBOOL(jobData->compress));
|
|
|
|
protocolCommandParamAdd(command, VARINT(jobData->compressLevel));
|
|
|
|
|
|
|
|
FUNCTION_TEST_RETURN(protocolParallelJobNew(VARSTR(walFile), command));
|
|
|
|
}
|
|
|
|
|
|
|
|
FUNCTION_TEST_RETURN(NULL);
|
|
|
|
}
|
|
|
|
|
2019-03-29 13:26:33 +00:00
|
|
|
void
|
|
|
|
cmdArchivePushAsync(void)
|
|
|
|
{
|
|
|
|
FUNCTION_LOG_VOID(logLevelDebug);
|
|
|
|
|
2020-01-15 12:24:58 -07:00
|
|
|
ASSERT(cfgCommand() == cfgCmdArchivePush && cfgCommandRole() == cfgCmdRoleAsync);
|
2019-03-29 13:26:33 +00:00
|
|
|
|
|
|
|
MEM_CONTEXT_TEMP_BEGIN()
|
|
|
|
{
|
|
|
|
// Make sure there is a parameter with the wal path
|
|
|
|
const StringList *commandParam = cfgCommandParam();
|
|
|
|
|
|
|
|
if (strLstSize(commandParam) != 1)
|
|
|
|
THROW(ParamRequiredError, "WAL path to push required");
|
|
|
|
|
2019-09-18 07:15:16 -04:00
|
|
|
ArchivePushAsyncData jobData =
|
|
|
|
{
|
|
|
|
.walPath = strLstGet(commandParam, 0),
|
|
|
|
.compress = cfgOptionBool(cfgOptCompress),
|
|
|
|
.compressLevel = cfgOptionInt(cfgOptCompressLevel),
|
|
|
|
};
|
2019-03-29 13:26:33 +00:00
|
|
|
|
|
|
|
TRY_BEGIN()
|
|
|
|
{
|
|
|
|
// Test for stop file
|
2019-07-10 12:04:25 -04:00
|
|
|
lockStopTest();
|
2019-03-29 13:26:33 +00:00
|
|
|
|
|
|
|
// Get a list of WAL files that are ready for processing
|
2019-09-18 07:15:16 -04:00
|
|
|
jobData.walFileList = archivePushProcessList(jobData.walPath);
|
2019-03-29 13:26:33 +00:00
|
|
|
|
2020-01-15 12:24:58 -07:00
|
|
|
// The archive-push:async command should not have been called unless there are WAL files to process
|
2019-09-18 07:15:16 -04:00
|
|
|
if (strLstSize(jobData.walFileList) == 0)
|
2019-03-29 13:26:33 +00:00
|
|
|
THROW(AssertError, "no WAL files to process");
|
|
|
|
|
2019-11-22 13:33:26 -05:00
|
|
|
LOG_INFO_FMT(
|
2019-09-18 07:15:16 -04:00
|
|
|
"push %u WAL file(s) to archive: %s%s", strLstSize(jobData.walFileList), strPtr(strLstGet(jobData.walFileList, 0)),
|
|
|
|
strLstSize(jobData.walFileList) == 1 ?
|
|
|
|
"" : strPtr(strNewFmt("...%s", strPtr(strLstGet(jobData.walFileList, strLstSize(jobData.walFileList) - 1)))));
|
2019-03-29 13:26:33 +00:00
|
|
|
|
|
|
|
// Drop files if queue max has been exceeded
|
2019-09-18 07:15:16 -04:00
|
|
|
if (cfgOptionTest(cfgOptArchivePushQueueMax) && archivePushDrop(jobData.walPath, jobData.walFileList))
|
2019-03-29 13:26:33 +00:00
|
|
|
{
|
2019-09-18 07:15:16 -04:00
|
|
|
for (unsigned int walFileIdx = 0; walFileIdx < strLstSize(jobData.walFileList); walFileIdx++)
|
2019-03-29 13:26:33 +00:00
|
|
|
{
|
2019-09-18 07:15:16 -04:00
|
|
|
const String *walFile = strLstGet(jobData.walFileList, walFileIdx);
|
2019-04-19 11:40:39 -04:00
|
|
|
const String *warning = archivePushDropWarning(walFile, cfgOptionUInt64(cfgOptArchivePushQueueMax));
|
2019-03-29 13:26:33 +00:00
|
|
|
|
|
|
|
archiveAsyncStatusOkWrite(archiveModePush, walFile, warning);
|
|
|
|
LOG_WARN(strPtr(warning));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Else continue processing
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Get the repo storage in case it is remote and encryption settings need to be pulled down
|
|
|
|
storageRepo();
|
|
|
|
|
2019-09-18 07:15:16 -04:00
|
|
|
// Get cipher type
|
|
|
|
jobData.cipherType = cipherType(cfgOptionStr(cfgOptRepoCipherType));
|
|
|
|
|
2019-03-29 13:26:33 +00:00
|
|
|
// Get archive info
|
2019-09-18 07:15:16 -04:00
|
|
|
jobData.archiveInfo = archivePushCheck(
|
2019-03-29 13:26:33 +00:00
|
|
|
cipherType(cfgOptionStr(cfgOptRepoCipherType)), cfgOptionStr(cfgOptRepoCipherPass));
|
|
|
|
|
|
|
|
// Create the parallel executor
|
|
|
|
ProtocolParallel *parallelExec = protocolParallelNew(
|
2019-09-18 07:15:16 -04:00
|
|
|
(TimeMSec)(cfgOptionDbl(cfgOptProtocolTimeout) * MSEC_PER_SEC) / 2, archivePushAsyncCallback, &jobData);
|
2019-03-29 13:26:33 +00:00
|
|
|
|
2019-04-19 11:40:39 -04:00
|
|
|
for (unsigned int processIdx = 1; processIdx <= cfgOptionUInt(cfgOptProcessMax); processIdx++)
|
2019-11-23 10:32:57 -05:00
|
|
|
protocolParallelClientAdd(parallelExec, protocolLocalGet(protocolStorageTypeRepo, 1, processIdx));
|
2019-03-29 13:26:33 +00:00
|
|
|
|
|
|
|
// Process jobs
|
|
|
|
do
|
|
|
|
{
|
|
|
|
unsigned int completed = protocolParallelProcess(parallelExec);
|
|
|
|
|
|
|
|
for (unsigned int jobIdx = 0; jobIdx < completed; jobIdx++)
|
|
|
|
{
|
|
|
|
protocolKeepAlive();
|
|
|
|
|
|
|
|
// Get the job and job key
|
|
|
|
ProtocolParallelJob *job = protocolParallelResult(parallelExec);
|
2019-04-09 11:08:27 -04:00
|
|
|
unsigned int processId = protocolParallelJobProcessId(job);
|
2019-03-29 13:26:33 +00:00
|
|
|
const String *walFile = varStr(protocolParallelJobKey(job));
|
|
|
|
|
|
|
|
// The job was successful
|
|
|
|
if (protocolParallelJobErrorCode(job) == 0)
|
|
|
|
{
|
2019-11-22 13:33:26 -05:00
|
|
|
LOG_DETAIL_PID_FMT(processId, "pushed WAL file '%s' to the archive", strPtr(walFile));
|
2019-03-29 13:26:33 +00:00
|
|
|
archiveAsyncStatusOkWrite(archiveModePush, walFile, varStr(protocolParallelJobResult(job)));
|
|
|
|
}
|
|
|
|
// Else the job errored
|
|
|
|
else
|
|
|
|
{
|
2019-11-22 13:33:26 -05:00
|
|
|
LOG_WARN_PID_FMT(
|
2019-04-09 11:08:27 -04:00
|
|
|
processId,
|
2019-03-29 13:26:33 +00:00
|
|
|
"could not push WAL file '%s' to the archive (will be retried): [%d] %s", strPtr(walFile),
|
|
|
|
protocolParallelJobErrorCode(job), strPtr(protocolParallelJobErrorMessage(job)));
|
|
|
|
|
|
|
|
archiveAsyncStatusErrorWrite(
|
|
|
|
archiveModePush, walFile, protocolParallelJobErrorCode(job), protocolParallelJobErrorMessage(job));
|
|
|
|
}
|
2019-09-18 07:15:16 -04:00
|
|
|
|
|
|
|
protocolParallelJobFree(job);
|
2019-03-29 13:26:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
while (!protocolParallelDone(parallelExec));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// On any global error write a single error file to cover all unprocessed files
|
|
|
|
CATCH_ANY()
|
|
|
|
{
|
2019-04-16 13:39:58 -04:00
|
|
|
archiveAsyncStatusErrorWrite(archiveModePush, NULL, errorCode(), STR(errorMessage()));
|
2019-03-29 13:26:33 +00:00
|
|
|
RETHROW();
|
|
|
|
}
|
|
|
|
TRY_END();
|
2018-01-17 15:52:00 -05:00
|
|
|
}
|
|
|
|
MEM_CONTEXT_TEMP_END();
|
2018-05-18 11:57:32 -04:00
|
|
|
|
2019-01-21 17:41:59 +02:00
|
|
|
FUNCTION_LOG_RETURN_VOID();
|
2018-01-17 15:52:00 -05:00
|
|
|
}
|