mirror of
https://github.com/postgres/postgres.git
synced 2025-06-30 21:42:05 +03:00
Integrate recovery.conf into postgresql.conf
recovery.conf settings are now set in postgresql.conf (or other GUC sources). Currently, all the affected settings are PGC_POSTMASTER; this could be refined in the future case by case. Recovery is now initiated by a file recovery.signal. Standby mode is initiated by a file standby.signal. The standby_mode setting is gone. If a recovery.conf file is found, an error is issued. The trigger_file setting has been renamed to promote_trigger_file as part of the move. The documentation chapter "Recovery Configuration" has been integrated into "Server Configuration". pg_basebackup -R now appends settings to postgresql.auto.conf and creates a standby.signal file. Author: Fujii Masao <masao.fujii@gmail.com> Author: Simon Riggs <simon@2ndquadrant.com> Author: Abhijit Menon-Sen <ams@2ndquadrant.com> Author: Sergei Kornilov <sk@zsrv.org> Discussion: https://www.postgresql.org/message-id/flat/607741529606767@web3g.yandex.ru/
This commit is contained in:
@ -69,7 +69,6 @@
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/guc.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/pg_lsn.h"
|
||||
#include "utils/ps_status.h"
|
||||
#include "utils/relmapper.h"
|
||||
#include "utils/snapmgr.h"
|
||||
@ -78,6 +77,9 @@
|
||||
|
||||
extern uint32 bootstrap_data_checksum_version;
|
||||
|
||||
/* Unsupported old recovery command file names (relative to $PGDATA) */
|
||||
#define RECOVERY_COMMAND_FILE "recovery.conf"
|
||||
#define RECOVERY_COMMAND_DONE "recovery.done"
|
||||
|
||||
/* User-settable parameters */
|
||||
int max_wal_size_mb = 1024; /* 1 GB */
|
||||
@ -161,6 +163,13 @@ const struct config_enum_entry archive_mode_options[] = {
|
||||
{NULL, 0, false}
|
||||
};
|
||||
|
||||
const struct config_enum_entry recovery_target_action_options[] = {
|
||||
{"pause", RECOVERY_TARGET_ACTION_PAUSE, false},
|
||||
{"promote", RECOVERY_TARGET_ACTION_PROMOTE, false},
|
||||
{"shutdown", RECOVERY_TARGET_ACTION_SHUTDOWN, false},
|
||||
{NULL, 0, false}
|
||||
};
|
||||
|
||||
/*
|
||||
* Statistics for current checkpoint are collected in this global struct.
|
||||
* Because only the checkpointer or a stand-alone backend can perform
|
||||
@ -230,7 +239,7 @@ static int LocalXLogInsertAllowed = -1;
|
||||
|
||||
/*
|
||||
* When ArchiveRecoveryRequested is set, archive recovery was requested,
|
||||
* ie. recovery.conf file was present. When InArchiveRecovery is set, we are
|
||||
* ie. signal files were present. When InArchiveRecovery is set, we are
|
||||
* currently recovering using offline XLOG archives. These variables are only
|
||||
* valid in the startup process.
|
||||
*
|
||||
@ -242,6 +251,9 @@ static int LocalXLogInsertAllowed = -1;
|
||||
bool ArchiveRecoveryRequested = false;
|
||||
bool InArchiveRecovery = false;
|
||||
|
||||
static bool standby_signal_file_found = false;
|
||||
static bool recovery_signal_file_found = false;
|
||||
|
||||
/* Was the last xlog file restored from archive, or local? */
|
||||
static bool restoredFromArchive = false;
|
||||
|
||||
@ -249,25 +261,25 @@ static bool restoredFromArchive = false;
|
||||
static char *replay_image_masked = NULL;
|
||||
static char *master_image_masked = NULL;
|
||||
|
||||
/* options taken from recovery.conf for archive recovery */
|
||||
/* options formerly taken from recovery.conf for archive recovery */
|
||||
char *recoveryRestoreCommand = NULL;
|
||||
static char *recoveryEndCommand = NULL;
|
||||
static char *archiveCleanupCommand = NULL;
|
||||
static RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
|
||||
static bool recoveryTargetInclusive = true;
|
||||
static RecoveryTargetAction recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
|
||||
static TransactionId recoveryTargetXid;
|
||||
static TimestampTz recoveryTargetTime;
|
||||
static char *recoveryTargetName;
|
||||
static XLogRecPtr recoveryTargetLSN;
|
||||
static int recovery_min_apply_delay = 0;
|
||||
static TimestampTz recoveryDelayUntilTime;
|
||||
char *recoveryEndCommand = NULL;
|
||||
char *archiveCleanupCommand = NULL;
|
||||
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
|
||||
bool recoveryTargetInclusive = true;
|
||||
int recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
|
||||
TransactionId recoveryTargetXid;
|
||||
TimestampTz recoveryTargetTime;
|
||||
char *recoveryTargetName;
|
||||
XLogRecPtr recoveryTargetLSN;
|
||||
int recovery_min_apply_delay = 0;
|
||||
TimestampTz recoveryDelayUntilTime;
|
||||
|
||||
/* options taken from recovery.conf for XLOG streaming */
|
||||
static bool StandbyModeRequested = false;
|
||||
static char *PrimaryConnInfo = NULL;
|
||||
static char *PrimarySlotName = NULL;
|
||||
static char *TriggerFile = NULL;
|
||||
/* options formerly taken from recovery.conf for XLOG streaming */
|
||||
bool StandbyModeRequested = false;
|
||||
char *PrimaryConnInfo = NULL;
|
||||
char *PrimarySlotName = NULL;
|
||||
char *PromoteTriggerFile = NULL;
|
||||
|
||||
/* are we currently in standby mode? */
|
||||
bool StandbyMode = false;
|
||||
@ -293,7 +305,11 @@ static bool recoveryStopAfter;
|
||||
* the currently-scanned WAL record was generated). We also need these
|
||||
* timeline values:
|
||||
*
|
||||
* recoveryTargetTLI: the desired timeline that we want to end in.
|
||||
* recoveryTargetTimeLineGoal: what the user requested, if any
|
||||
*
|
||||
* recoveryTargetTLIRequested: numeric value of requested timeline, if constant
|
||||
*
|
||||
* recoveryTargetTLI: the currently understood target timeline; changes
|
||||
*
|
||||
* recoveryTargetIsLatest: was the requested target timeline 'latest'?
|
||||
*
|
||||
@ -309,8 +325,9 @@ static bool recoveryStopAfter;
|
||||
* file was created.) During a sequential scan we do not allow this value
|
||||
* to decrease.
|
||||
*/
|
||||
static TimeLineID recoveryTargetTLI;
|
||||
static bool recoveryTargetIsLatest = false;
|
||||
RecoveryTargetTimeLineGoal recoveryTargetTimeLineGoal = RECOVERY_TARGET_TIMELINE_CONTROLFILE;
|
||||
TimeLineID recoveryTargetTLIRequested = 0;
|
||||
TimeLineID recoveryTargetTLI = 0;
|
||||
static List *expectedTLEs;
|
||||
static TimeLineID curFileTLI;
|
||||
|
||||
@ -624,12 +641,6 @@ typedef struct XLogCtlData
|
||||
TimeLineID ThisTimeLineID;
|
||||
TimeLineID PrevTimeLineID;
|
||||
|
||||
/*
|
||||
* archiveCleanupCommand is read from recovery.conf but needs to be in
|
||||
* shared memory so that the checkpointer process can access it.
|
||||
*/
|
||||
char archiveCleanupCommand[MAXPGPATH];
|
||||
|
||||
/*
|
||||
* SharedRecoveryInProgress indicates if we're still in crash or archive
|
||||
* recovery. Protected by info_lck.
|
||||
@ -846,7 +857,8 @@ static bool holdingAllLocks = false;
|
||||
static MemoryContext walDebugCxt = NULL;
|
||||
#endif
|
||||
|
||||
static void readRecoveryCommandFile(void);
|
||||
static void readRecoverySignalFile(void);
|
||||
static void validateRecoveryParameters(void);
|
||||
static void exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog);
|
||||
static bool recoveryStopsBefore(XLogReaderState *record);
|
||||
static bool recoveryStopsAfter(XLogReaderState *record);
|
||||
@ -5285,283 +5297,111 @@ str_time(pg_time_t tnow)
|
||||
}
|
||||
|
||||
/*
|
||||
* See if there is a recovery command file (recovery.conf), and if so
|
||||
* read in parameters for archive recovery and XLOG streaming.
|
||||
* See if there are any recovery signal files and if so, set state for
|
||||
* recovery.
|
||||
*
|
||||
* The file is parsed using the main configuration parser.
|
||||
* See if there is a recovery command file (recovery.conf), and if so
|
||||
* throw an ERROR since as of PG12 we no longer recognize that.
|
||||
*/
|
||||
static void
|
||||
readRecoveryCommandFile(void)
|
||||
readRecoverySignalFile(void)
|
||||
{
|
||||
FILE *fd;
|
||||
TimeLineID rtli = 0;
|
||||
bool rtliGiven = false;
|
||||
ConfigVariable *item,
|
||||
*head = NULL,
|
||||
*tail = NULL;
|
||||
bool recoveryTargetActionSet = false;
|
||||
struct stat stat_buf;
|
||||
|
||||
|
||||
fd = AllocateFile(RECOVERY_COMMAND_FILE, "r");
|
||||
if (fd == NULL)
|
||||
{
|
||||
if (errno == ENOENT)
|
||||
return; /* not there, so no archive recovery */
|
||||
ereport(FATAL,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not open recovery command file \"%s\": %m",
|
||||
RECOVERY_COMMAND_FILE)));
|
||||
}
|
||||
if (IsBootstrapProcessingMode())
|
||||
return;
|
||||
|
||||
/*
|
||||
* Since we're asking ParseConfigFp() to report errors as FATAL, there's
|
||||
* no need to check the return value.
|
||||
* Check for old recovery API file: recovery.conf
|
||||
*/
|
||||
(void) ParseConfigFp(fd, RECOVERY_COMMAND_FILE, 0, FATAL, &head, &tail);
|
||||
if (stat(RECOVERY_COMMAND_FILE, &stat_buf) == 0)
|
||||
ereport(FATAL,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("using recovery command file \"%s\" is not supported",
|
||||
RECOVERY_COMMAND_FILE)));
|
||||
|
||||
FreeFile(fd);
|
||||
/*
|
||||
* Remove unused .done file, if present. Ignore if absent.
|
||||
*/
|
||||
unlink(RECOVERY_COMMAND_DONE);
|
||||
|
||||
for (item = head; item; item = item->next)
|
||||
/*
|
||||
* Check for recovery signal files and if found, fsync them since they
|
||||
* represent server state information.
|
||||
*
|
||||
* If present, standby signal file takes precedence. If neither is present
|
||||
* then we won't enter archive recovery.
|
||||
*/
|
||||
if (stat(STANDBY_SIGNAL_FILE, &stat_buf) == 0)
|
||||
{
|
||||
if (strcmp(item->name, "restore_command") == 0)
|
||||
{
|
||||
recoveryRestoreCommand = pstrdup(item->value);
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("restore_command = '%s'",
|
||||
recoveryRestoreCommand)));
|
||||
}
|
||||
else if (strcmp(item->name, "recovery_end_command") == 0)
|
||||
{
|
||||
recoveryEndCommand = pstrdup(item->value);
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("recovery_end_command = '%s'",
|
||||
recoveryEndCommand)));
|
||||
}
|
||||
else if (strcmp(item->name, "archive_cleanup_command") == 0)
|
||||
{
|
||||
archiveCleanupCommand = pstrdup(item->value);
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("archive_cleanup_command = '%s'",
|
||||
archiveCleanupCommand)));
|
||||
}
|
||||
else if (strcmp(item->name, "recovery_target_action") == 0)
|
||||
{
|
||||
if (strcmp(item->value, "pause") == 0)
|
||||
recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
|
||||
else if (strcmp(item->value, "promote") == 0)
|
||||
recoveryTargetAction = RECOVERY_TARGET_ACTION_PROMOTE;
|
||||
else if (strcmp(item->value, "shutdown") == 0)
|
||||
recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN;
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("invalid value for recovery parameter \"%s\": \"%s\"",
|
||||
"recovery_target_action",
|
||||
item->value),
|
||||
errhint("Valid values are \"pause\", \"promote\", and \"shutdown\".")));
|
||||
int fd;
|
||||
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("recovery_target_action = '%s'",
|
||||
item->value)));
|
||||
|
||||
recoveryTargetActionSet = true;
|
||||
}
|
||||
else if (strcmp(item->name, "recovery_target_timeline") == 0)
|
||||
{
|
||||
rtliGiven = true;
|
||||
if (strcmp(item->value, "latest") == 0)
|
||||
rtli = 0;
|
||||
else
|
||||
{
|
||||
errno = 0;
|
||||
rtli = (TimeLineID) strtoul(item->value, NULL, 0);
|
||||
if (errno == EINVAL || errno == ERANGE)
|
||||
ereport(FATAL,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("recovery_target_timeline is not a valid number: \"%s\"",
|
||||
item->value)));
|
||||
}
|
||||
if (rtli)
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("recovery_target_timeline = %u", rtli)));
|
||||
else
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("recovery_target_timeline = latest")));
|
||||
}
|
||||
else if (strcmp(item->name, "recovery_target_xid") == 0)
|
||||
{
|
||||
errno = 0;
|
||||
recoveryTargetXid = (TransactionId) strtoul(item->value, NULL, 0);
|
||||
if (errno == EINVAL || errno == ERANGE)
|
||||
ereport(FATAL,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("recovery_target_xid is not a valid number: \"%s\"",
|
||||
item->value)));
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("recovery_target_xid = %u",
|
||||
recoveryTargetXid)));
|
||||
recoveryTarget = RECOVERY_TARGET_XID;
|
||||
}
|
||||
else if (strcmp(item->name, "recovery_target_time") == 0)
|
||||
{
|
||||
recoveryTarget = RECOVERY_TARGET_TIME;
|
||||
|
||||
if (strcmp(item->value, "epoch") == 0 ||
|
||||
strcmp(item->value, "infinity") == 0 ||
|
||||
strcmp(item->value, "-infinity") == 0 ||
|
||||
strcmp(item->value, "now") == 0 ||
|
||||
strcmp(item->value, "today") == 0 ||
|
||||
strcmp(item->value, "tomorrow") == 0 ||
|
||||
strcmp(item->value, "yesterday") == 0)
|
||||
ereport(FATAL,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("recovery_target_time is not a valid timestamp: \"%s\"",
|
||||
item->value)));
|
||||
|
||||
/*
|
||||
* Convert the time string given by the user to TimestampTz form.
|
||||
*/
|
||||
recoveryTargetTime =
|
||||
DatumGetTimestampTz(DirectFunctionCall3(timestamptz_in,
|
||||
CStringGetDatum(item->value),
|
||||
ObjectIdGetDatum(InvalidOid),
|
||||
Int32GetDatum(-1)));
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("recovery_target_time = '%s'",
|
||||
timestamptz_to_str(recoveryTargetTime))));
|
||||
}
|
||||
else if (strcmp(item->name, "recovery_target_name") == 0)
|
||||
{
|
||||
recoveryTarget = RECOVERY_TARGET_NAME;
|
||||
|
||||
recoveryTargetName = pstrdup(item->value);
|
||||
if (strlen(recoveryTargetName) >= MAXFNAMELEN)
|
||||
ereport(FATAL,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("recovery_target_name is too long (maximum %d characters)",
|
||||
MAXFNAMELEN - 1)));
|
||||
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("recovery_target_name = '%s'",
|
||||
recoveryTargetName)));
|
||||
}
|
||||
else if (strcmp(item->name, "recovery_target_lsn") == 0)
|
||||
{
|
||||
recoveryTarget = RECOVERY_TARGET_LSN;
|
||||
|
||||
/*
|
||||
* Convert the LSN string given by the user to XLogRecPtr form.
|
||||
*/
|
||||
recoveryTargetLSN =
|
||||
DatumGetLSN(DirectFunctionCall3(pg_lsn_in,
|
||||
CStringGetDatum(item->value),
|
||||
ObjectIdGetDatum(InvalidOid),
|
||||
Int32GetDatum(-1)));
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("recovery_target_lsn = '%X/%X'",
|
||||
(uint32) (recoveryTargetLSN >> 32),
|
||||
(uint32) recoveryTargetLSN)));
|
||||
}
|
||||
else if (strcmp(item->name, "recovery_target") == 0)
|
||||
{
|
||||
if (strcmp(item->value, "immediate") == 0)
|
||||
recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("invalid value for recovery parameter \"%s\": \"%s\"",
|
||||
"recovery_target",
|
||||
item->value),
|
||||
errhint("The only allowed value is \"immediate\".")));
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("recovery_target = '%s'",
|
||||
item->value)));
|
||||
}
|
||||
else if (strcmp(item->name, "recovery_target_inclusive") == 0)
|
||||
{
|
||||
/*
|
||||
* does nothing if a recovery_target is not also set
|
||||
*/
|
||||
if (!parse_bool(item->value, &recoveryTargetInclusive))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("parameter \"%s\" requires a Boolean value",
|
||||
"recovery_target_inclusive")));
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("recovery_target_inclusive = %s",
|
||||
item->value)));
|
||||
}
|
||||
else if (strcmp(item->name, "standby_mode") == 0)
|
||||
{
|
||||
if (!parse_bool(item->value, &StandbyModeRequested))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("parameter \"%s\" requires a Boolean value",
|
||||
"standby_mode")));
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("standby_mode = '%s'", item->value)));
|
||||
}
|
||||
else if (strcmp(item->name, "primary_conninfo") == 0)
|
||||
{
|
||||
PrimaryConnInfo = pstrdup(item->value);
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("primary_conninfo = '%s'",
|
||||
PrimaryConnInfo)));
|
||||
}
|
||||
else if (strcmp(item->name, "primary_slot_name") == 0)
|
||||
{
|
||||
ReplicationSlotValidateName(item->value, ERROR);
|
||||
PrimarySlotName = pstrdup(item->value);
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("primary_slot_name = '%s'",
|
||||
PrimarySlotName)));
|
||||
}
|
||||
else if (strcmp(item->name, "trigger_file") == 0)
|
||||
{
|
||||
TriggerFile = pstrdup(item->value);
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("trigger_file = '%s'",
|
||||
TriggerFile)));
|
||||
}
|
||||
else if (strcmp(item->name, "recovery_min_apply_delay") == 0)
|
||||
{
|
||||
const char *hintmsg;
|
||||
|
||||
if (!parse_int(item->value, &recovery_min_apply_delay, GUC_UNIT_MS,
|
||||
&hintmsg))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("parameter \"%s\" requires a temporal value",
|
||||
"recovery_min_apply_delay"),
|
||||
hintmsg ? errhint("%s", _(hintmsg)) : 0));
|
||||
ereport(DEBUG2,
|
||||
(errmsg_internal("recovery_min_apply_delay = '%s'", item->value)));
|
||||
}
|
||||
else
|
||||
ereport(FATAL,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("unrecognized recovery parameter \"%s\"",
|
||||
item->name)));
|
||||
fd = BasicOpenFilePerm(STANDBY_SIGNAL_FILE, O_RDWR | PG_BINARY | get_sync_bit(sync_method),
|
||||
S_IRUSR | S_IWUSR);
|
||||
pg_fsync(fd);
|
||||
close(fd);
|
||||
standby_signal_file_found = true;
|
||||
}
|
||||
else if (stat(RECOVERY_SIGNAL_FILE, &stat_buf) == 0)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = BasicOpenFilePerm(RECOVERY_SIGNAL_FILE, O_RDWR | PG_BINARY | get_sync_bit(sync_method),
|
||||
S_IRUSR | S_IWUSR);
|
||||
pg_fsync(fd);
|
||||
close(fd);
|
||||
recovery_signal_file_found = true;
|
||||
}
|
||||
|
||||
StandbyModeRequested = false;
|
||||
ArchiveRecoveryRequested = false;
|
||||
if (standby_signal_file_found)
|
||||
{
|
||||
StandbyModeRequested = true;
|
||||
ArchiveRecoveryRequested = true;
|
||||
}
|
||||
else if (recovery_signal_file_found)
|
||||
{
|
||||
StandbyModeRequested = false;
|
||||
ArchiveRecoveryRequested = true;
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
/*
|
||||
* We don't support standby mode in standalone backends; that requires
|
||||
* other processes such as the WAL receiver to be alive.
|
||||
*/
|
||||
if (StandbyModeRequested && !IsUnderPostmaster)
|
||||
ereport(FATAL,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("standby mode is not supported by single-user servers")));
|
||||
}
|
||||
|
||||
static void
|
||||
validateRecoveryParameters(void)
|
||||
{
|
||||
if (!ArchiveRecoveryRequested)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Check for compulsory parameters
|
||||
*/
|
||||
if (StandbyModeRequested)
|
||||
{
|
||||
if (PrimaryConnInfo == NULL && recoveryRestoreCommand == NULL)
|
||||
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
|
||||
(recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
|
||||
ereport(WARNING,
|
||||
(errmsg("recovery command file \"%s\" specified neither primary_conninfo nor restore_command",
|
||||
RECOVERY_COMMAND_FILE),
|
||||
(errmsg("specified neither primary_conninfo nor restore_command"),
|
||||
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (recoveryRestoreCommand == NULL)
|
||||
if (recoveryRestoreCommand == NULL ||
|
||||
strcmp(recoveryRestoreCommand, "") == 0)
|
||||
ereport(FATAL,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("recovery command file \"%s\" must specify restore_command when standby mode is not enabled",
|
||||
RECOVERY_COMMAND_FILE)));
|
||||
errmsg("must specify restore_command when standby mode is not enabled")));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -5570,50 +5410,40 @@ readRecoveryCommandFile(void)
|
||||
* hot_standby = off, which was surprising behaviour.
|
||||
*/
|
||||
if (recoveryTargetAction == RECOVERY_TARGET_ACTION_PAUSE &&
|
||||
recoveryTargetActionSet &&
|
||||
!EnableHotStandby)
|
||||
recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN;
|
||||
|
||||
/*
|
||||
* We don't support standby_mode in standalone backends; that requires
|
||||
* other processes such as the WAL receiver to be alive.
|
||||
*/
|
||||
if (StandbyModeRequested && !IsUnderPostmaster)
|
||||
ereport(FATAL,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("standby mode is not supported by single-user servers")));
|
||||
|
||||
/* Enable fetching from archive recovery area */
|
||||
ArchiveRecoveryRequested = true;
|
||||
|
||||
/*
|
||||
* If user specified recovery_target_timeline, validate it or compute the
|
||||
* "latest" value. We can't do this until after we've gotten the restore
|
||||
* command and set InArchiveRecovery, because we need to fetch timeline
|
||||
* history files from the archive.
|
||||
*/
|
||||
if (rtliGiven)
|
||||
if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC)
|
||||
{
|
||||
if (rtli)
|
||||
{
|
||||
/* Timeline 1 does not have a history file, all else should */
|
||||
if (rtli != 1 && !existsTimeLineHistory(rtli))
|
||||
ereport(FATAL,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("recovery target timeline %u does not exist",
|
||||
rtli)));
|
||||
recoveryTargetTLI = rtli;
|
||||
recoveryTargetIsLatest = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We start the "latest" search from pg_control's timeline */
|
||||
recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI);
|
||||
recoveryTargetIsLatest = true;
|
||||
}
|
||||
}
|
||||
TimeLineID rtli = recoveryTargetTLIRequested;
|
||||
|
||||
FreeConfigVariables(head);
|
||||
/* Timeline 1 does not have a history file, all else should */
|
||||
if (rtli != 1 && !existsTimeLineHistory(rtli))
|
||||
ereport(FATAL,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("recovery target timeline %u does not exist",
|
||||
rtli)));
|
||||
recoveryTargetTLI = rtli;
|
||||
}
|
||||
else if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST)
|
||||
{
|
||||
/* We start the "latest" search from pg_control's timeline */
|
||||
recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* else we just use the recoveryTargetTLI as already read from
|
||||
* ControlFile
|
||||
*/
|
||||
Assert(recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_CONTROLFILE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -5714,11 +5544,14 @@ exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog)
|
||||
unlink(recoveryPath); /* ignore any error */
|
||||
|
||||
/*
|
||||
* Rename the config file out of the way, so that we don't accidentally
|
||||
* Remove the signal files out of the way, so that we don't accidentally
|
||||
* re-enter archive recovery mode in a subsequent crash.
|
||||
*/
|
||||
unlink(RECOVERY_COMMAND_DONE);
|
||||
durable_rename(RECOVERY_COMMAND_FILE, RECOVERY_COMMAND_DONE, FATAL);
|
||||
if (standby_signal_file_found)
|
||||
durable_unlink(STANDBY_SIGNAL_FILE, FATAL);
|
||||
|
||||
if (recovery_signal_file_found)
|
||||
durable_unlink(RECOVERY_SIGNAL_FILE, FATAL);
|
||||
|
||||
ereport(LOG,
|
||||
(errmsg("archive recovery complete")));
|
||||
@ -6461,18 +6294,10 @@ StartupXLOG(void)
|
||||
recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
|
||||
|
||||
/*
|
||||
* Check for recovery control file, and if so set up state for offline
|
||||
* recovery
|
||||
* Check for signal files, and if so set up state for offline recovery
|
||||
*/
|
||||
readRecoveryCommandFile();
|
||||
|
||||
/*
|
||||
* Save archive_cleanup_command in shared memory so that other processes
|
||||
* can see it.
|
||||
*/
|
||||
strlcpy(XLogCtl->archiveCleanupCommand,
|
||||
archiveCleanupCommand ? archiveCleanupCommand : "",
|
||||
sizeof(XLogCtl->archiveCleanupCommand));
|
||||
readRecoverySignalFile();
|
||||
validateRecoveryParameters();
|
||||
|
||||
if (ArchiveRecoveryRequested)
|
||||
{
|
||||
@ -6652,7 +6477,8 @@ StartupXLOG(void)
|
||||
* This can happen for example if a base backup is taken from a
|
||||
* running server using an atomic filesystem snapshot, without calling
|
||||
* pg_start/stop_backup. Or if you just kill a running master server
|
||||
* and put it into archive recovery by creating a recovery.conf file.
|
||||
* and put it into archive recovery by creating a recovery signal
|
||||
* file.
|
||||
*
|
||||
* Our strategy in that case is to perform crash recovery first,
|
||||
* replaying all the WAL present in pg_wal, and only enter archive
|
||||
@ -6687,7 +6513,7 @@ StartupXLOG(void)
|
||||
{
|
||||
/*
|
||||
* We used to attempt to go back to a secondary checkpoint record
|
||||
* here, but only when not in standby_mode. We now just fail if we
|
||||
* here, but only when not in standby mode. We now just fail if we
|
||||
* can't read the last checkpoint because this allows us to
|
||||
* simplify processing around checkpoints.
|
||||
*/
|
||||
@ -6878,7 +6704,7 @@ StartupXLOG(void)
|
||||
|
||||
/*
|
||||
* Check whether we need to force recovery from WAL. If it appears to
|
||||
* have been a clean shutdown and we did not have a recovery.conf file,
|
||||
* have been a clean shutdown and we did not have a recovery signal file,
|
||||
* then assume no recovery needed.
|
||||
*/
|
||||
if (checkPoint.redo < RecPtr)
|
||||
@ -6892,7 +6718,7 @@ StartupXLOG(void)
|
||||
InRecovery = true;
|
||||
else if (ArchiveRecoveryRequested)
|
||||
{
|
||||
/* force recovery due to presence of recovery.conf */
|
||||
/* force recovery due to presence of recovery signal file */
|
||||
InRecovery = true;
|
||||
}
|
||||
|
||||
@ -7763,7 +7589,7 @@ StartupXLOG(void)
|
||||
/*
|
||||
* And finally, execute the recovery_end_command, if any.
|
||||
*/
|
||||
if (recoveryEndCommand)
|
||||
if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
|
||||
ExecuteRecoveryCommand(recoveryEndCommand,
|
||||
"recovery_end_command",
|
||||
true);
|
||||
@ -9485,8 +9311,8 @@ CreateRestartPoint(int flags)
|
||||
/*
|
||||
* Finally, execute archive_cleanup_command, if any.
|
||||
*/
|
||||
if (XLogCtl->archiveCleanupCommand[0])
|
||||
ExecuteRecoveryCommand(XLogCtl->archiveCleanupCommand,
|
||||
if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
|
||||
ExecuteRecoveryCommand(archiveCleanupCommand,
|
||||
"archive_cleanup_command",
|
||||
false);
|
||||
|
||||
@ -11995,7 +11821,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
|
||||
* that when we later jump backwards to start redo at
|
||||
* RedoStartLSN, we will have the logs streamed already.
|
||||
*/
|
||||
if (PrimaryConnInfo)
|
||||
if (PrimaryConnInfo && strcmp(PrimaryConnInfo, "") != 0)
|
||||
{
|
||||
XLogRecPtr ptr;
|
||||
TimeLineID tli;
|
||||
@ -12064,7 +11890,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
|
||||
* Before we sleep, re-scan for possible new timelines if
|
||||
* we were requested to recover to the latest timeline.
|
||||
*/
|
||||
if (recoveryTargetIsLatest)
|
||||
if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST)
|
||||
{
|
||||
if (rescanLatestTimeLine())
|
||||
{
|
||||
@ -12367,14 +12193,14 @@ CheckForStandbyTrigger(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TriggerFile == NULL)
|
||||
if (PromoteTriggerFile == NULL || strcmp(PromoteTriggerFile, "") == 0)
|
||||
return false;
|
||||
|
||||
if (stat(TriggerFile, &stat_buf) == 0)
|
||||
if (stat(PromoteTriggerFile, &stat_buf) == 0)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("trigger file found: %s", TriggerFile)));
|
||||
unlink(TriggerFile);
|
||||
(errmsg("promote trigger file found: %s", PromoteTriggerFile)));
|
||||
unlink(PromoteTriggerFile);
|
||||
triggered = true;
|
||||
fast_promote = true;
|
||||
return true;
|
||||
@ -12382,8 +12208,8 @@ CheckForStandbyTrigger(void)
|
||||
else if (errno != ENOENT)
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not stat trigger file \"%s\": %m",
|
||||
TriggerFile)));
|
||||
errmsg("could not stat promote trigger file \"%s\": %m",
|
||||
PromoteTriggerFile)));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
Reference in New Issue
Block a user