diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml index 8c24ae2..3071879 100644 --- a/doc/src/sgml/recovery-config.sgml +++ b/doc/src/sgml/recovery-config.sgml @@ -11,30 +11,90 @@ This chapter describes the settings available in the - recovery.confrecovery.conf - file. They apply only for the duration of the + postgresql.confpostgresql.conf + file that apply only for the duration of the recovery. They must be reset for any subsequent recovery you wish to - perform. They cannot be changed once recovery has begun. + perform. - Settings in recovery.conf are specified in the format - name = 'value'. One parameter is specified per line. - Hash marks (#) designate the rest of the - line as a comment. To embed a single quote in a parameter - value, write two quotes (''). + The database server can also be started "in recovery" a term that covers + using the server as a standby or for executing a targeted recovery. Typically + standby mode would be used to provide high availability and/or read + scalability, whereas a targeted recovery is used to recover from data loss. - A sample file, share/recovery.conf.sample, - is provided in the installation's share/ directory. + To start the server in standby mode create a zero-length file + called standby.signalstandby.signal + in the signal_file_directory. The server will enter recovery and + will not stop recovery when the end of archived WAL is reached, but + will keep trying to continue recovery by connecting to the sending server as + specified by the sender_conninfo setting and/or by + fetching new WAL segments using restore_command + In this mode you may use parameters + in both and + sections. Parameters from + will not be used. + + To start the server in targeted recovery create a zero-length file called + recovery.signalrecovery.signal + in the signal_file_directory. + If both standby.signal and recovery.signal files are + created, standby mode takes precedence. Targeted recovery mode will end when + end of archived WAL is reached, or when recovery_target is reached. + In this mode you may use parameters from both + and + sections. Parameters from + will not be used. + + + + + Recovery Signal Settings + + + + signal_file_directory (string) + + signal_file_directory recovery parameter + trigger_file a now-deprecated recovery parameter used to specify name of a promotion signal file + + + + + Specifies a directory where files can be created to signal state changes + to the server, also sometimes described as a trigger file though that term + is not normally used. The default setting is data_directory. + + + The server will look for standby.signal, recovery.signal + and promote.signal files in this directory. + + + The parameter may only be changed at server start. + + + + + + + + Archive Recovery Settings + + Parameter settings may be changed in postgresql.conf or + by executing the ALTER SYSTEM. Changes will take some + time to take effect, so changes made while not in paused state may not + have the desired effect in all cases. + + restore_command (string) @@ -45,7 +105,7 @@ The local shell command to execute to retrieve an archived segment of the WAL file series. This parameter is required for archive recovery, - but optional for streaming replication. + but optional for standby mode. Any %f in the string is replaced by the name of the file to retrieve from the archive, and any %p is replaced by the copy destination path name @@ -154,99 +214,91 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows Recovery Target Settings - By default, recovery will recover to the end of the WAL log. The - following parameters can be used to specify an earlier stopping point. - At most one of recovery_target, - recovery_target_lsn, recovery_target_name, - recovery_target_time, or recovery_target_xid - can be used; if more than one of these is specified in the configuration - file, the last entry will be used. + By default, recovery will recover to the end of the WAL log. An earlier + stopping point may be specified using recovery_target_type + and in most cases also recovery_target_value, plus the optional + parameters recovery_target_inclusive, + recovery_targeti_timeline and recovery_target_action. + + + + Parameter settings may be changed in postgresql.conf or + by executing the ALTER SYSTEM. Changes will take some + time to take effect, so changes made while not in paused state may not + have the desired effect in all cases. - - recovery_target = 'immediate' + + recovery_target_type (enum) - recovery_target recovery parameter + recovery_target_type targeted recovery parameter - This parameter specifies that recovery should end as soon as a - consistent state is reached, i.e. as early as possible. When restoring - from an online backup, this means the point where taking the backup - ended. + recovery_target_type specifies the search criteria used for a + a targeted recovery. The default value is none. Valid + values are none, immediate, + name, timestamp, + xid and lsn. + - Technically, this is a string parameter, but 'immediate' - is currently the only allowed value. + Target-type none specifies that recovery will not stop + until it runs out of WAL, which is the default setting. When not in targeted + recovery this is the only meaningful setting. - - - - recovery_target_name (string) - - recovery_target_name recovery parameter - - - - This parameter specifies the named restore point (created with - pg_create_restore_point()) to which recovery will proceed. + Target-type immediate specifies that recovery should end as + soon as a consistent state is reached, i.e. as early as possible. When restoring + from an online backup, this means the point where taking the backup ended. + For this target-type, no additional target-specifiers influence the stopping + point. - - - - recovery_target_time (timestamp) - - recovery_target_time recovery parameter - - - - This parameter specifies the time stamp up to which recovery - will proceed. + Target-type name specifies that recovery will + proceed to the point specified by recovery_target_value + when interpreted as the name of a restore point created with + pg_create_restore_point(). The precise stopping point is also influenced by - . + and + . - - - - recovery_target_xid (string) - - recovery_target_xid recovery parameter - - - - This parameter specifies the transaction ID up to which recovery - will proceed. Keep in mind + Target-type timestamp specifies that recovery will + proceed to the point specified by recovery_target_value + when interpreted as the timestamp of a transaction commit or abort. + The precise stopping point is also influenced by + and + . + + + + Target-type xid specifies that recovery will + proceed to the point specified by recovery_target_value + when interpreted as the transaction ID of a transaction commit or abort. + Keep in mind that while transaction IDs are assigned sequentially at transaction start, transactions can complete in a different numeric order. The transactions that will be recovered are those that committed before (and optionally including) the specified one. The precise stopping point is also influenced by - . + and + . - - - - recovery_target_lsn (pg_lsn) - - recovery_target_lsn recovery parameter - - - - This parameter specifies the LSN of the transaction log location up - to which recovery will proceed. The precise stopping point is also - influenced by . This - parameter is parsed using the system data type - pg_lsn. + Target-type lsn specifies that recovery will + proceed to the point specified by recovery_target_value + when interpreted as the LSN of any WAL record, parsed using the system + data type pg_lsn. + The precise stopping point is also influenced by + and + . @@ -258,6 +310,23 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows + + recovery_target_value (string) + + recovery_target_value targeted recovery search parameter + + + + + Specifies the stopping point for targeted recovery. The string value + is interpreted according to strict rules according to the value of + recovery_target_type. An empty string may be an + invalid value in some cases. + + + + recovery_target_inclusive (boolean) @@ -357,34 +426,21 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows Standby Server Settings - - standby_mode (boolean) - - standby_mode recovery parameter - - - - - Specifies whether to start the PostgreSQL server as - a standby. If this parameter is on, the server will - not stop recovery when the end of archived WAL is reached, but - will keep trying to continue recovery by fetching new WAL segments - using restore_command - and/or by connecting to the primary server as specified by the - primary_conninfo setting. - - - - - primary_conninfo (string) + + Parameter settings may be changed only at server start, though later + patches may add this capability. + + + + sender_conninfo (string) - primary_conninfo recovery parameter + sender_conninfo recovery parameter Specifies a connection string to be used for the standby server - to connect with the primary. This string is in the format + to connect with a sending server. This string is in the format described in . If any option is unspecified in this string, then the corresponding environment variable (see ) is checked. If the @@ -393,56 +449,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows The connection string should specify the host name (or address) - of the primary server, as well as the port number if it is not + of the sending server, as well as the port number if it is not the same as the standby server's default. Also specify a user name corresponding to a suitably-privileged role - on the primary (see + on the sending server (see ). - A password needs to be provided too, if the primary demands password + A password needs to be provided too, if the sender demands password authentication. It can be provided in the - primary_conninfo string, or in a separate + sender_conninfo string, or in a separate ~/.pgpass file on the standby server (use replication as the database name). Do not specify a database name in the - primary_conninfo string. + sender_conninfo string. This setting has no effect if standby_mode is off. - - primary_slot_name (string) + + + sender_slot_name (string) - primary_slot_name recovery parameter + sender_slot_name recovery parameter Optionally specifies an existing replication slot to be used when - connecting to the primary via streaming replication to control + connecting to the sending server via streaming replication to control resource removal on the upstream node (see ). - This setting has no effect if primary_conninfo is not + This setting has no effect if sender_conninfo is not set. - - trigger_file (string) - - trigger_file recovery parameter - - - - - Specifies a trigger file whose presence ends recovery in the - standby. Even if this value is not set, you can still promote - the standby using pg_ctl promote. - This setting has no effect if standby_mode is off. - - - recovery_min_apply_delay (integer) @@ -453,7 +495,7 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows By default, a standby server restores WAL records from the - primary as soon as possible. It may be useful to have a time-delayed + sending server as soon as possible. It may be useful to have a time-delayed copy of the data, offering opportunities to correct data loss errors. This parameter allows you to delay recovery by a fixed period of time, measured in milliseconds if no unit is specified. For example, if diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f8ffa5c..e4168c2 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -76,11 +76,13 @@ extern uint32 bootstrap_data_checksum_version; -/* File path names (all relative to $PGDATA) */ +/* File path names */ #define RECOVERY_COMMAND_FILE "recovery.conf" #define RECOVERY_COMMAND_DONE "recovery.done" -#define PROMOTE_SIGNAL_FILE "promote" -#define FALLBACK_PROMOTE_SIGNAL_FILE "fallback_promote" +#define PROMOTE_SIGNAL_FILE "promote.signal" +#define FALLBACK_PROMOTE_SIGNAL_FILE "fallback_promote.signal" +#define RECOVERY_SIGNAL_FILE "recovery.signal" +#define STANDBY_SIGNAL_FILE "standby.signal" /* User-settable parameters */ @@ -241,29 +243,35 @@ 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; /* options 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 *recoveryRestoreCommand = NULL; +char *recoveryEndCommand = NULL; +char *archiveCleanupCommand = NULL; +char *recoveryTargetTypeString; +RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET; +char *recoveryTargetValue = NULL; +bool recoveryTargetInclusive = true; +RecoveryTargetAction 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; +bool StandbyModeRequested = false; + +char *signal_file_directory = NULL; +char PromoteSignalFile[MAXPGPATH]; +char RecoverySignalFile[MAXPGPATH]; +char StandbySignalFile[MAXPGPATH]; /* are we currently in standby mode? */ bool StandbyMode = false; @@ -289,7 +297,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'? * @@ -305,7 +317,10 @@ static bool recoveryStopAfter; * file was created.) During a sequential scan we do not allow this value * to decrease. */ -static TimeLineID recoveryTargetTLI; +char *recoveryTargetTLIString = NULL; +RecoveryTargetTimeLineGoal recoveryTargetTimeLineGoal = RECOVERY_TARGET_TIMELINE_CONTROLFILE; +TimeLineID recoveryTargetTLIRequested = 0; +TimeLineID recoveryTargetTLI = 0; static bool recoveryTargetIsLatest = false; static List *expectedTLEs; static TimeLineID curFileTLI; @@ -801,7 +816,8 @@ static bool holdingAllLocks = false; static MemoryContext walDebugCxt = NULL; #endif -static void readRecoveryCommandFile(void); +static void readRecoverySignalFile(void); +static void logRecoveryParameters(void); static void exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog); static bool recoveryStopsBefore(XLogReaderState *record); static bool recoveryStopsAfter(XLogReaderState *record); @@ -4963,261 +4979,170 @@ 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. + * throw an ERROR. * - * The file is parsed using the main configuration parser. + * See if there are any recovery signal files and if so, set state for + * recovery. */ 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; + if (IsBootstrapProcessingMode()) + return; - fd = AllocateFile(RECOVERY_COMMAND_FILE, "r"); - if (fd == NULL) + /* + * Set paths for named signal files + */ + if (signal_file_directory) { - if (errno == ENOENT) - return; /* not there, so no archive recovery */ + snprintf(StandbySignalFile, MAXPGPATH, "%s/%s", signal_file_directory, STANDBY_SIGNAL_FILE); + snprintf(RecoverySignalFile, MAXPGPATH, "%s/%s", signal_file_directory, RECOVERY_SIGNAL_FILE); + snprintf(PromoteSignalFile, MAXPGPATH, "%s/%s", signal_file_directory, PROMOTE_SIGNAL_FILE); + } + else + { + snprintf(StandbySignalFile, MAXPGPATH, "%s", STANDBY_SIGNAL_FILE); + snprintf(RecoverySignalFile, MAXPGPATH, "%s", RECOVERY_SIGNAL_FILE); + snprintf(PromoteSignalFile, MAXPGPATH, "%s", PROMOTE_SIGNAL_FILE); + } + + /* + * Check for old recovery API file: recovery.conf + */ + if (stat(RECOVERY_COMMAND_FILE, &stat_buf) == 0) ereport(FATAL, - (errcode_for_file_access(), - errmsg("could not open recovery command file \"%s\": %m", + (errcode_for_file_access(), + errmsg("deprecated API using recovery command file \"%s\"", RECOVERY_COMMAND_FILE))); + /* + * Remove unused .done file, if present. Ignore if absent. + */ + unlink(RECOVERY_COMMAND_DONE); + + /* + * Check for recovery signal files. + * + * If present, standby signal file takes precedence. + * If neither is present then we won't enter archive recovery. + */ + if (stat(StandbySignalFile, &stat_buf) == 0) + { + standby_signal_file_found = true; + StandbyModeRequested = true; + ArchiveRecoveryRequested = true; + } + else if (stat(RecoverySignalFile, &stat_buf) == 0) + { + recovery_signal_file_found = true; + StandbyModeRequested = false; + ArchiveRecoveryRequested = true; } + else + return; /* - * Since we're asking ParseConfigFp() to report errors as FATAL, there's - * no need to check the return value. + * We don't support standby_mode in standalone backends; that requires + * other processes such as the WAL receiver to be alive. */ - (void) ParseConfigFp(fd, RECOVERY_COMMAND_FILE, 0, FATAL, &head, &tail); + if (StandbyModeRequested && !IsUnderPostmaster) + ereport(FATAL, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("standby mode is not supported by single-user servers"))); - FreeFile(fd); + logRecoveryParameters(); + validateRecoveryParameters(); +} - for (item = head; item; item = item->next) - { - 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\"."))); +static void +logRecoveryParameters(void) +{ + int normal_log_level = LOG; //DEBUG2; - ereport(DEBUG2, - (errmsg_internal("recovery_target_action = '%s'", - item->value))); + /* + * Log messages for recovery parameters at server start + */ + ereport(normal_log_level, + (errmsg_internal("standby_mode = '%s'", (StandbyModeRequested ? "on " : "off")))); - 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 (recoveryRestoreCommand != NULL) + ereport(normal_log_level, + (errmsg_internal("restore_command = '%s'", recoveryRestoreCommand))); - /* - * 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; + if (recoveryEndCommand != NULL) + ereport(normal_log_level, + (errmsg_internal("recovery_end_command = '%s'", recoveryRestoreCommand))); - 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))); + if (archiveCleanupCommand != NULL) + ereport(normal_log_level, + (errmsg_internal("archive_cleanup_command = '%s'", archiveCleanupCommand))); - ereport(DEBUG2, - (errmsg_internal("recovery_target_name = '%s'", - recoveryTargetName))); - } - else if (strcmp(item->name, "recovery_target_lsn") == 0) - { - recoveryTarget = RECOVERY_TARGET_LSN; + if (SenderConnInfo != NULL) + ereport(normal_log_level, + (errmsg_internal("sender_conninfo = '%s'", SenderConnInfo))); - /* - * 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) + if (SenderSlotName != NULL) + ereport(normal_log_level, + (errmsg_internal("sender_slot_name = '%s'", SenderSlotName))); + + if (recovery_min_apply_delay > 0) + ereport(normal_log_level, + (errmsg_internal("recovery_min_apply_delay = '%u'", recovery_min_apply_delay))); + + /* + * Check details for recovery target, if any + */ + if (recoveryTarget > RECOVERY_TARGET_UNSET) + { + ereport(normal_log_level, + (errmsg_internal("recovery_target_type = '%s'", RecoveryTargetText(recoveryTarget)))); + if (recoveryTargetValue != NULL) + ereport(normal_log_level, + (errmsg_internal("recovery_target_value = '%s'", recoveryTargetValue))); + ereport(normal_log_level, + (errmsg_internal("recovery_target_inclusive = '%s'", (recoveryTargetInclusive ? "on " : "off")))); + + if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_CONTROLFILE) + ereport(normal_log_level, + (errmsg_internal("recovery_target_timeline = '%u' (from controlfile)", + recoveryTargetTLI))); + else if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST) + ereport(normal_log_level, + (errmsg_internal("recovery_target_timeline = 'latest'"))); + else { - TriggerFile = pstrdup(item->value); - ereport(DEBUG2, - (errmsg_internal("trigger_file = '%s'", - TriggerFile))); + Assert(recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC); + ereport(normal_log_level, + (errmsg_internal("recovery_target_timeline = '%u'", + recoveryTargetTLIRequested))); } - 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(normal_log_level, + (errmsg_internal("recovery_target_action = '%s'", RecoveryTargetActionText(recoveryTargetAction)))); +} + +void +validateRecoveryParameters(void) +{ + if (!ArchiveRecoveryRequested) + return; + + if (recoveryTarget > RECOVERY_TARGET_UNSET && + recoveryTargetValue == NULL) ereport(FATAL, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized recovery parameter \"%s\"", - item->name))); - } + errmsg("must specify recovery_target_value when recovery_target_type is set"))); /* * Check for compulsory parameters */ if (StandbyModeRequested) { - if (PrimaryConnInfo == NULL && recoveryRestoreCommand == NULL) + if (SenderConnInfo == NULL && recoveryRestoreCommand == NULL) 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 @@ -5225,8 +5150,7 @@ readRecoveryCommandFile(void) if (recoveryRestoreCommand == NULL) 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"))); } /* @@ -5235,50 +5159,46 @@ 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; + + /* + * The user has requested a specific tli. This might be the latest + * timeline but we don't know that; the point here is that we do not + * allow the recoveryTargetTLI to follow any changes. + */ + recoveryTargetIsLatest = false; + } + else if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST) + { + /* We start the "latest" search from pg_control's timeline */ + recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI); + recoveryTargetIsLatest = true; + } + else + { + /* else we just use the recoveryTargetTLI as already read from ControlFile */ + Assert(recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_CONTROLFILE); + recoveryTargetIsLatest = false; + } } /* @@ -5379,11 +5299,19 @@ exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog) unlink(recoveryPath); /* ignore any error */ /* - * Rename the config file out of the way, so that we don't accidentally + * Rename 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 (unlink(StandbySignalFile) != 0 && standby_signal_file_found) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", + StandbySignalFile))); + if (unlink(RecoverySignalFile) != 0 && recovery_signal_file_found) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", + RecoverySignalFile))); ereport(LOG, (errmsg("archive recovery complete"))); @@ -6114,10 +6042,9 @@ 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(); + readRecoverySignalFile(); /* * Save archive_cleanup_command in shared memory so that other processes @@ -11383,7 +11310,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 (SenderConnInfo) { XLogRecPtr ptr; TimeLineID tli; @@ -11404,8 +11331,8 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, tli, curFileTLI); } curFileTLI = tli; - RequestXLogStreaming(tli, ptr, PrimaryConnInfo, - PrimarySlotName); + RequestXLogStreaming(tli, ptr, SenderConnInfo, + SenderSlotName); receivedUpto = 0; } @@ -11733,14 +11660,15 @@ CheckForStandbyTrigger(void) return true; } - if (TriggerFile == NULL) - return false; - - if (stat(TriggerFile, &stat_buf) == 0) + /* + * Check for PromoteSignalFile... this is now a constant filename, with only the + * filepath varying, so a user can still specify what they need. + */ + if (stat(PromoteSignalFile, &stat_buf) == 0) { ereport(LOG, - (errmsg("trigger file found: %s", TriggerFile))); - unlink(TriggerFile); + (errmsg("promote signal file found: %s", PromoteSignalFile))); + unlink(PromoteSignalFile); triggered = true; fast_promote = true; return true; @@ -11748,8 +11676,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 signal file \"%s\": %m", + PromoteSignalFile))); return false; } diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index bc7253f..f23e131 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -328,10 +328,11 @@ pg_create_restore_point(PG_FUNCTION_ARGS) restore_name_str = text_to_cstring(restore_name); - if (strlen(restore_name_str) >= MAXFNAMELEN) + if (strlen(restore_name_str) >= MAXRESTOREPOINTNAMELEN) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("value too long for restore point (maximum %d characters)", MAXFNAMELEN - 1))); + errmsg("value too long for restore point (maximum %d characters)", + MAXRESTOREPOINTNAMELEN - 1))); restorepoint = XLogRestorePoint(restore_name_str); diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c index a7ae7e3..74b72c9 100644 --- a/src/backend/postmaster/startup.c +++ b/src/backend/postmaster/startup.c @@ -153,6 +153,7 @@ HandleStartupProcInterrupts(void) { got_SIGHUP = false; ProcessConfigFile(PGC_SIGHUP); + validateRecoveryParameters(); } /* diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c index cc3cf7d..01f62b5 100644 --- a/src/backend/replication/walreceiver.c +++ b/src/backend/replication/walreceiver.c @@ -70,9 +70,11 @@ /* GUC variables */ -int wal_receiver_status_interval; -int wal_receiver_timeout; -bool hot_standby_feedback; +int wal_receiver_status_interval; +int wal_receiver_timeout; +bool hot_standby_feedback; +char *SenderConnInfo; +char *SenderSlotName; /* libpqwalreceiver connection */ static WalReceiverConn *wrconn = NULL; @@ -415,9 +417,33 @@ WalReceiverMain(void) if (got_SIGHUP) { + char *conninfo = pstrdup(SenderConnInfo); + char *slotname = pstrdup(SenderSlotName); + got_SIGHUP = false; ProcessConfigFile(PGC_SIGHUP); XLogWalRcvSendHSFeedback(true); + + /* + * If sender_conninfo has been changed while walreceiver is running, + * shut down walreceiver so that a new walreceiver is started and + * initiates replication with the new connection information. + */ + if (strcmp(conninfo, SenderConnInfo) != 0) + ereport(FATAL, + (errcode(ERRCODE_ADMIN_SHUTDOWN), + errmsg("closing replication connection because sender_conninfo was changed"))); + + /* + * And the same for sender_slot_name. + */ + if (strcmp(slotname, SenderSlotName) != 0) + ereport(FATAL, + (errcode(ERRCODE_ADMIN_SHUTDOWN), + errmsg("closing replication connection because sender_slot_name was changed"))); + + pfree(conninfo); + pfree(slotname); } /* See if we can read data immediately */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 946ba9e..b560e48 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -31,6 +31,7 @@ #include "access/transam.h" #include "access/twophase.h" #include "access/xact.h" +#include "access/xlog.h" #include "catalog/namespace.h" #include "commands/async.h" #include "commands/prepare.h" @@ -77,6 +78,7 @@ #include "utils/guc_tables.h" #include "utils/memutils.h" #include "utils/pg_locale.h" +#include "utils/pg_lsn.h" #include "utils/plancache.h" #include "utils/portal.h" #include "utils/ps_status.h" @@ -182,6 +184,15 @@ static void assign_application_name(const char *newval, void *extra); static bool check_cluster_name(char **newval, void **extra, GucSource source); static const char *show_unix_socket_permissions(void); static const char *show_log_file_mode(void); +static bool check_recovery_target_type(char **newval, void **extra, GucSource source); +static void assign_recovery_target_type(const char *newval, void *extra); +static bool check_recovery_target_value(char **newval, void **extra, GucSource source); +static void assign_recovery_target_value(const char *newval, void *extra); +static bool check_recovery_target_timeline(char **newval, void **extra, GucSource source); +static void assign_recovery_target_timeline(const char *newval, void *extra); +static bool check_recovery_target_action(char **newval, void **extra, GucSource source); +static void assign_recovery_target_action(const char *newval, void *extra); +static bool check_sender_slot_name(char **newval, void **extra, GucSource source); /* Private functions in guc-file.l that need to be called from guc.c */ static ConfigVariable *ProcessConfigFileInternal(GucContext context, @@ -459,6 +470,9 @@ char *IdentFileName; char *external_pid_file; char *pgstat_temp_directory; +char *signal_file_directory; +char *recovery_target_timeline_string; +char *recovery_target_action_string; char *application_name; @@ -592,6 +606,10 @@ const char *const config_group_names[] = gettext_noop("Write-Ahead Log / Checkpoints"), /* WAL_ARCHIVING */ gettext_noop("Write-Ahead Log / Archiving"), + /* WAL_ARCHIVE_RECOVERY */ + gettext_noop("Write-Ahead Log / Archive Recovery"), + /* WAL_RECOVERY_TARGET */ + gettext_noop("Write-Ahead Log / Recovery Target"), /* REPLICATION */ gettext_noop("Replication"), /* REPLICATION_SENDING */ @@ -1549,6 +1567,16 @@ static struct config_bool ConfigureNamesBool[] = }, { + {"recovery_target_inclusive", PGC_SIGHUP, WAL_RECOVERY_TARGET, + gettext_noop("Sets whether to include or exclude transaction with recovery target."), + NULL + }, + &recoveryTargetInclusive, + true, + NULL, NULL, NULL + }, + + { {"hot_standby", PGC_POSTMASTER, REPLICATION_STANDBY, gettext_noop("Allows connections and queries during recovery."), NULL @@ -1789,8 +1817,19 @@ static struct config_int ConfigureNamesInt[] = }, { + {"recovery_min_apply_delay", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the minimum delay to apply changes during recovery."), + NULL, + GUC_UNIT_MS + }, + &recovery_min_apply_delay, + 0, 0, INT_MAX, + NULL, NULL, NULL + }, + + { {"wal_receiver_status_interval", PGC_SIGHUP, REPLICATION_STANDBY, - gettext_noop("Sets the maximum interval between WAL receiver status reports to the primary."), + gettext_noop("Sets the maximum interval between WAL receiver status reports to the sending server."), NULL, GUC_UNIT_S }, @@ -1801,7 +1840,7 @@ static struct config_int ConfigureNamesInt[] = { {"wal_receiver_timeout", PGC_SIGHUP, REPLICATION_STANDBY, - gettext_noop("Sets the maximum wait time to receive data from the primary."), + gettext_noop("Sets the maximum wait time to receive data from the sending server."), NULL, GUC_UNIT_MS }, @@ -2996,6 +3035,98 @@ static struct config_string ConfigureNamesString[] = }, { + {"restore_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY, + gettext_noop("Sets the shell command that will retrieve an archived WAL file."), + NULL + }, + &recoveryRestoreCommand, + "", + NULL, NULL, NULL + }, + + { + {"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY, + gettext_noop("Sets the shell command that will be executed at every restartpoint."), + NULL + }, + &archiveCleanupCommand, + "", + NULL, NULL, NULL + }, + + { + {"recovery_end_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY, + gettext_noop("Sets the shell command that will be executed once only at the end of recovery."), + NULL + }, + &recoveryEndCommand, + "", + NULL, NULL, NULL + }, + + { + {"recovery_target_type", PGC_SIGHUP, WAL_RECOVERY_TARGET, + gettext_noop("Sets the type of desired recovery target."), + NULL + }, + &recoveryTargetTypeString, + "", + check_recovery_target_type, assign_recovery_target_type, NULL + }, + + { + {"recovery_target_value", PGC_SIGHUP, WAL_RECOVERY_TARGET, + gettext_noop("Sets the value of the recovery taregt up to which recovery will proceed."), + NULL + }, + &recoveryTargetValue, + "", + check_recovery_target_value, assign_recovery_target_value, NULL + }, + + { + {"recovery_target_timeline", PGC_SIGHUP, WAL_RECOVERY_TARGET, + gettext_noop("Sets recovering into a particular timeline."), + NULL + }, + &recovery_target_timeline_string, + "", + check_recovery_target_timeline, assign_recovery_target_timeline, NULL + }, + + { + {"recovery_target_action", PGC_SIGHUP, WAL_RECOVERY_TARGET, + gettext_noop("Sets the action to perform upon reaching the recovery target."), + NULL + }, + &recovery_target_action_string, + "", + check_recovery_target_action, assign_recovery_target_action, NULL + }, + + { + {"sender_conninfo", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the connection string to be used to connect with the sending server."), + NULL, + GUC_SUPERUSER_ONLY + }, + &SenderConnInfo, + "", + NULL, NULL, NULL + }, + + { + {"sender_slot_name", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the name of the replication slot to use on the sending server."), + NULL, + GUC_SUPERUSER_ONLY + }, + &SenderSlotName, + "", + check_sender_slot_name, NULL, NULL + }, + + { {"client_encoding", PGC_USERSET, CLIENT_CONN_LOCALE, gettext_noop("Sets the client's character set encoding."), NULL, @@ -3475,6 +3606,17 @@ static struct config_string ConfigureNamesString[] = }, { + {"signal_file_directory", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY, + gettext_noop("Signal files may be written to the specified directory."), + NULL, + GUC_SUPERUSER_ONLY + }, + &signal_file_directory, + NULL, + check_canonical_path, NULL, NULL + }, + + { {"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR, gettext_noop("Writes temporary statistics files to the specified directory."), NULL, @@ -10335,4 +10477,301 @@ show_log_file_mode(void) return buf; } +static bool +check_recovery_target_type(char **newval, void **extra, GucSource source) +{ + RecoveryTargetType *myextra; + RecoveryTargetType rt = RECOVERY_TARGET_UNSET; + + if (strcmp(*newval, "xid") == 0) + rt = RECOVERY_TARGET_XID; + else if (strcmp(*newval, "timestamp") == 0) + rt = RECOVERY_TARGET_TIME; + else if (strcmp(*newval, "name") == 0) + rt = RECOVERY_TARGET_NAME; + else if (strcmp(*newval, "lsn") == 0) + rt = RECOVERY_TARGET_LSN; + else if (strcmp(*newval, "immediate") == 0) + rt = RECOVERY_TARGET_IMMEDIATE; + else if (strcmp(*newval, "") != 0) + { + GUC_check_errdetail("recovery_target_type is not valid: \"%s\"", *newval); + return false; + } + + myextra = (RecoveryTargetType *) guc_malloc(ERROR, sizeof(RecoveryTargetType)); + *myextra = rt; + *extra = (void *) myextra; + + return true; +} + +static void +assign_recovery_target_type(const char *newval, void *extra) +{ + recoveryTarget = *((RecoveryTargetType *) extra); +} + +static bool +check_recovery_target_value(char **newval, void **extra, GucSource source) +{ + bool valid = false; + + /* + * Value must be present in some cases, must not be present in others + */ + if (strcmp(*newval, "") == 0) + { + if (recoveryTarget == RECOVERY_TARGET_UNSET || + recoveryTarget == RECOVERY_TARGET_IMMEDIATE) + valid = true; + } + else + { + if (recoveryTarget == RECOVERY_TARGET_UNSET || + recoveryTarget == RECOVERY_TARGET_IMMEDIATE) + valid = false; + else + valid = true; + } + + if (!valid) + { + GUC_check_errdetail("recovery_target_value is not valid: \"%s\"", *newval); + return false; + } + + /* + * We assume that recovery_target_type has already been parsed + * since it sorts alphabetically before recovery_target_value. + */ + switch (recoveryTarget) + { + case RECOVERY_TARGET_UNSET: + case RECOVERY_TARGET_IMMEDIATE: + /* No value, so do nothing */ + break; + + case RECOVERY_TARGET_XID: + { + TransactionId xid; + TransactionId *myextra; + + errno = 0; + xid = (TransactionId) strtoul(*newval, NULL, 0); + if (errno == EINVAL || errno == ERANGE) + { + GUC_check_errdetail("recovery_target_value is not a valid number: \"%s\"", + *newval); + return false; + } + + myextra = (TransactionId *) guc_malloc(ERROR, sizeof(TransactionId)); + *myextra = xid; + *extra = (void *) myextra; + } + break; + + case RECOVERY_TARGET_TIME: + { + TimestampTz time; + TimestampTz *myextra; + MemoryContext oldcontext = CurrentMemoryContext; + + PG_TRY(); + { + time = DatumGetTimestampTz(DirectFunctionCall3(timestamptz_in, + CStringGetDatum(*newval), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1))); + } + PG_CATCH(); + { + ErrorData *edata; + + /* Save error info */ + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* Pass the error message */ + GUC_check_errdetail("%s", edata->message); + FreeErrorData(edata); + return false; + } + PG_END_TRY(); + + myextra = (TimestampTz *) guc_malloc(ERROR, sizeof(TimestampTz)); + *myextra = time; + *extra = (void *) myextra; + } + break; + + case RECOVERY_TARGET_NAME: + /* Use the value of newval directly */ + if (strlen(*newval) > MAXRESTOREPOINTNAMELEN) + { + GUC_check_errdetail("recovery_target_value is too long (maximum %d characters)", + MAXRESTOREPOINTNAMELEN); + return false; + } + break; + + case RECOVERY_TARGET_LSN: + { + XLogRecPtr lsn; + XLogRecPtr *myextra; + MemoryContext oldcontext = CurrentMemoryContext; + + /* + * Convert the LSN string given by the user to XLogRecPtr form. + */ + PG_TRY(); + { + lsn = + DatumGetLSN(DirectFunctionCall3(pg_lsn_in, + CStringGetDatum(*newval), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1))); + } + PG_CATCH(); + { + ErrorData *edata; + + /* Save error info */ + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* Pass the error message */ + GUC_check_errdetail("%s", edata->message); + FreeErrorData(edata); + return false; + } + PG_END_TRY(); + + myextra = (XLogRecPtr *) guc_malloc(ERROR, sizeof(XLogRecPtr)); + *myextra = lsn; + *extra = (void *) myextra; + } + break; + } + + return true; +} + +static void +assign_recovery_target_value(const char *newval, void *extra) +{ + switch (recoveryTarget) + { + case RECOVERY_TARGET_UNSET: + case RECOVERY_TARGET_IMMEDIATE: + break; + + case RECOVERY_TARGET_XID: + recoveryTargetXid = *((TransactionId *) extra); + break; + + case RECOVERY_TARGET_TIME: + recoveryTargetTime = *((TimestampTz *) extra); + break; + + case RECOVERY_TARGET_NAME: + if (newval && *newval) + recoveryTargetName = (char *) newval; + break; + + case RECOVERY_TARGET_LSN: + recoveryTargetLSN = *((XLogRecPtr *) extra); + break; + } +} + +static bool +check_recovery_target_timeline(char **newval, void **extra, GucSource source) +{ + RecoveryTargetTimeLineGoal rttg = RECOVERY_TARGET_TIMELINE_CONTROLFILE; + RecoveryTargetTimeLineGoal *myextra; + + if (strcmp(*newval, "latest") == 0) + rttg = RECOVERY_TARGET_TIMELINE_LATEST; + else if (strcmp(*newval, "controlfile") == 0 || strcmp(*newval, "") == 0) + rttg = RECOVERY_TARGET_TIMELINE_CONTROLFILE; + else + { + TimeLineID tli; + + errno = 0; + tli = (TimeLineID) strtoul(*newval, NULL, 0); + if (errno == EINVAL || errno == ERANGE) + { + GUC_check_errdetail("recovery_target_timeline is not a valid number: \"%s\"", + *newval); + return false; + } + rttg = RECOVERY_TARGET_TIMELINE_NUMERIC; + } + + myextra = (TimeLineID *) guc_malloc(ERROR, sizeof(TimeLineID)); + *myextra = rttg; + *extra = (void *) myextra; + + return true; +} + +static void +assign_recovery_target_timeline(const char *newval, void *extra) +{ + recoveryTargetTimeLineGoal = *((TimeLineID *) extra); + if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC) + recoveryTargetTLIRequested = (TimeLineID) strtoul(newval, NULL, 0); + else + recoveryTargetTLIRequested = 0; +} + +static bool +check_recovery_target_action(char **newval, void **extra, GucSource source) +{ + RecoveryTargetAction rta = RECOVERY_TARGET_ACTION_PAUSE; + RecoveryTargetAction *myextra; + + if (strcmp(*newval, "pause") == 0) + rta = RECOVERY_TARGET_ACTION_PAUSE; + else if (strcmp(*newval, "promote") == 0) + rta = RECOVERY_TARGET_ACTION_PROMOTE; + else if (strcmp(*newval, "shutdown") == 0) + rta = RECOVERY_TARGET_ACTION_SHUTDOWN; + else if (strcmp(*newval, "") != 0) + { + GUC_check_errdetail("recovery_target_action is not valid: \"%s\"", *newval); + return false; + } + + myextra = (RecoveryTargetAction *) guc_malloc(ERROR, sizeof(RecoveryTargetAction)); + *myextra = rta; + *extra = (void *) myextra; + + return true; +} + +static void +assign_recovery_target_action(const char *newval, void *extra) +{ + recoveryTargetAction = *((RecoveryTargetAction *) extra); +} + +static bool +check_sender_slot_name(char **newval, void **extra, GucSource source) +{ + if (strcmp(*newval,"") != 0 && + !ReplicationSlotValidateName(*newval, WARNING)) + { + GUC_check_errdetail("sender_slot_name is not valid: \"%s\"", *newval); + return false; + } + + return true; +} + #include "guc-file.c" diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index ee8232f..2ee3bca 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -45,6 +45,9 @@ #ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file # (change requires restart) +#signal_file_directory = 'ConfigDir' # signal files for recovery/replication + # (change requires restart) + # If external_pid_file is not explicitly set, no extra PID file is written. #external_pid_file = '' # write an extra PID file # (change requires restart) @@ -221,6 +224,26 @@ #archive_timeout = 0 # force a logfile segment switch after this # number of seconds; 0 disables +# - Archive Recovery - +#restore_command = '' # command to use to restore an archived logfile segment + # placeholders: %p = path of file to restore + # %f = file name only + # e.g. 'cp /mnt/server/archivedir/%f %p' +#archive_cleanup_command = '' # command to execute at every restartpoint +#recovery_end_command = '' # command to execute at completion of recovery + +# - Recovery Target - + +# Set these only when performing a targeted recovery + +#recovery_target_type='' # 'xid', 'time', 'name', 'lsn', + # or 'immediate' +#recovery_target_value='' # value interpreted according to type +#recovery_target_inclusive = on +#recovery_target_timeline = '' # unset means read from controlfile (default), + # or set to 'latest' or timeline ID +#recovery_target_action = '' # 'pause', 'promote', 'shutdown' + #------------------------------------------------------------------------------ # REPLICATION @@ -254,6 +277,8 @@ # These settings are ignored on a master server. +#sender_conninfo = '' # connection string on sending server +#sender_slot_name = '' # connection slot on sending server #hot_standby = off # "on" allows queries during recovery # (change requires restart) #max_standby_archive_delay = 30s # max delay before canceling queries diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 7d21408..8b6dfca 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -87,6 +87,41 @@ typedef enum RECOVERY_TARGET_IMMEDIATE } RecoveryTargetType; +#define RecoveryTargetText(t) ( \ + t == RECOVERY_TARGET_UNSET ? "unset" : ( \ + t == RECOVERY_TARGET_XID ? "xid" : ( \ + t == RECOVERY_TARGET_TIME ? "timestamp" : ( \ + t == RECOVERY_TARGET_NAME ? "name" : ( \ + t == RECOVERY_TARGET_LSN ? "lsn" : \ + "immediate" ))))) + +/* + * Recovery target action. + */ +typedef enum +{ + RECOVERY_TARGET_ACTION_PAUSE, + RECOVERY_TARGET_ACTION_PROMOTE, + RECOVERY_TARGET_ACTION_SHUTDOWN +} RecoveryTargetAction; + +#define RecoveryTargetActionText(t) ( \ + t == RECOVERY_TARGET_ACTION_PAUSE ? "pause" : ( \ + t == RECOVERY_TARGET_ACTION_PROMOTE ? "promote" : ( \ + "shutdown" ))) +/* + * Recovery target TimeLine goal + */ +typedef enum +{ + RECOVERY_TARGET_TIMELINE_CONTROLFILE, + RECOVERY_TARGET_TIMELINE_LATEST, + RECOVERY_TARGET_TIMELINE_NUMERIC +} RecoveryTargetTimeLineGoal; + +/* Max length of named restore points */ +#define MAXRESTOREPOINTNAMELEN 64 + extern XLogRecPtr ProcLastRecPtr; extern XLogRecPtr XactLastRecEnd; extern PGDLLIMPORT XLogRecPtr XactLastCommitEnd; @@ -109,6 +144,36 @@ extern bool log_checkpoints; extern int CheckPointSegments; +/* options previously taken from recovery.conf for archive recovery */ +extern char *recoveryRestoreCommand; +extern char *recoveryEndCommand; +extern char *archiveCleanupCommand; +extern char *recoveryTargetTypeString; +extern RecoveryTargetType recoveryTarget; +extern char *recoveryTargetValue; +extern bool recoveryTargetInclusive; +extern RecoveryTargetAction recoveryTargetAction; +extern TransactionId recoveryTargetXid; +extern TimestampTz recoveryTargetTime; +extern char *recoveryTargetName; +extern XLogRecPtr recoveryTargetLSN; +extern int recovery_min_apply_delay; + +/* option set locally in Startup process only when signal files exist */ +extern bool StandbyModeRequested; +extern bool StandbyMode; + +/* options for WALreceiver.c */ +extern char *SenderConnInfo; +extern char *SenderSlotName; + +extern char *signal_file_directory; + +extern char *recoveryTargetTLIString; +extern RecoveryTargetTimeLineGoal recoveryTargetTimeLineGoal; +extern TimeLineID recoveryTargetTLIRequested; +extern TimeLineID recoveryTargetTLI; + /* Archive modes */ typedef enum ArchiveMode { @@ -238,6 +303,7 @@ extern const char *xlog_identify(uint8 info); extern void issue_xlog_fsync(int fd, XLogSegNo segno); +extern void validateRecoveryParameters(void); extern bool RecoveryInProgress(void); extern bool HotStandbyActive(void); extern bool HotStandbyActiveInReplay(void); diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h index 05f996b..1a80377 100644 --- a/src/include/access/xlog_internal.h +++ b/src/include/access/xlog_internal.h @@ -246,16 +246,6 @@ typedef struct XLogRecData } XLogRecData; /* - * Recovery target action. - */ -typedef enum -{ - RECOVERY_TARGET_ACTION_PAUSE, - RECOVERY_TARGET_ACTION_PROMOTE, - RECOVERY_TARGET_ACTION_SHUTDOWN -} RecoveryTargetAction; - -/* * Method table for resource managers. * * This struct must be kept in sync with the PG_RMGR definition in diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index b695f61..7f4389c 100644 --- a/src/include/utils/guc_tables.h +++ b/src/include/utils/guc_tables.h @@ -68,6 +68,8 @@ enum config_group WAL_SETTINGS, WAL_CHECKPOINTS, WAL_ARCHIVING, + WAL_ARCHIVE_RECOVERY, + WAL_RECOVERY_TARGET, REPLICATION, REPLICATION_SENDING, REPLICATION_MASTER, diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm index c1b16ca..60ad78e 100644 --- a/src/test/perl/PostgresNode.pm +++ b/src/test/perl/PostgresNode.pm @@ -574,8 +574,6 @@ of a backup previously created on that node with $node->backup. Does not start the node after initializing it. -A recovery.conf is not created. - pg_hba.conf is configured to allow replication connections. Pass the keyword parameter hba_permit_replication => 0 to disable this. @@ -745,13 +743,16 @@ sub enable_streaming my ($self, $root_node) = @_; my $root_connstr = $root_node->connstr; my $name = $self->name; + my $pgdata = $self->data_dir; print "### Enabling streaming replication for node \"$name\"\n"; $self->append_conf( - 'recovery.conf', qq( -primary_conninfo='$root_connstr application_name=$name' -standby_mode=on + 'postgresql.conf', qq( +sender_conninfo='$root_connstr application_name=$name' )); + open my $standbysignal, ">>$pgdata/standby.signal"; + print $standbysignal "\n# Allow replication (set up by PostgresNode.pm)\n"; + close $standbysignal; } # Internal routine to enable archive recovery command on a standby node @@ -760,6 +761,7 @@ sub enable_restoring my ($self, $root_node) = @_; my $path = $root_node->archive_dir; my $name = $self->name; + my $pgdata = $self->data_dir; print "### Enabling WAL restore for node \"$name\"\n"; @@ -776,10 +778,12 @@ sub enable_restoring : qq{cp "$path/%f" "%p"}; $self->append_conf( - 'recovery.conf', qq( + 'postgresql.conf', qq( restore_command = '$copy_command' -standby_mode = on )); + open my $recoverysignal, ">>$pgdata/recovery.signal"; + print $recoverysignal "\n# Allow recovery (set up by PostgresNode.pm)\n"; + close $recoverysignal; } # Internal routine to enable archiving diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl index a82545b..236e651 100644 --- a/src/test/recovery/t/003_recovery_targets.pl +++ b/src/test/recovery/t/003_recovery_targets.pl @@ -3,7 +3,7 @@ use strict; use warnings; use PostgresNode; use TestLib; -use Test::More tests => 9; +use Test::More tests => 5; # Create and test a standby from given backup, with a certain # recovery target. @@ -23,7 +23,7 @@ sub test_recovery_standby foreach my $param_item (@$recovery_params) { $node_standby->append_conf( - 'recovery.conf', + 'postgresql.conf', qq($param_item )); } @@ -100,47 +100,20 @@ $node_master->safe_psql('postgres', $node_master->safe_psql('postgres', "SELECT pg_switch_xlog()"); # Test recovery targets -my @recovery_params = ("recovery_target = 'immediate'"); +my @recovery_params = ("recovery_target_type = 'immediate'"); test_recovery_standby('immediate target', 'standby_1', $node_master, \@recovery_params, "1000", $lsn1); -@recovery_params = ("recovery_target_xid = '$recovery_txid'"); +@recovery_params = ("recovery_target_type = 'xid'", "recovery_target_value = '$recovery_txid'"); test_recovery_standby('XID', 'standby_2', $node_master, \@recovery_params, "2000", $lsn2); -@recovery_params = ("recovery_target_time = '$recovery_time'"); +@recovery_params = ("recovery_target_type = 'timestamp'", "recovery_target_value = '$recovery_time'"); test_recovery_standby('time', 'standby_3', $node_master, \@recovery_params, "3000", $lsn3); -@recovery_params = ("recovery_target_name = '$recovery_name'"); +@recovery_params = ("recovery_target_type = 'name'", "recovery_target_value = '$recovery_name'"); test_recovery_standby('name', 'standby_4', $node_master, \@recovery_params, "4000", $lsn4); -@recovery_params = ("recovery_target_lsn = '$recovery_lsn'"); +@recovery_params = ("recovery_target_type = 'lsn'", "recovery_target_value = '$recovery_lsn'"); test_recovery_standby('LSN', 'standby_5', $node_master, \@recovery_params, "5000", $lsn5); -# Multiple targets -# Last entry has priority (note that an array respects the order of items -# not hashes). -@recovery_params = ( - "recovery_target_name = '$recovery_name'", - "recovery_target_xid = '$recovery_txid'", - "recovery_target_time = '$recovery_time'"); -test_recovery_standby('name + XID + time', - 'standby_6', $node_master, \@recovery_params, "3000", $lsn3); -@recovery_params = ( - "recovery_target_time = '$recovery_time'", - "recovery_target_name = '$recovery_name'", - "recovery_target_xid = '$recovery_txid'"); -test_recovery_standby('time + name + XID', - 'standby_7', $node_master, \@recovery_params, "2000", $lsn2); -@recovery_params = ( - "recovery_target_xid = '$recovery_txid'", - "recovery_target_time = '$recovery_time'", - "recovery_target_name = '$recovery_name'"); -test_recovery_standby('XID + time + name', - 'standby_8', $node_master, \@recovery_params, "4000", $lsn4); -@recovery_params = ( - "recovery_target_xid = '$recovery_txid'", - "recovery_target_time = '$recovery_time'", - "recovery_target_name = '$recovery_name'", - "recovery_target_lsn = '$recovery_lsn'",); -test_recovery_standby('XID + time + name + LSN', - 'standby_9', $node_master, \@recovery_params, "5000", $lsn5); +# Tests for multiple targets no longer needed from 10.0 onwards diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl index 5f3b2fe..d73ffa6 100644 --- a/src/test/recovery/t/004_timeline_switch.pl +++ b/src/test/recovery/t/004_timeline_switch.pl @@ -46,12 +46,10 @@ $node_master->teardown_node; $node_standby_1->promote; # Switch standby 2 to replay from standby 1 -rmtree($node_standby_2->data_dir . '/recovery.conf'); my $connstr_1 = $node_standby_1->connstr; $node_standby_2->append_conf( - 'recovery.conf', qq( -primary_conninfo='$connstr_1' -standby_mode=on + 'postgresql.conf', qq( +sender_conninfo='$connstr_1' recovery_target_timeline='latest' )); $node_standby_2->restart; diff --git a/src/test/recovery/t/005_replay_delay.pl b/src/test/recovery/t/005_replay_delay.pl index 640295b..ab87faf 100644 --- a/src/test/recovery/t/005_replay_delay.pl +++ b/src/test/recovery/t/005_replay_delay.pl @@ -25,7 +25,7 @@ my $delay = 3; $node_standby->init_from_backup($node_master, $backup_name, has_streaming => 1); $node_standby->append_conf( - 'recovery.conf', qq( + 'postgresql.conf', qq( recovery_min_apply_delay = '${delay}s' )); $node_standby->start;