diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml index a91864b..1841947 100644 --- a/doc/src/sgml/recovery-config.sgml +++ b/doc/src/sgml/recovery-config.sgml @@ -11,23 +11,44 @@ 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 data 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 primary_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 data 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. @@ -35,6 +56,13 @@ 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 +73,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 +182,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 + . + + + + 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 + . - - - - 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 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 +278,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,24 +394,11 @@ 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. - - - + + Parameter settings may be changed only at server start, though later + patches may add this capability. + + primary_conninfo (string) @@ -384,7 +408,7 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows 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,12 +417,12 @@ 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 ~/.pgpass file on the standby server (use @@ -411,6 +435,7 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows + primary_slot_name (string) @@ -420,7 +445,7 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows 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 @@ -428,21 +453,6 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows - - 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 +463,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 5016273..25a2092 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -80,8 +80,10 @@ extern uint32 bootstrap_data_checksum_version; /* File path names (all relative to $PGDATA) */ #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 */ @@ -233,7 +235,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. * @@ -245,6 +247,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; @@ -252,29 +257,35 @@ static bool restoredFromArchive = false; static char *replay_image_masked = NULL; static char *master_image_masked = NULL; -/* 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; - -/* 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 archive recovery */ +char *recoveryRestoreCommand = NULL; +char *recoveryEndCommand = NULL; +char *archiveCleanupCommand = NULL; +RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET; +char *recoveryTargetTypeString; +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 formerly taken from recovery.conf for XLOG streaming */ +bool StandbyModeRequested = false; +char *PrimaryConnInfo = NULL; +char *PrimarySlotName = NULL; +char *TriggerFile = NULL; /* are we currently in standby mode? */ bool StandbyMode = false; +char PromoteSignalFile[MAXPGPATH]; +char RecoverySignalFile[MAXPGPATH]; +char StandbySignalFile[MAXPGPATH]; + /* whether request for fast promotion has been made yet */ static bool fast_promote = false; @@ -296,7 +307,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'? * @@ -312,7 +327,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; @@ -624,12 +642,6 @@ typedef struct XLogCtlData 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. */ @@ -833,7 +845,7 @@ static bool holdingAllLocks = false; static MemoryContext walDebugCxt = NULL; #endif -static void readRecoveryCommandFile(void); +static void readRecoverySignalFile(void); static void exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog); static bool recoveryStopsBefore(XLogReaderState *record); static bool recoveryStopsAfter(XLogReaderState *record); @@ -5083,251 +5095,182 @@ 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 since as of PG10.0 we no longer recognize that. + * + * See if there are any recovery signal files and if so, set state for + * recovery. * - * The file is parsed using the main configuration parser. + * !!!! NOTE !!!! + * There is a discrepancy in the design here: pg_basebackup -R expects + * to be able to write a file containing backend parameters. To allow + * that the server reads parameters from a file called + * recovery.auto.conf in the data directory. So one might ask why we + * allow that and yet disallow the use of recovery.conf. I have no + * explanation for that, only what you see is what was requested. */ 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) - { - if (errno == ENOENT) - return; /* not there, so no archive recovery */ + /* + * Set paths for named signal files + */ + 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); /* - * Since we're asking ParseConfigFp() to report errors as FATAL, there's - * no need to check the return value. + * 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. */ - (void) ParseConfigFp(fd, RECOVERY_COMMAND_FILE, 0, FATAL, &head, &tail); + if (stat(StandbySignalFile, &stat_buf) == 0) + { + int fd; - FreeFile(fd); + fd = BasicOpenFile(StandbySignalFile, 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(RecoverySignalFile, &stat_buf) == 0) + { + int fd; - for (item = head; item; item = item->next) + fd = BasicOpenFile(RecoverySignalFile, 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) { - 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\"."))); + StandbyModeRequested = true; + ArchiveRecoveryRequested = true; + } + else if (recovery_signal_file_found) + { + StandbyModeRequested = false; + ArchiveRecoveryRequested = true; + } + else + return; - ereport(DEBUG2, - (errmsg_internal("recovery_target_action = '%s'", - item->value))); + /* + * 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"))); - 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; + logRecoveryParameters(); + validateRecoveryParameters(); +} - /* - * 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; +void +logRecoveryParameters(void) +{ + int normal_log_level = LOG; //DEBUG2; - 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))); + /* + * Log messages for recovery parameters at server start + */ + ereport(normal_log_level, + (errmsg_internal("standby_mode = '%s'", (StandbyModeRequested ? "on " : "off")))); - ereport(DEBUG2, - (errmsg_internal("recovery_target_name = '%s'", - recoveryTargetName))); - } - else if (strcmp(item->name, "recovery_target_lsn") == 0) - { - recoveryTarget = RECOVERY_TARGET_LSN; + if (recoveryRestoreCommand != NULL) + ereport(normal_log_level, + (errmsg_internal("restore_command = '%s'", recoveryRestoreCommand))); - /* - * 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 (recoveryEndCommand != NULL) + ereport(normal_log_level, + (errmsg_internal("recovery_end_command = '%s'", recoveryEndCommand))); + + if (archiveCleanupCommand != NULL) + ereport(normal_log_level, + (errmsg_internal("archive_cleanup_command = '%s'", archiveCleanupCommand))); + + if (PrimaryConnInfo != NULL) + ereport(normal_log_level, + (errmsg_internal("primary_conninfo = '%s'", PrimaryConnInfo))); + + if (PrimarySlotName != NULL) + ereport(normal_log_level, + (errmsg_internal("primary_slot_name = '%s'", PrimarySlotName))); + + 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 @@ -5336,8 +5279,7 @@ readRecoveryCommandFile(void) { if (PrimaryConnInfo == 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 @@ -5345,8 +5287,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"))); } /* @@ -5355,50 +5296,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; + + /* 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; - FreeConfigVariables(head); + /* + * 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; + } } /* @@ -5499,11 +5436,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"))); @@ -6234,18 +6179,9 @@ StartupXLOG(void) recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID; /* - * Check for recovery control file, and if so set up state for offline - * recovery - */ - readRecoveryCommandFile(); - - /* - * Save archive_cleanup_command in shared memory so that other processes - * can see it. + * Check for signal files, and if so set up state for offline recovery */ - strlcpy(XLogCtl->archiveCleanupCommand, - archiveCleanupCommand ? archiveCleanupCommand : "", - sizeof(XLogCtl->archiveCleanupCommand)); + readRecoverySignalFile(); if (ArchiveRecoveryRequested) { @@ -6422,7 +6358,7 @@ 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 @@ -6652,7 +6588,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) @@ -6666,7 +6602,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; } @@ -7171,7 +7107,6 @@ StartupXLOG(void) /* * end of main redo apply loop */ - if (reachedStopPoint) { if (!reachedConsistency) @@ -9225,8 +9160,8 @@ CreateRestartPoint(int flags) /* * Finally, execute archive_cleanup_command, if any. */ - if (XLogCtl->archiveCleanupCommand[0]) - ExecuteRecoveryCommand(XLogCtl->archiveCleanupCommand, + if (archiveCleanupCommand) + ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command", false); @@ -11592,7 +11527,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, } curFileTLI = tli; RequestXLogStreaming(tli, ptr, PrimaryConnInfo, - PrimarySlotName); + PrimarySlotName); receivedUpto = 0; } @@ -11920,14 +11855,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; @@ -11935,8 +11871,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/xlogarchive.c b/src/backend/access/transam/xlogarchive.c index 7e91e8f..5751cb8 100644 --- a/src/backend/access/transam/xlogarchive.c +++ b/src/backend/access/transam/xlogarchive.c @@ -409,7 +409,7 @@ ExecuteRecoveryCommand(char *command, char *commandName, bool failOnSignal) ereport((signaled && failOnSignal) ? FATAL : WARNING, /*------ - translator: First %s represents a recovery.conf parameter name like + translator: First %s represents a postgresql.conf parameter name like "recovery_end_command", the 2nd is the value of that parameter, the third an already translated error message. */ (errmsg("%s \"%s\": %s", commandName, diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index 27c0c56..61c1d4a 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -327,10 +327,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/commands/extension.c b/src/backend/commands/extension.c index 325f5b7..c699668 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -9,8 +9,8 @@ * dependent objects can be associated with it. An extension is created by * populating the pg_extension catalog from a "control" file. * The extension control file is parsed with the same parser we use for - * postgresql.conf and recovery.conf. An extension also has an installation - * script file, containing SQL commands to create the extension's objects. + * postgresql.conf. An extension also has an installation script file, + * containing SQL commands to create the extension's objects. * * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c index b172b5e..7003f044 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 18d9d7e..3dacbde 100644 --- a/src/backend/replication/walreceiver.c +++ b/src/backend/replication/walreceiver.c @@ -70,9 +70,9 @@ /* 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; /* libpqwalreceiver connection */ static WalReceiverConn *wrconn = NULL; @@ -425,9 +425,33 @@ WalReceiverMain(void) if (got_SIGHUP) { + char *conninfo = pstrdup(PrimaryConnInfo); + char *slotname = pstrdup(PrimarySlotName); + got_SIGHUP = false; ProcessConfigFile(PGC_SIGHUP); XLogWalRcvSendHSFeedback(true); + + /* + * If primary_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, PrimaryConnInfo) != 0) + ereport(FATAL, + (errcode(ERRCODE_ADMIN_SHUTDOWN), + errmsg("closing replication connection because primary_conninfo was changed"))); + + /* + * And the same for primary_slot_name. + */ + if (strcmp(slotname, PrimarySlotName) != 0) + ereport(FATAL, + (errcode(ERRCODE_ADMIN_SHUTDOWN), + errmsg("closing replication connection because primary_slot_name was changed"))); + + pfree(conninfo); + pfree(slotname); } /* See if we can read data immediately */ diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l index f01b814..2593065 100644 --- a/src/backend/utils/misc/guc-file.l +++ b/src/backend/utils/misc/guc-file.l @@ -190,13 +190,28 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel) } /* + * Read automatically generated configuration files. Because these files + * are in the data directory, we can't read them until the DataDir has + * been set. + * + * First, parse any RECOVERY_AUTOCONF_FILENAME, if it has been generated + * by pg_basebackup + * * Parse the PG_AUTOCONF_FILENAME file, if present, after the main file to - * replace any parameters set by ALTER SYSTEM command. Because this file - * is in the data directory, we can't read it until the DataDir has been - * set. + * replace any parameters set by ALTER SYSTEM command. */ if (DataDir) { + if (!ParseConfigFile(RECOVERY_AUTOCONF_FILENAME, false, + NULL, 0, 0, elevel, + &head, &tail)) + { + /* Syntax error(s) detected in the file, so bail out */ + error = true; + ConfFileWithError = RECOVERY_AUTOCONF_FILENAME; + goto bail_out; + } + if (!ParseConfigFile(PG_AUTOCONF_FILENAME, false, NULL, 0, 0, elevel, &head, &tail)) diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 0707f66..c39c188 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -32,6 +32,7 @@ #include "access/transam.h" #include "access/twophase.h" #include "access/xact.h" +#include "access/xlog.h" #include "access/xlog_internal.h" #include "catalog/namespace.h" #include "commands/async.h" @@ -80,6 +81,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" @@ -190,6 +192,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_primary_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, @@ -467,6 +478,8 @@ char *IdentFileName; char *external_pid_file; char *pgstat_temp_directory; +char *recovery_target_timeline_string; +char *recovery_target_action_string; char *application_name; @@ -600,6 +613,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 */ @@ -1553,6 +1570,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 @@ -1793,8 +1820,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 }, @@ -1805,7 +1843,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 }, @@ -3023,6 +3061,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_POSTMASTER, 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 + }, + + { + {"primary_conninfo", PGC_SIGHUP, REPLICATION_STANDBY, + gettext_noop("Sets the connection string to be used to connect with the sending server."), + NULL, + GUC_SUPERUSER_ONLY + }, + &PrimaryConnInfo, + "", + NULL, NULL, NULL + }, + + { + {"primary_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 + }, + &PrimarySlotName, + "", + check_primary_slot_name, NULL, NULL + }, + + { {"client_encoding", PGC_USERSET, CLIENT_CONN_LOCALE, gettext_noop("Sets the client's character set encoding."), NULL, @@ -10452,4 +10582,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_primary_slot_name(char **newval, void **extra, GucSource source) +{ + if (strcmp(*newval,"") != 0 && + !ReplicationSlotValidateName(*newval, WARNING)) + { + GUC_check_errdetail("primary_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 157d775..ea2bb4f 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -221,6 +221,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 +274,8 @@ # These settings are ignored on a master server. +#primary_conninfo = '' # connection string on sending server +#primary_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/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c index 9020fb1..007b4a6 100644 --- a/src/bin/pg_basebackup/pg_basebackup.c +++ b/src/bin/pg_basebackup/pg_basebackup.c @@ -127,9 +127,12 @@ static int has_xlogendptr = 0; static volatile LONG has_xlogendptr = 0; #endif -/* Contents of recovery.conf to be generated */ +/* Contents of configuration file to be generated */ static PQExpBuffer recoveryconfcontents = NULL; +#define RECOVERY_AUTOCONF_FILENAME "recovery.auto.conf" +#define STANDBY_SIGNAL_FILE "standby.signal" + /* Function headers */ static void usage(void); static void disconnect_and_exit(int code); @@ -337,7 +340,7 @@ usage(void) printf(_(" -r, --max-rate=RATE maximum transfer rate to transfer data directory\n" " (in kB/s, or use suffix \"k\" or \"M\")\n")); printf(_(" -R, --write-recovery-conf\n" - " write recovery.conf for replication\n")); + " write recovery.conf.auto for replication\n")); printf(_(" -S, --slot=SLOTNAME replication slot to use\n")); printf(_(" --no-slot prevent creation of temporary replication slot\n")); printf(_(" -T, --tablespace-mapping=OLDDIR=NEWDIR\n" @@ -1073,8 +1076,8 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) { /* * End of chunk. If requested, and this is the base tablespace, - * write recovery.conf into the tarfile. When done, close the file - * (but not stdout). + * write configuration file into the tarfile. When done, close the + * file (but not stdout). * * Also, write two completely empty blocks at the end of the tar * file, as required by some tar programs. @@ -1088,7 +1091,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) char header[512]; int padding; - tarCreateHeader(header, "recovery.conf", NULL, + tarCreateHeader(header, RECOVERY_AUTOCONF_FILENAME, NULL, recoveryconfcontents->len, 0600, 04000, 02000, time(NULL)); @@ -1099,6 +1102,14 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) WRITE_TAR_DATA(recoveryconfcontents->data, recoveryconfcontents->len); if (padding) WRITE_TAR_DATA(zerobuf, padding); + + tarCreateHeader(header, STANDBY_SIGNAL_FILE, NULL, + 0, /* zero-length file */ + 0600, 04000, 02000, + time(NULL)); + + WRITE_TAR_DATA(header, sizeof(header)); + WRITE_TAR_DATA(zerobuf, 511); } /* 2 * 512 bytes empty data at end of file */ @@ -1142,8 +1153,8 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) if (!writerecoveryconf || !basetablespace) { /* - * When not writing recovery.conf, or when not working on the base - * tablespace, we never have to look for an existing recovery.conf + * When not writing config file, or when not working on the base + * tablespace, we never have to look for an existing configuration * file in the stream. */ WRITE_TAR_DATA(copybuf, r); @@ -1151,7 +1162,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) else { /* - * Look for a recovery.conf in the existing tar stream. If it's + * Look for a config file in the existing tar stream. If it's * there, we must skip it so we can later overwrite it with our * own version of the file. * @@ -1196,13 +1207,15 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) /* * We have the complete header structure in tarhdr, * look at the file metadata: - the subsequent file - * contents have to be skipped if the filename is - * recovery.conf - find out the size of the file + * contents have to be skipped if the filename matches + * config file - find out the size of the file * padded to the next multiple of 512 + * + * Note we don't skip STANDBY_SIGNAL_FILE (correct??) */ int padding; - skip_file = (strcmp(&tarhdr[0], "recovery.conf") == 0); + skip_file = (strcmp(&tarhdr[0], RECOVERY_AUTOCONF_FILENAME) == 0); filesz = read_tar_number(&tarhdr[124], 12); @@ -1569,7 +1582,7 @@ escape_quotes(const char *src) } /* - * Create a recovery.conf file in memory using a PQExpBuffer + * Create a configuration file in memory using a PQExpBuffer */ static void GenerateRecoveryConf(PGconn *conn) @@ -1653,8 +1666,9 @@ GenerateRecoveryConf(PGconn *conn) /* - * Write a recovery.conf file into the directory specified in basedir, + * Write the configuration file into the directory specified in basedir, * with the contents already collected in memory. + * Then write the signal file into the basedir also. */ static void WriteRecoveryConf(void) @@ -1662,8 +1676,7 @@ WriteRecoveryConf(void) char filename[MAXPGPATH]; FILE *cf; - sprintf(filename, "%s/recovery.conf", basedir); - + snprintf(filename, MAXPGPATH, "%s/%s", basedir, RECOVERY_AUTOCONF_FILENAME); cf = fopen(filename, "w"); if (cf == NULL) { @@ -1680,6 +1693,16 @@ WriteRecoveryConf(void) } fclose(cf); + + snprintf(filename, MAXPGPATH, "%s/%s", basedir, STANDBY_SIGNAL_FILE); + cf = fopen(filename, "w"); + if (cf == NULL) + { + fprintf(stderr, _("%s: could not create file \"%s\": %s\n"), progname, filename, strerror(errno)); + disconnect_and_exit(1); + } + + fclose(cf); } @@ -1735,7 +1758,7 @@ BaseBackup(void) } /* - * Build contents of recovery.conf if requested + * Build contents of configuration file if requested */ if (writerecoveryconf) GenerateRecoveryConf(conn); @@ -2018,7 +2041,7 @@ BaseBackup(void) #endif } - /* Free the recovery.conf contents */ + /* Free the configuration file contents */ destroyPQExpBuffer(recoveryconfcontents); /* diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl index 29f519d..d8132ab 100644 --- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl +++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl @@ -212,16 +212,13 @@ SKIP: $node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backupR", '-R' ], 'pg_basebackup -R runs'); -ok(-f "$tempdir/backupR/recovery.conf", 'recovery.conf was created'); -my $recovery_conf = slurp_file "$tempdir/backupR/recovery.conf"; +ok(-f "$tempdir/backupR/recovery.auto.conf", 'recovery conf file was created'); +ok(-f "$tempdir/backupR/standby.signal", 'standby mode is configured'); +my $recovery_conf = slurp_file "$tempdir/backupR/recovery.auto.conf"; my $port = $node->port; like( $recovery_conf, - qr/^standby_mode = 'on'\n/m, - 'recovery.conf sets standby_mode'); -like( - $recovery_conf, qr/^primary_conninfo = '.*port=$port.*'\n/m, 'recovery.conf sets primary_conninfo'); @@ -278,6 +275,6 @@ $node->command_ok( 'stream', '-S', 'slot1', '-R' ], 'pg_basebackup with replication slot and -R runs'); like( - slurp_file("$tempdir/backupxs_sl_R/recovery.conf"), + slurp_file("$tempdir/backupxs_sl_R/recovery.auto.conf"), qr/^primary_slot_name = 'slot1'\n/m, - 'recovery.conf sets primary_slot_name'); + 'recovery conf file sets primary_slot_name'); diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm index c67212f..3ccb201 100644 --- a/src/bin/pg_rewind/RewindTest.pm +++ b/src/bin/pg_rewind/RewindTest.pm @@ -136,7 +136,7 @@ sub create_standby my $connstr_master = $node_master->connstr(); $node_standby->append_conf( - "recovery.conf", qq( + "postgresql.conf", qq( primary_conninfo='$connstr_master application_name=rewind_standby' standby_mode=on recovery_target_timeline='latest' @@ -234,7 +234,7 @@ sub run_pg_rewind # Plug-in rewound node to the now-promoted standby node my $port_standby = $node_standby->port; $node_master->append_conf( - 'recovery.conf', qq( + 'postgresql.conf', qq( primary_conninfo='port=$port_standby' standby_mode=on recovery_target_timeline='latest' diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 9f036c7..cb5bdac 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; @@ -111,6 +146,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 *PrimaryConnInfo; +extern char *PrimarySlotName; + +extern char *signal_file_directory; + +extern char *recoveryTargetTLIString; +extern RecoveryTargetTimeLineGoal recoveryTargetTimeLineGoal; +extern TimeLineID recoveryTargetTLIRequested; +extern TimeLineID recoveryTargetTLI; + /* Archive modes */ typedef enum ArchiveMode { @@ -240,6 +305,8 @@ extern const char *xlog_identify(uint8 info); extern void issue_xlog_fsync(int fd, XLogSegNo segno); +extern void logRecoveryParameters(void); +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 578bff5..e4e4ac0 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.h b/src/include/utils/guc.h index 7dd3780..829c0d2 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -34,6 +34,11 @@ #define PG_AUTOCONF_FILENAME "postgresql.auto.conf" /* + * Recovery commands generated by pg_basebackup + */ +#define RECOVERY_AUTOCONF_FILENAME "recovery.auto.conf" + +/* * Certain options can only be set at certain times. The rules are * like this: * diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index 2da9115..c75537c 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 4018f0a..f0eec62 100644 --- a/src/test/perl/PostgresNode.pm +++ b/src/test/perl/PostgresNode.pm @@ -413,6 +413,8 @@ sub init print $conf "fsync = off\n"; print $conf "log_line_prefix = '%m [%p] %q%a '\n"; print $conf "log_statement = all\n"; + print $conf "log_replication_commands = on\n"; + print $conf "log_min_messages = DEBUG2\n"; print $conf "port = $port\n"; if ($params{allows_streaming}) @@ -589,8 +591,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. @@ -760,13 +760,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( + 'postgresql.conf', qq( primary_conninfo='$root_connstr application_name=$name' -standby_mode=on )); + 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 @@ -775,6 +778,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"; @@ -791,10 +795,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 $standbysignal, ">>$pgdata/standby.signal"; + print $standbysignal "\n# Allow replication (set up by PostgresNode.pm)\n"; + close $standbysignal; } # Internal routine to enable archiving diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl index 7fb2e9e..411c279 100644 --- a/src/test/recovery/t/001_stream_rep.pl +++ b/src/test/recovery/t/001_stream_rep.pl @@ -68,12 +68,12 @@ my ($slotname_1, $slotname_2) = ('standby_1', 'standby_2'); $node_master->append_conf('postgresql.conf', "max_replication_slots = 4\n"); $node_master->restart; is($node_master->psql('postgres', qq[SELECT pg_create_physical_replication_slot('$slotname_1');]), 0, 'physical slot created on master'); -$node_standby_1->append_conf('recovery.conf', "primary_slot_name = $slotname_1\n"); +$node_standby_1->append_conf('postgresql.conf', "primary_slot_name = $slotname_1\n"); $node_standby_1->append_conf('postgresql.conf', "wal_receiver_status_interval = 1\n"); $node_standby_1->append_conf('postgresql.conf', "max_replication_slots = 4\n"); $node_standby_1->restart; is($node_standby_1->psql('postgres', qq[SELECT pg_create_physical_replication_slot('$slotname_2');]), 0, 'physical slot created on intermediate replica'); -$node_standby_2->append_conf('recovery.conf', "primary_slot_name = $slotname_2\n"); +$node_standby_2->append_conf('postgresql.conf', "primary_slot_name = $slotname_2\n"); $node_standby_2->append_conf('postgresql.conf', "wal_receiver_status_interval = 1\n"); $node_standby_2->restart; diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl index b7b0caa..a23b35f 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_wal()"); # 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 7c6587a..2abdd71 100644 --- a/src/test/recovery/t/004_timeline_switch.pl +++ b/src/test/recovery/t/004_timeline_switch.pl @@ -41,10 +41,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'); +#----still needed? rmtree($node_standby_2->data_dir . '/recovery.conf'); my $connstr_1 = $node_standby_1->connstr; $node_standby_2->append_conf( - 'recovery.conf', qq( + 'postgresql.conf', qq( primary_conninfo='$connstr_1 application_name=@{[$node_standby_2->name]}' standby_mode=on recovery_target_timeline='latest' diff --git a/src/test/recovery/t/005_replay_delay.pl b/src/test/recovery/t/005_replay_delay.pl index cd9e8f5..b7de0a9 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;