diff options
Diffstat (limited to 'src')
22 files changed, 828 insertions, 580 deletions
diff --git a/src/backend/Makefile b/src/backend/Makefile index 3a58bf6685b..25eb0439416 100644 --- a/src/backend/Makefile +++ b/src/backend/Makefile @@ -209,7 +209,6 @@ endif $(INSTALL_DATA) $(srcdir)/libpq/pg_hba.conf.sample '$(DESTDIR)$(datadir)/pg_hba.conf.sample' $(INSTALL_DATA) $(srcdir)/libpq/pg_ident.conf.sample '$(DESTDIR)$(datadir)/pg_ident.conf.sample' $(INSTALL_DATA) $(srcdir)/utils/misc/postgresql.conf.sample '$(DESTDIR)$(datadir)/postgresql.conf.sample' - $(INSTALL_DATA) $(srcdir)/access/transam/recovery.conf.sample '$(DESTDIR)$(datadir)/recovery.conf.sample' ifeq ($(with_llvm), yes) install-bin: install-postgres-bitcode @@ -274,8 +273,7 @@ endif $(MAKE) -C utils uninstall-data rm -f '$(DESTDIR)$(datadir)/pg_hba.conf.sample' \ '$(DESTDIR)$(datadir)/pg_ident.conf.sample' \ - '$(DESTDIR)$(datadir)/postgresql.conf.sample' \ - '$(DESTDIR)$(datadir)/recovery.conf.sample' + '$(DESTDIR)$(datadir)/postgresql.conf.sample' ifeq ($(with_llvm), yes) $(call uninstall_llvm_module,postgres) endif diff --git a/src/backend/access/transam/recovery.conf.sample b/src/backend/access/transam/recovery.conf.sample deleted file mode 100644 index 8e466126428..00000000000 --- a/src/backend/access/transam/recovery.conf.sample +++ /dev/null @@ -1,158 +0,0 @@ -# ------------------------------- -# PostgreSQL recovery config file -# ------------------------------- -# -# Edit this file to provide the parameters that PostgreSQL needs to -# perform an archive recovery of a database, or to act as a replication -# standby. -# -# If "recovery.conf" is present in the PostgreSQL data directory, it is -# read on postmaster startup. After successful recovery, it is renamed -# to "recovery.done" to ensure that we do not accidentally re-enter -# archive recovery or standby mode. -# -# This file consists of lines of the form: -# -# name = value -# -# Comments are introduced with '#'. -# -# The complete list of option names and allowed values can be found -# in the PostgreSQL documentation. -# -#--------------------------------------------------------------------------- -# ARCHIVE RECOVERY PARAMETERS -#--------------------------------------------------------------------------- -# -# restore_command -# -# specifies the shell command that is executed to copy log files -# back from archival storage. The command string may contain %f, -# which is replaced by the name of the desired log file, and %p, -# which is replaced by the absolute path to copy the log file to. -# -# This parameter is *required* for an archive recovery, but optional -# for streaming replication. -# -# It is important that the command return nonzero exit status on failure. -# The command *will* be asked for log files that are not present in the -# archive; it must return nonzero when so asked. -# -# NOTE that the basename of %p will be different from %f; do not -# expect them to be interchangeable. -# -#restore_command = '' # e.g. 'cp /mnt/server/archivedir/%f %p' -# -# -# archive_cleanup_command -# -# specifies an optional shell command to execute at every restartpoint. -# This can be useful for cleaning up the archive of a standby server. -# -#archive_cleanup_command = '' -# -# recovery_end_command -# -# specifies an optional shell command to execute at completion of recovery. -# This can be useful for cleaning up after the restore_command. -# -#recovery_end_command = '' -# -#--------------------------------------------------------------------------- -# RECOVERY TARGET PARAMETERS -#--------------------------------------------------------------------------- -# -# By default, recovery will rollforward to the end of the WAL log. -# If you want to stop rollforward at a specific point, you -# must set a recovery target. -# -# You may set a recovery target either by transactionId, by name, by -# timestamp or by WAL location (LSN). Recovery may either include or -# exclude the transaction(s) with the recovery target value (i.e., -# stop either just after or just before the given target, -# respectively). -# -# -#recovery_target_name = '' # e.g. 'daily backup 2011-01-26' -# -#recovery_target_time = '' # e.g. '2004-07-14 22:39:00 EST' -# -#recovery_target_xid = '' -# -#recovery_target_lsn = '' # e.g. '0/70006B8' -# -#recovery_target_inclusive = true -# -# -# Alternatively, you can request stopping as soon as a consistent state -# is reached, by uncommenting this option. -# -#recovery_target = 'immediate' -# -# -# If you want to recover into a timeline other than the "main line" shown in -# pg_control, specify the timeline number here, or write 'latest' to get -# the latest branch for which there's a history file. -# -#recovery_target_timeline = 'latest' -# -# -# If recovery_target_action = 'pause', recovery will pause when the -# recovery target is reached. The pause state will continue until -# pg_wal_replay_resume() is called. This setting has no effect if -# no recovery target is set. If hot_standby is not enabled then the -# server will shutdown instead, though you may request this in -# any case by specifying 'shutdown'. -# -#recovery_target_action = 'pause' -# -#--------------------------------------------------------------------------- -# STANDBY SERVER PARAMETERS -#--------------------------------------------------------------------------- -# -# standby_mode -# -# When standby_mode is enabled, the PostgreSQL server will work as a -# standby. It will continuously wait for the additional XLOG records, using -# restore_command and/or primary_conninfo. -# -#standby_mode = off -# -# primary_conninfo -# -# If set, the PostgreSQL server will try to connect to the primary using this -# connection string and receive XLOG records continuously. -# -#primary_conninfo = '' # e.g. 'host=localhost port=5432' -# -# If set, the PostgreSQL server will use the specified replication slot when -# connecting to the primary via streaming replication to control resource -# removal on the upstream node. This setting has no effect if primary_conninfo -# is not set. -# -#primary_slot_name = '' -# -# By default, a standby server keeps restoring XLOG records from the -# primary indefinitely. If you want to stop the standby mode, finish recovery -# and open the system in read/write mode, specify a path to a trigger file. -# The server will poll the trigger file path periodically and start as a -# primary server when it's found. -# -#trigger_file = '' -# -# By default, a standby server restores XLOG records from the primary as -# soon as possible. If you want to explicitly delay the replay of committed -# transactions from the master, specify a minimum apply delay. For example, -# if you set this parameter to 5min, the standby will replay each transaction -# commit only when the system time on the standby is at least five minutes -# past the commit time reported by the master. -# -#recovery_min_apply_delay = 0 -# -#--------------------------------------------------------------------------- -# HOT STANDBY PARAMETERS -#--------------------------------------------------------------------------- -# -# Hot Standby related parameters are listed in postgresql.conf -# -#--------------------------------------------------------------------------- diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 7397b6ee06b..c80b14ed971 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -69,7 +69,6 @@ #include "utils/builtins.h" #include "utils/guc.h" #include "utils/memutils.h" -#include "utils/pg_lsn.h" #include "utils/ps_status.h" #include "utils/relmapper.h" #include "utils/snapmgr.h" @@ -78,6 +77,9 @@ extern uint32 bootstrap_data_checksum_version; +/* Unsupported old recovery command file names (relative to $PGDATA) */ +#define RECOVERY_COMMAND_FILE "recovery.conf" +#define RECOVERY_COMMAND_DONE "recovery.done" /* User-settable parameters */ int max_wal_size_mb = 1024; /* 1 GB */ @@ -161,6 +163,13 @@ const struct config_enum_entry archive_mode_options[] = { {NULL, 0, false} }; +const struct config_enum_entry recovery_target_action_options[] = { + {"pause", RECOVERY_TARGET_ACTION_PAUSE, false}, + {"promote", RECOVERY_TARGET_ACTION_PROMOTE, false}, + {"shutdown", RECOVERY_TARGET_ACTION_SHUTDOWN, false}, + {NULL, 0, false} +}; + /* * Statistics for current checkpoint are collected in this global struct. * Because only the checkpointer or a stand-alone backend can perform @@ -230,7 +239,7 @@ static int LocalXLogInsertAllowed = -1; /* * When ArchiveRecoveryRequested is set, archive recovery was requested, - * ie. recovery.conf file was present. When InArchiveRecovery is set, we are + * ie. signal files were present. When InArchiveRecovery is set, we are * currently recovering using offline XLOG archives. These variables are only * valid in the startup process. * @@ -242,6 +251,9 @@ static int LocalXLogInsertAllowed = -1; bool ArchiveRecoveryRequested = false; bool InArchiveRecovery = false; +static bool standby_signal_file_found = false; +static bool recovery_signal_file_found = false; + /* Was the last xlog file restored from archive, or local? */ static bool restoredFromArchive = false; @@ -249,25 +261,25 @@ static bool restoredFromArchive = false; static char *replay_image_masked = NULL; static char *master_image_masked = NULL; -/* options taken from recovery.conf for archive recovery */ +/* options formerly taken from recovery.conf for archive recovery */ char *recoveryRestoreCommand = NULL; -static char *recoveryEndCommand = NULL; -static char *archiveCleanupCommand = NULL; -static RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET; -static bool recoveryTargetInclusive = true; -static RecoveryTargetAction recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE; -static TransactionId recoveryTargetXid; -static TimestampTz recoveryTargetTime; -static char *recoveryTargetName; -static XLogRecPtr recoveryTargetLSN; -static int recovery_min_apply_delay = 0; -static TimestampTz recoveryDelayUntilTime; - -/* options taken from recovery.conf for XLOG streaming */ -static bool StandbyModeRequested = false; -static char *PrimaryConnInfo = NULL; -static char *PrimarySlotName = NULL; -static char *TriggerFile = NULL; +char *recoveryEndCommand = NULL; +char *archiveCleanupCommand = NULL; +RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET; +bool recoveryTargetInclusive = true; +int recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE; +TransactionId recoveryTargetXid; +TimestampTz recoveryTargetTime; +char *recoveryTargetName; +XLogRecPtr recoveryTargetLSN; +int recovery_min_apply_delay = 0; +TimestampTz recoveryDelayUntilTime; + +/* options formerly taken from recovery.conf for XLOG streaming */ +bool StandbyModeRequested = false; +char *PrimaryConnInfo = NULL; +char *PrimarySlotName = NULL; +char *PromoteTriggerFile = NULL; /* are we currently in standby mode? */ bool StandbyMode = false; @@ -293,7 +305,11 @@ static bool recoveryStopAfter; * the currently-scanned WAL record was generated). We also need these * timeline values: * - * recoveryTargetTLI: the desired timeline that we want to end in. + * recoveryTargetTimeLineGoal: what the user requested, if any + * + * recoveryTargetTLIRequested: numeric value of requested timeline, if constant + * + * recoveryTargetTLI: the currently understood target timeline; changes * * recoveryTargetIsLatest: was the requested target timeline 'latest'? * @@ -309,8 +325,9 @@ static bool recoveryStopAfter; * file was created.) During a sequential scan we do not allow this value * to decrease. */ -static TimeLineID recoveryTargetTLI; -static bool recoveryTargetIsLatest = false; +RecoveryTargetTimeLineGoal recoveryTargetTimeLineGoal = RECOVERY_TARGET_TIMELINE_CONTROLFILE; +TimeLineID recoveryTargetTLIRequested = 0; +TimeLineID recoveryTargetTLI = 0; static List *expectedTLEs; static TimeLineID curFileTLI; @@ -625,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. */ @@ -846,7 +857,8 @@ static bool holdingAllLocks = false; static MemoryContext walDebugCxt = NULL; #endif -static void readRecoveryCommandFile(void); +static void readRecoverySignalFile(void); +static void validateRecoveryParameters(void); static void exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog); static bool recoveryStopsBefore(XLogReaderState *record); static bool recoveryStopsAfter(XLogReaderState *record); @@ -5285,283 +5297,111 @@ str_time(pg_time_t tnow) } /* - * See if there is a recovery command file (recovery.conf), and if so - * read in parameters for archive recovery and XLOG streaming. + * See if there are any recovery signal files and if so, set state for + * recovery. * - * The file is parsed using the main configuration parser. + * See if there is a recovery command file (recovery.conf), and if so + * throw an ERROR since as of PG12 we no longer recognize that. */ static void -readRecoveryCommandFile(void) +readRecoverySignalFile(void) { - FILE *fd; - TimeLineID rtli = 0; - bool rtliGiven = false; - ConfigVariable *item, - *head = NULL, - *tail = NULL; - bool recoveryTargetActionSet = false; + struct stat stat_buf; + if (IsBootstrapProcessingMode()) + return; - fd = AllocateFile(RECOVERY_COMMAND_FILE, "r"); - if (fd == NULL) - { - if (errno == ENOENT) - return; /* not there, so no archive recovery */ + /* + * 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", + errmsg("using recovery command file \"%s\" is not supported", RECOVERY_COMMAND_FILE))); - } /* - * Since we're asking ParseConfigFp() to report errors as FATAL, there's - * no need to check the return value. + * Remove unused .done file, if present. Ignore if absent. */ - (void) ParseConfigFp(fd, RECOVERY_COMMAND_FILE, 0, FATAL, &head, &tail); - - FreeFile(fd); + unlink(RECOVERY_COMMAND_DONE); - for (item = head; item; item = item->next) + /* + * Check for recovery signal files and if found, fsync them since they + * represent server state information. + * + * If present, standby signal file takes precedence. If neither is present + * then we won't enter archive recovery. + */ + if (stat(STANDBY_SIGNAL_FILE, &stat_buf) == 0) { - if (strcmp(item->name, "restore_command") == 0) - { - recoveryRestoreCommand = pstrdup(item->value); - ereport(DEBUG2, - (errmsg_internal("restore_command = '%s'", - recoveryRestoreCommand))); - } - else if (strcmp(item->name, "recovery_end_command") == 0) - { - recoveryEndCommand = pstrdup(item->value); - ereport(DEBUG2, - (errmsg_internal("recovery_end_command = '%s'", - recoveryEndCommand))); - } - else if (strcmp(item->name, "archive_cleanup_command") == 0) - { - archiveCleanupCommand = pstrdup(item->value); - ereport(DEBUG2, - (errmsg_internal("archive_cleanup_command = '%s'", - archiveCleanupCommand))); - } - else if (strcmp(item->name, "recovery_target_action") == 0) - { - if (strcmp(item->value, "pause") == 0) - recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE; - else if (strcmp(item->value, "promote") == 0) - recoveryTargetAction = RECOVERY_TARGET_ACTION_PROMOTE; - else if (strcmp(item->value, "shutdown") == 0) - recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN; - else - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for recovery parameter \"%s\": \"%s\"", - "recovery_target_action", - item->value), - errhint("Valid values are \"pause\", \"promote\", and \"shutdown\"."))); - - ereport(DEBUG2, - (errmsg_internal("recovery_target_action = '%s'", - item->value))); - - recoveryTargetActionSet = true; - } - else if (strcmp(item->name, "recovery_target_timeline") == 0) - { - rtliGiven = true; - if (strcmp(item->value, "latest") == 0) - rtli = 0; - else - { - errno = 0; - rtli = (TimeLineID) strtoul(item->value, NULL, 0); - if (errno == EINVAL || errno == ERANGE) - ereport(FATAL, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("recovery_target_timeline is not a valid number: \"%s\"", - item->value))); - } - if (rtli) - ereport(DEBUG2, - (errmsg_internal("recovery_target_timeline = %u", rtli))); - else - ereport(DEBUG2, - (errmsg_internal("recovery_target_timeline = latest"))); - } - else if (strcmp(item->name, "recovery_target_xid") == 0) - { - errno = 0; - recoveryTargetXid = (TransactionId) strtoul(item->value, NULL, 0); - if (errno == EINVAL || errno == ERANGE) - ereport(FATAL, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("recovery_target_xid is not a valid number: \"%s\"", - item->value))); - ereport(DEBUG2, - (errmsg_internal("recovery_target_xid = %u", - recoveryTargetXid))); - recoveryTarget = RECOVERY_TARGET_XID; - } - else if (strcmp(item->name, "recovery_target_time") == 0) - { - recoveryTarget = RECOVERY_TARGET_TIME; - - if (strcmp(item->value, "epoch") == 0 || - strcmp(item->value, "infinity") == 0 || - strcmp(item->value, "-infinity") == 0 || - strcmp(item->value, "now") == 0 || - strcmp(item->value, "today") == 0 || - strcmp(item->value, "tomorrow") == 0 || - strcmp(item->value, "yesterday") == 0) - ereport(FATAL, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("recovery_target_time is not a valid timestamp: \"%s\"", - item->value))); + int fd; - /* - * 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; + fd = BasicOpenFilePerm(STANDBY_SIGNAL_FILE, O_RDWR | PG_BINARY | get_sync_bit(sync_method), + S_IRUSR | S_IWUSR); + pg_fsync(fd); + close(fd); + standby_signal_file_found = true; + } + else if (stat(RECOVERY_SIGNAL_FILE, &stat_buf) == 0) + { + int fd; - 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))); + fd = BasicOpenFilePerm(RECOVERY_SIGNAL_FILE, O_RDWR | PG_BINARY | get_sync_bit(sync_method), + S_IRUSR | S_IWUSR); + pg_fsync(fd); + close(fd); + recovery_signal_file_found = true; + } - ereport(DEBUG2, - (errmsg_internal("recovery_target_name = '%s'", - recoveryTargetName))); - } - else if (strcmp(item->name, "recovery_target_lsn") == 0) - { - recoveryTarget = RECOVERY_TARGET_LSN; + StandbyModeRequested = false; + ArchiveRecoveryRequested = false; + if (standby_signal_file_found) + { + StandbyModeRequested = true; + ArchiveRecoveryRequested = true; + } + else if (recovery_signal_file_found) + { + StandbyModeRequested = false; + ArchiveRecoveryRequested = true; + } + else + return; - /* - * Convert the LSN string given by the user to XLogRecPtr form. - */ - recoveryTargetLSN = - DatumGetLSN(DirectFunctionCall3(pg_lsn_in, - CStringGetDatum(item->value), - ObjectIdGetDatum(InvalidOid), - Int32GetDatum(-1))); - ereport(DEBUG2, - (errmsg_internal("recovery_target_lsn = '%X/%X'", - (uint32) (recoveryTargetLSN >> 32), - (uint32) recoveryTargetLSN))); - } - else if (strcmp(item->name, "recovery_target") == 0) - { - if (strcmp(item->value, "immediate") == 0) - recoveryTarget = RECOVERY_TARGET_IMMEDIATE; - else - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for recovery parameter \"%s\": \"%s\"", - "recovery_target", - item->value), - errhint("The only allowed value is \"immediate\"."))); - ereport(DEBUG2, - (errmsg_internal("recovery_target = '%s'", - item->value))); - } - else if (strcmp(item->name, "recovery_target_inclusive") == 0) - { - /* - * does nothing if a recovery_target is not also set - */ - if (!parse_bool(item->value, &recoveryTargetInclusive)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("parameter \"%s\" requires a Boolean value", - "recovery_target_inclusive"))); - ereport(DEBUG2, - (errmsg_internal("recovery_target_inclusive = %s", - item->value))); - } - else if (strcmp(item->name, "standby_mode") == 0) - { - if (!parse_bool(item->value, &StandbyModeRequested)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("parameter \"%s\" requires a Boolean value", - "standby_mode"))); - ereport(DEBUG2, - (errmsg_internal("standby_mode = '%s'", item->value))); - } - else if (strcmp(item->name, "primary_conninfo") == 0) - { - PrimaryConnInfo = pstrdup(item->value); - ereport(DEBUG2, - (errmsg_internal("primary_conninfo = '%s'", - PrimaryConnInfo))); - } - else if (strcmp(item->name, "primary_slot_name") == 0) - { - ReplicationSlotValidateName(item->value, ERROR); - PrimarySlotName = pstrdup(item->value); - ereport(DEBUG2, - (errmsg_internal("primary_slot_name = '%s'", - PrimarySlotName))); - } - else if (strcmp(item->name, "trigger_file") == 0) - { - TriggerFile = pstrdup(item->value); - ereport(DEBUG2, - (errmsg_internal("trigger_file = '%s'", - TriggerFile))); - } - else if (strcmp(item->name, "recovery_min_apply_delay") == 0) - { - const char *hintmsg; + /* + * 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"))); +} - if (!parse_int(item->value, &recovery_min_apply_delay, GUC_UNIT_MS, - &hintmsg)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("parameter \"%s\" requires a temporal value", - "recovery_min_apply_delay"), - hintmsg ? errhint("%s", _(hintmsg)) : 0)); - ereport(DEBUG2, - (errmsg_internal("recovery_min_apply_delay = '%s'", item->value))); - } - else - ereport(FATAL, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized recovery parameter \"%s\"", - item->name))); - } +static void +validateRecoveryParameters(void) +{ + if (!ArchiveRecoveryRequested) + return; /* * Check for compulsory parameters */ if (StandbyModeRequested) { - if (PrimaryConnInfo == NULL && recoveryRestoreCommand == NULL) + if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) && + (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)) ereport(WARNING, - (errmsg("recovery command file \"%s\" specified neither primary_conninfo nor restore_command", - RECOVERY_COMMAND_FILE), + (errmsg("specified neither primary_conninfo nor restore_command"), errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there."))); } else { - if (recoveryRestoreCommand == NULL) + if (recoveryRestoreCommand == NULL || + strcmp(recoveryRestoreCommand, "") == 0) ereport(FATAL, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("recovery command file \"%s\" must specify restore_command when standby mode is not enabled", - RECOVERY_COMMAND_FILE))); + errmsg("must specify restore_command when standby mode is not enabled"))); } /* @@ -5570,50 +5410,40 @@ readRecoveryCommandFile(void) * hot_standby = off, which was surprising behaviour. */ if (recoveryTargetAction == RECOVERY_TARGET_ACTION_PAUSE && - recoveryTargetActionSet && !EnableHotStandby) recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN; /* - * We don't support standby_mode in standalone backends; that requires - * other processes such as the WAL receiver to be alive. - */ - if (StandbyModeRequested && !IsUnderPostmaster) - ereport(FATAL, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("standby mode is not supported by single-user servers"))); - - /* Enable fetching from archive recovery area */ - ArchiveRecoveryRequested = true; - - /* * If user specified recovery_target_timeline, validate it or compute the * "latest" value. We can't do this until after we've gotten the restore * command and set InArchiveRecovery, because we need to fetch timeline * history files from the archive. */ - if (rtliGiven) + if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC) { - if (rtli) - { - /* Timeline 1 does not have a history file, all else should */ - if (rtli != 1 && !existsTimeLineHistory(rtli)) - ereport(FATAL, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("recovery target timeline %u does not exist", - rtli))); - recoveryTargetTLI = rtli; - recoveryTargetIsLatest = false; - } - else - { - /* We start the "latest" search from pg_control's timeline */ - recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI); - recoveryTargetIsLatest = true; - } - } + TimeLineID rtli = recoveryTargetTLIRequested; - FreeConfigVariables(head); + /* Timeline 1 does not have a history file, all else should */ + if (rtli != 1 && !existsTimeLineHistory(rtli)) + ereport(FATAL, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("recovery target timeline %u does not exist", + rtli))); + recoveryTargetTLI = rtli; + } + else if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST) + { + /* We start the "latest" search from pg_control's timeline */ + recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI); + } + else + { + /* + * else we just use the recoveryTargetTLI as already read from + * ControlFile + */ + Assert(recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_CONTROLFILE); + } } /* @@ -5714,11 +5544,14 @@ exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog) unlink(recoveryPath); /* ignore any error */ /* - * Rename the config file out of the way, so that we don't accidentally + * Remove the signal files out of the way, so that we don't accidentally * re-enter archive recovery mode in a subsequent crash. */ - unlink(RECOVERY_COMMAND_DONE); - durable_rename(RECOVERY_COMMAND_FILE, RECOVERY_COMMAND_DONE, FATAL); + if (standby_signal_file_found) + durable_unlink(STANDBY_SIGNAL_FILE, FATAL); + + if (recovery_signal_file_found) + durable_unlink(RECOVERY_SIGNAL_FILE, FATAL); ereport(LOG, (errmsg("archive recovery complete"))); @@ -6461,18 +6294,10 @@ StartupXLOG(void) recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID; /* - * Check for recovery control file, and if so set up state for offline - * recovery - */ - 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(); + validateRecoveryParameters(); if (ArchiveRecoveryRequested) { @@ -6652,7 +6477,8 @@ StartupXLOG(void) * This can happen for example if a base backup is taken from a * running server using an atomic filesystem snapshot, without calling * pg_start/stop_backup. Or if you just kill a running master server - * and put it into archive recovery by creating a recovery.conf file. + * and put it into archive recovery by creating a recovery signal + * file. * * Our strategy in that case is to perform crash recovery first, * replaying all the WAL present in pg_wal, and only enter archive @@ -6687,7 +6513,7 @@ StartupXLOG(void) { /* * We used to attempt to go back to a secondary checkpoint record - * here, but only when not in standby_mode. We now just fail if we + * here, but only when not in standby mode. We now just fail if we * can't read the last checkpoint because this allows us to * simplify processing around checkpoints. */ @@ -6878,7 +6704,7 @@ StartupXLOG(void) /* * Check whether we need to force recovery from WAL. If it appears to - * have been a clean shutdown and we did not have a recovery.conf file, + * have been a clean shutdown and we did not have a recovery signal file, * then assume no recovery needed. */ if (checkPoint.redo < RecPtr) @@ -6892,7 +6718,7 @@ StartupXLOG(void) InRecovery = true; else if (ArchiveRecoveryRequested) { - /* force recovery due to presence of recovery.conf */ + /* force recovery due to presence of recovery signal file */ InRecovery = true; } @@ -7763,7 +7589,7 @@ StartupXLOG(void) /* * And finally, execute the recovery_end_command, if any. */ - if (recoveryEndCommand) + if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0) ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true); @@ -9485,8 +9311,8 @@ CreateRestartPoint(int flags) /* * Finally, execute archive_cleanup_command, if any. */ - if (XLogCtl->archiveCleanupCommand[0]) - ExecuteRecoveryCommand(XLogCtl->archiveCleanupCommand, + if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0) + ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command", false); @@ -11995,7 +11821,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, * that when we later jump backwards to start redo at * RedoStartLSN, we will have the logs streamed already. */ - if (PrimaryConnInfo) + if (PrimaryConnInfo && strcmp(PrimaryConnInfo, "") != 0) { XLogRecPtr ptr; TimeLineID tli; @@ -12064,7 +11890,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, * Before we sleep, re-scan for possible new timelines if * we were requested to recover to the latest timeline. */ - if (recoveryTargetIsLatest) + if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST) { if (rescanLatestTimeLine()) { @@ -12367,14 +12193,14 @@ CheckForStandbyTrigger(void) return true; } - if (TriggerFile == NULL) + if (PromoteTriggerFile == NULL || strcmp(PromoteTriggerFile, "") == 0) return false; - if (stat(TriggerFile, &stat_buf) == 0) + if (stat(PromoteTriggerFile, &stat_buf) == 0) { ereport(LOG, - (errmsg("trigger file found: %s", TriggerFile))); - unlink(TriggerFile); + (errmsg("promote trigger file found: %s", PromoteTriggerFile))); + unlink(PromoteTriggerFile); triggered = true; fast_promote = true; return true; @@ -12382,8 +12208,8 @@ CheckForStandbyTrigger(void) else if (errno != ENOENT) ereport(ERROR, (errcode_for_file_access(), - errmsg("could not stat trigger file \"%s\": %m", - TriggerFile))); + errmsg("could not stat promote trigger file \"%s\": %m", + PromoteTriggerFile))); return false; } diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c index d40317168e2..b8da714b800 100644 --- a/src/backend/access/transam/xlogarchive.c +++ b/src/backend/access/transam/xlogarchive.c @@ -66,7 +66,7 @@ RestoreArchivedFile(char *path, const char *xlogfname, TimeLineID restartTli; /* In standby mode, restore_command might not be supplied */ - if (recoveryRestoreCommand == NULL) + if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0) goto not_available; /* @@ -410,7 +410,7 @@ ExecuteRecoveryCommand(const char *command, const char *commandName, bool failOn 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/commands/extension.c b/src/backend/commands/extension.c index a587a1bc03e..87e4dd82453 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-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 0ec3ff0fd6b..6497393c032 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -84,6 +84,7 @@ #include "utils/float.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" @@ -195,6 +196,19 @@ 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 const char *show_data_directory_mode(void); +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(char **newval, void **extra, GucSource source); +static void assign_recovery_target(const char *newval, void *extra); +static bool check_recovery_target_xid(char **newval, void **extra, GucSource source); +static void assign_recovery_target_xid(const char *newval, void *extra); +static bool check_recovery_target_time(char **newval, void **extra, GucSource source); +static void assign_recovery_target_time(const char *newval, void *extra); +static bool check_recovery_target_name(char **newval, void **extra, GucSource source); +static void assign_recovery_target_name(const char *newval, void *extra); +static bool check_recovery_target_lsn(char **newval, void **extra, GucSource source); +static void assign_recovery_target_lsn(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, @@ -442,6 +456,7 @@ const struct config_enum_entry ssl_protocol_versions_info[] = { */ extern const struct config_enum_entry wal_level_options[]; extern const struct config_enum_entry archive_mode_options[]; +extern const struct config_enum_entry recovery_target_action_options[]; extern const struct config_enum_entry sync_method_options[]; extern const struct config_enum_entry dynamic_shared_memory_options[]; @@ -533,6 +548,13 @@ static int wal_block_size; static bool data_checksums; static bool integer_datetimes; static bool assert_enabled; +static char *recovery_target_timeline_string; +static char *recovery_target_string; +static char *recovery_target_xid_string; +static char *recovery_target_time_string; +static char *recovery_target_name_string; +static char *recovery_target_lsn_string; + /* should be static, but commands/variable.c needs to get at this */ char *role_string; @@ -616,6 +638,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 */ @@ -1638,6 +1664,16 @@ static struct config_bool ConfigureNamesBool[] = }, { + {"recovery_target_inclusive", PGC_POSTMASTER, 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 @@ -1974,6 +2010,17 @@ static struct config_int ConfigureNamesInt[] = }, { + {"recovery_min_apply_delay", PGC_POSTMASTER, REPLICATION_STANDBY, + gettext_noop("Sets the minimum delay for applying 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 sending server."), NULL, @@ -3292,6 +3339,123 @@ static struct config_string ConfigureNamesString[] = }, { + {"restore_command", PGC_POSTMASTER, 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_POSTMASTER, WAL_ARCHIVE_RECOVERY, + gettext_noop("Sets the shell command that will be executed at every restart point."), + NULL + }, + &archiveCleanupCommand, + "", + NULL, NULL, NULL + }, + + { + {"recovery_end_command", PGC_POSTMASTER, WAL_ARCHIVE_RECOVERY, + gettext_noop("Sets the shell command that will be executed once at the end of recovery."), + NULL + }, + &recoveryEndCommand, + "", + NULL, NULL, NULL + }, + + { + {"recovery_target_timeline", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Specifies the timeline to recovery into."), + NULL + }, + &recovery_target_timeline_string, + "", + check_recovery_target_timeline, assign_recovery_target_timeline, NULL + }, + + { + {"recovery_target", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Set to 'immediate' to end recovery as soon as a consistent state is reached."), + NULL + }, + &recovery_target_string, + "", + check_recovery_target, assign_recovery_target, NULL + }, + { + {"recovery_target_xid", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Sets the transaction ID up to which recovery will proceed."), + NULL + }, + &recovery_target_xid_string, + "", + check_recovery_target_xid, assign_recovery_target_xid, NULL + }, + { + {"recovery_target_time", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Sets the time stamp up to which recovery will proceed."), + NULL + }, + &recovery_target_time_string, + "", + check_recovery_target_time, assign_recovery_target_time, NULL + }, + { + {"recovery_target_name", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Sets the named restore point up to which recovery will proceed."), + NULL + }, + &recovery_target_name_string, + "", + check_recovery_target_name, assign_recovery_target_name, NULL + }, + { + {"recovery_target_lsn", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Sets the LSN of the write-ahead log location up to which recovery will proceed."), + NULL + }, + &recovery_target_lsn_string, + "", + check_recovery_target_lsn, assign_recovery_target_lsn, NULL + }, + + { + {"promote_trigger_file", PGC_POSTMASTER, REPLICATION_STANDBY, + gettext_noop("Specifies a file name whose presence ends recovery in the standby."), + NULL + }, + &PromoteTriggerFile, + "", + NULL, NULL, NULL + }, + + { + {"primary_conninfo", PGC_POSTMASTER, REPLICATION_STANDBY, + gettext_noop("Sets the connection string to be used to connect to the sending server."), + NULL, + GUC_SUPERUSER_ONLY + }, + &PrimaryConnInfo, + "", + NULL, NULL, NULL + }, + + { + {"primary_slot_name", PGC_POSTMASTER, REPLICATION_STANDBY, + gettext_noop("Sets the name of the replication slot to use on the sending server."), + NULL + }, + &PrimarySlotName, + "", + check_primary_slot_name, NULL, NULL + }, + + { {"client_encoding", PGC_USERSET, CLIENT_CONN_LOCALE, gettext_noop("Sets the client's character set encoding."), NULL, @@ -4072,6 +4236,16 @@ static struct config_enum ConfigureNamesEnum[] = }, { + {"recovery_target_action", PGC_POSTMASTER, WAL_RECOVERY_TARGET, + gettext_noop("Sets the action to perform upon reaching the recovery target."), + NULL + }, + &recoveryTargetAction, + RECOVERY_TARGET_ACTION_PAUSE, recovery_target_action_options, + NULL, NULL, NULL + }, + + { {"trace_recovery_messages", PGC_SIGHUP, DEVELOPER_OPTIONS, gettext_noop("Enables logging of recovery-related debugging information."), gettext_noop("Each level includes all the levels that follow it. The later" @@ -10838,4 +11012,251 @@ show_data_directory_mode(void) return buf; } +static bool +check_recovery_target_timeline(char **newval, void **extra, GucSource source) +{ + RecoveryTargetTimeLineGoal rttg = RECOVERY_TARGET_TIMELINE_CONTROLFILE; + RecoveryTargetTimeLineGoal *myextra; + + if (strcmp(*newval, "") == 0) + rttg = RECOVERY_TARGET_TIMELINE_CONTROLFILE; + else if (strcmp(*newval, "latest") == 0) + rttg = RECOVERY_TARGET_TIMELINE_LATEST; + else + { + errno = 0; + strtoul(*newval, NULL, 0); + if (errno == EINVAL || errno == ERANGE) + { + GUC_check_errdetail("recovery_target_timeline is not a valid number"); + return false; + } + rttg = RECOVERY_TARGET_TIMELINE_NUMERIC; + } + + myextra = (RecoveryTargetTimeLineGoal *) guc_malloc(ERROR, sizeof(RecoveryTargetTimeLineGoal)); + *myextra = rttg; + *extra = (void *) myextra; + + return true; +} + +static void +assign_recovery_target_timeline(const char *newval, void *extra) +{ + recoveryTargetTimeLineGoal = *((RecoveryTargetTimeLineGoal *) extra); + if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC) + recoveryTargetTLIRequested = (TimeLineID) strtoul(newval, NULL, 0); + else + recoveryTargetTLIRequested = 0; +} + +static bool +check_recovery_target(char **newval, void **extra, GucSource source) +{ + if (strcmp(*newval, "immediate") != 0 && strcmp(*newval, "") != 0) + { + GUC_check_errdetail("The only allowed value is \"immediate\"."); + return false; + } + return true; +} + +static void +assign_recovery_target(const char *newval, void *extra) +{ + if (newval && strcmp(newval, "") != 0) + recoveryTarget = RECOVERY_TARGET_IMMEDIATE; + else + /* + * Reset recoveryTarget to RECOVERY_TARGET_UNSET to proper handle user + * setting multiple recovery_target with blank value on last. + */ + recoveryTarget = RECOVERY_TARGET_UNSET; +} + +static bool +check_recovery_target_xid(char **newval, void **extra, GucSource source) +{ + if (strcmp(*newval, "") != 0) + { + TransactionId xid; + TransactionId *myextra; + + errno = 0; + xid = (TransactionId) strtoul(*newval, NULL, 0); + if (errno == EINVAL || errno == ERANGE) + return false; + + myextra = (TransactionId *) guc_malloc(ERROR, sizeof(TransactionId)); + *myextra = xid; + *extra = (void *) myextra; + } + return true; +} + +static void +assign_recovery_target_xid(const char *newval, void *extra) +{ + if (newval && strcmp(newval, "") != 0) + { + recoveryTarget = RECOVERY_TARGET_XID; + recoveryTargetXid = *((TransactionId *) extra); + } + else + recoveryTarget = RECOVERY_TARGET_UNSET; +} + +static bool +check_recovery_target_time(char **newval, void **extra, GucSource source) +{ + if (strcmp(*newval, "") != 0) + { + TimestampTz time; + TimestampTz *myextra; + MemoryContext oldcontext = CurrentMemoryContext; + + /* reject some special values */ + if (strcmp(*newval, "epoch") == 0 || + strcmp(*newval, "infinity") == 0 || + strcmp(*newval, "-infinity") == 0 || + strcmp(*newval, "now") == 0 || + strcmp(*newval, "today") == 0 || + strcmp(*newval, "tomorrow") == 0 || + strcmp(*newval, "yesterday") == 0) + { + return false; + } + + 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; + } + return true; +} + +static void +assign_recovery_target_time(const char *newval, void *extra) +{ + if (newval && strcmp(newval, "") != 0) + { + recoveryTarget = RECOVERY_TARGET_TIME; + recoveryTargetTime = *((TimestampTz *) extra); + } + else + recoveryTarget = RECOVERY_TARGET_UNSET; +} + +static bool +check_recovery_target_name(char **newval, void **extra, GucSource source) +{ + /* Use the value of newval directly */ + if (strlen(*newval) >= MAXFNAMELEN) + { + GUC_check_errdetail("recovery_target_name is too long (maximum %d characters)", + MAXFNAMELEN - 1); + return false; + } + return true; +} + +static void +assign_recovery_target_name(const char *newval, void *extra) +{ + if (newval && strcmp(newval, "") != 0) + { + recoveryTarget = RECOVERY_TARGET_NAME; + recoveryTargetName = (char *) newval; + } + else + recoveryTarget = RECOVERY_TARGET_UNSET; +} + +static bool +check_recovery_target_lsn(char **newval, void **extra, GucSource source) +{ + if (strcmp(*newval, "") != 0) + { + 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; + } + return true; +} + +static void +assign_recovery_target_lsn(const char *newval, void *extra) +{ + if (newval && strcmp(newval, "") != 0) + { + recoveryTarget = RECOVERY_TARGET_LSN; + recoveryTargetLSN = *((XLogRecPtr *) extra); + } + else + recoveryTarget = RECOVERY_TARGET_UNSET; +} + +static bool +check_primary_slot_name(char **newval, void **extra, GucSource source) +{ + if (*newval && strcmp(*newval, "") != 0 && + !ReplicationSlotValidateName(*newval, WARNING)) + 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 3038fe627b2..ee9ec6a1200 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -228,6 +228,45 @@ #archive_timeout = 0 # force a logfile segment switch after this # number of seconds; 0 disables +# - Archive Recovery - + +# These are only used in recovery mode. + +#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' + # (change requires restart) +#archive_cleanup_command = '' # command to execute at every restartpoint + # (change requires restart) +#recovery_end_command = '' # command to execute at completion of recovery + # (change requires restart) + +# - Recovery Target - + +# Set these only when performing a targeted recovery. + +#recovery_target = '' # 'immediate' to end recovery as soon as a + # consistent state is reached + # (change requires restart) +#recovery_target_name = '' # the named restore point to which recovery will proceed + # (change requires restart) +#recovery_target_time = '' # the time stamp up to which recovery will proceed + # (change requires restart) +#recovery_target_xid = '' # the transaction ID up to which recovery will proceed + # (change requires restart) +#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed + # (change requires restart) +#recovery_target_inclusive = on # Specifies whether to stop: + # just after the specified recovery target (on) + # just before the recovery target (off) + # (change requires restart) +#recovery_target_timeline = '' # unset means read from control file (default), + # or set to 'latest' or timeline ID + # (change requires restart) +#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' + # (change requires restart) + #------------------------------------------------------------------------------ # REPLICATION @@ -261,6 +300,12 @@ # These settings are ignored on a master server. +#primary_conninfo = '' # connection string to sending server + # (change requires restart) +#primary_slot_name = '' # replication slot on sending server + # (change requires restart) +#promote_trigger_file = '' # file name whose presence ends recovery + # (change requires restart) #hot_standby = on # "off" disallows queries during recovery # (change requires restart) #max_standby_archive_delay = 30s # max delay before canceling queries @@ -278,6 +323,8 @@ # in milliseconds; 0 disables #wal_retrieve_retry_interval = 5s # time to wait before retrying to # retrieve WAL after a failed attempt +#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery + # (change requires restart) # - Subscribers - diff --git a/src/bin/pg_archivecleanup/pg_archivecleanup.c b/src/bin/pg_archivecleanup/pg_archivecleanup.c index 51904c6c2af..c62478dbd6f 100644 --- a/src/bin/pg_archivecleanup/pg_archivecleanup.c +++ b/src/bin/pg_archivecleanup/pg_archivecleanup.c @@ -265,7 +265,7 @@ usage(void) printf(_(" -x EXT clean up files if they have this extension\n")); printf(_(" -?, --help show this help, then exit\n")); printf(_("\n" - "For use as archive_cleanup_command in recovery.conf when standby_mode = on:\n" + "For use as archive_cleanup_command in postgresql.conf:\n" " archive_cleanup_command = 'pg_archivecleanup [OPTION]... ARCHIVELOCATION %%r'\n" "e.g.\n" " archive_cleanup_command = 'pg_archivecleanup /mnt/server/archiverdir %%r'\n")); diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c index d6fef38760e..af1c4de58f9 100644 --- a/src/bin/pg_basebackup/pg_basebackup.c +++ b/src/bin/pg_basebackup/pg_basebackup.c @@ -131,7 +131,7 @@ 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; /* Function headers */ @@ -346,7 +346,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 configuration for replication\n")); printf(_(" -T, --tablespace-mapping=OLDDIR=NEWDIR\n" " relocate tablespace in OLDDIR to NEWDIR\n")); printf(_(" --waldir=WALDIR location for the write-ahead log directory\n")); @@ -974,6 +974,9 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) bool basetablespace = PQgetisnull(res, rownum, 0); bool in_tarhdr = true; bool skip_file = false; + bool is_postgresql_auto_conf = false; + bool found_postgresql_auto_conf = false; + int file_padding_len = 0; size_t tarhdrsz = 0; pgoff_t filesz = 0; @@ -1113,8 +1116,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. @@ -1126,19 +1129,31 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) if (basetablespace && writerecoveryconf) { char header[512]; - int padding; - tarCreateHeader(header, "recovery.conf", NULL, - recoveryconfcontents->len, + if (!found_postgresql_auto_conf) + { + int padding; + + tarCreateHeader(header, "postgresql.auto.conf", NULL, + recoveryconfcontents->len, + pg_file_create_mode, 04000, 02000, + time(NULL)); + + padding = ((recoveryconfcontents->len + 511) & ~511) - recoveryconfcontents->len; + + WRITE_TAR_DATA(header, sizeof(header)); + WRITE_TAR_DATA(recoveryconfcontents->data, recoveryconfcontents->len); + if (padding) + WRITE_TAR_DATA(zerobuf, padding); + } + + tarCreateHeader(header, "standby.signal", NULL, + 0, /* zero-length file */ pg_file_create_mode, 04000, 02000, time(NULL)); - padding = ((recoveryconfcontents->len + 511) & ~511) - recoveryconfcontents->len; - WRITE_TAR_DATA(header, sizeof(header)); - WRITE_TAR_DATA(recoveryconfcontents->data, recoveryconfcontents->len); - if (padding) - WRITE_TAR_DATA(zerobuf, padding); + WRITE_TAR_DATA(zerobuf, 511); } /* 2 * 512 bytes empty data at end of file */ @@ -1182,8 +1197,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); @@ -1191,7 +1206,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. * @@ -1235,29 +1250,46 @@ 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 - * padded to the next multiple of 512 + * look at the file metadata: we may want append + * recovery info into postgresql.auto.conf and skip + * standby.signal file. In both cases we must + * calculate tar padding */ - int padding; - - skip_file = (strcmp(&tarhdr[0], "recovery.conf") == 0); + skip_file = (strcmp(&tarhdr[0], "standby.signal") == 0); + is_postgresql_auto_conf = (strcmp(&tarhdr[0], "postgresql.auto.conf") == 0); filesz = read_tar_number(&tarhdr[124], 12); + file_padding_len = ((filesz + 511) & ~511) - filesz; + + if (is_postgresql_auto_conf && writerecoveryconf) + { + /* replace tar header */ + char header[512]; - padding = ((filesz + 511) & ~511) - filesz; - filesz += padding; + tarCreateHeader(header, "postgresql.auto.conf", NULL, + filesz + recoveryconfcontents->len, + pg_file_create_mode, 04000, 02000, + time(NULL)); + + WRITE_TAR_DATA(header, sizeof(header)); + } + else + { + /* copy stream with padding */ + filesz += file_padding_len; + + if (!skip_file) + { + /* + * If we're not skipping the file, write the + * tar header unmodified. + */ + WRITE_TAR_DATA(tarhdr, 512); + } + } /* Next part is the file, not the header */ in_tarhdr = false; - - /* - * If we're not skipping the file, write the tar - * header unmodified. - */ - if (!skip_file) - WRITE_TAR_DATA(tarhdr, 512); } } else @@ -1281,6 +1313,32 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) pos += bytes2write; filesz -= bytes2write; } + else if (is_postgresql_auto_conf && writerecoveryconf) + { + /* append recovery config to postgresql.auto.conf */ + int padding; + int tailsize; + + tailsize = (512 - file_padding_len) + recoveryconfcontents->len; + padding = ((tailsize + 511) & ~511) - tailsize; + + WRITE_TAR_DATA(recoveryconfcontents->data, recoveryconfcontents->len); + + if (padding) + { + char zerobuf[512]; + + MemSet(zerobuf, 0, sizeof(zerobuf)); + WRITE_TAR_DATA(zerobuf, padding); + } + + /* skip original file padding */ + is_postgresql_auto_conf = false; + skip_file = true; + filesz += file_padding_len; + + found_postgresql_auto_conf = true; + } else { /* @@ -1289,6 +1347,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum) */ in_tarhdr = true; skip_file = false; + is_postgresql_auto_conf = false; tarhdrsz = 0; filesz = 0; } @@ -1614,7 +1673,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) @@ -1638,8 +1697,6 @@ GenerateRecoveryConf(PGconn *conn) disconnect_and_exit(1); } - appendPQExpBufferStr(recoveryconfcontents, "standby_mode = 'on'\n"); - initPQExpBuffer(&conninfo_buf); for (option = connOptions; option && option->keyword; option++) { @@ -1698,8 +1755,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) @@ -1707,12 +1765,12 @@ WriteRecoveryConf(void) char filename[MAXPGPATH]; FILE *cf; - sprintf(filename, "%s/recovery.conf", basedir); + snprintf(filename, MAXPGPATH, "%s/%s", basedir, "postgresql.auto.conf"); - cf = fopen(filename, "w"); + cf = fopen(filename, "a"); if (cf == NULL) { - fprintf(stderr, _("%s: could not create file \"%s\": %s\n"), progname, filename, strerror(errno)); + fprintf(stderr, _("%s: could not open file \"%s\": %s\n"), progname, filename, strerror(errno)); disconnect_and_exit(1); } @@ -1725,6 +1783,16 @@ WriteRecoveryConf(void) } fclose(cf); + + snprintf(filename, MAXPGPATH, "%s/%s", basedir, "standby.signal"); + 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); } @@ -1780,7 +1848,7 @@ BaseBackup(void) } /* - * Build contents of recovery.conf if requested + * Build contents of configuration file if requested */ if (writerecoveryconf) GenerateRecoveryConf(conn); @@ -2094,7 +2162,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 2211d90c6f9..3e1c3863c4f 100644 --- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl +++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl @@ -358,19 +358,16 @@ 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/postgresql.auto.conf", 'postgresql.auto.conf exists'); +ok(-f "$tempdir/backupR/standby.signal", 'standby.signal was created'); +my $recovery_conf = slurp_file "$tempdir/backupR/postgresql.auto.conf"; rmtree("$tempdir/backupR"); 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'); + 'postgresql.auto.conf sets primary_conninfo'); $node->command_ok( [ 'pg_basebackup', '-D', "$tempdir/backupxd" ], @@ -478,9 +475,9 @@ $node->command_ok( ], 'pg_basebackup with replication slot and -R runs'); like( - slurp_file("$tempdir/backupxs_sl_R/recovery.conf"), + slurp_file("$tempdir/backupxs_sl_R/postgresql.auto.conf"), qr/^primary_slot_name = 'slot1'\n/m, - 'recovery.conf sets primary_slot_name'); + 'recovery conf file sets primary_slot_name'); my $checksum = $node->safe_psql('postgres', 'SHOW data_checksums;'); is($checksum, 'on', 'checksums are enabled'); diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm index 1dce56d0352..3d07da5d949 100644 --- a/src/bin/pg_rewind/RewindTest.pm +++ b/src/bin/pg_rewind/RewindTest.pm @@ -159,12 +159,13 @@ 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' )); + $node_standby->set_standby_mode(); + # Start standby $node_standby->start; @@ -270,12 +271,13 @@ 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' )); + $node_master->set_standby_mode(); + # Restart the master to check that rewind went correctly $node_master->start; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index e01d12eb7c8..f3a7ba4d421 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -75,7 +75,7 @@ extern HotStandbyState standbyState; /* * Recovery target type. - * Only set during a Point in Time recovery, not when standby_mode = on + * Only set during a Point in Time recovery, not when in standby mode. */ typedef enum { @@ -87,6 +87,16 @@ typedef enum RECOVERY_TARGET_IMMEDIATE } RecoveryTargetType; +/* + * Recovery target TimeLine goal + */ +typedef enum +{ + RECOVERY_TARGET_TIMELINE_CONTROLFILE, + RECOVERY_TARGET_TIMELINE_LATEST, + RECOVERY_TARGET_TIMELINE_NUMERIC +} RecoveryTargetTimeLineGoal; + extern XLogRecPtr ProcLastRecPtr; extern XLogRecPtr XactLastRecEnd; extern PGDLLIMPORT XLogRecPtr XactLastCommitEnd; @@ -109,9 +119,32 @@ extern bool wal_compression; extern bool *wal_consistency_checking; extern char *wal_consistency_checking_string; extern bool log_checkpoints; +extern char *recoveryRestoreCommand; +extern char *recoveryEndCommand; +extern char *archiveCleanupCommand; +extern bool recoveryTargetInclusive; +extern int recoveryTargetAction; +extern int recovery_min_apply_delay; +extern char *PrimaryConnInfo; +extern char *PrimarySlotName; + +/* indirectly set via GUC system */ +extern TransactionId recoveryTargetXid; +extern TimestampTz recoveryTargetTime; +extern char *recoveryTargetName; +extern XLogRecPtr recoveryTargetLSN; +extern RecoveryTargetType recoveryTarget; +extern char *PromoteTriggerFile; +extern RecoveryTargetTimeLineGoal recoveryTargetTimeLineGoal; +extern TimeLineID recoveryTargetTLIRequested; +extern TimeLineID recoveryTargetTLI; extern int CheckPointSegments; +/* option set locally in startup process only when signal files exist */ +extern bool StandbyModeRequested; +extern bool StandbyMode; + /* Archive modes */ typedef enum ArchiveMode { @@ -319,8 +352,8 @@ extern void do_pg_abort_backup(void); extern SessionBackupState get_backup_status(void); /* File path names (all relative to $PGDATA) */ -#define RECOVERY_COMMAND_FILE "recovery.conf" -#define RECOVERY_COMMAND_DONE "recovery.done" +#define RECOVERY_SIGNAL_FILE "recovery.signal" +#define STANDBY_SIGNAL_FILE "standby.signal" #define BACKUP_LABEL_FILE "backup_label" #define BACKUP_LABEL_OLD "backup_label.old" diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index 668d9efd357..6f9fdb6a5fb 100644 --- a/src/include/utils/guc_tables.h +++ b/src/include/utils/guc_tables.h @@ -69,6 +69,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/port/quotes.c b/src/port/quotes.c index 29770c7a00e..94b63b9bb64 100644 --- a/src/port/quotes.c +++ b/src/port/quotes.c @@ -19,7 +19,7 @@ * Escape (by doubling) any single quotes or backslashes in given string * * Note: this is used to process postgresql.conf entries and to quote - * string literals in pg_basebackup for creating recovery.conf. + * string literals in pg_basebackup for writing the recovery configuration. * Since postgresql.conf strings are defined to treat backslashes as escapes, * we have to double backslashes here. * diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm index efdebc3877c..8a2c6fc1221 100644 --- a/src/test/perl/PostgresNode.pm +++ b/src/test/perl/PostgresNode.pm @@ -635,8 +635,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. - Streaming replication can be enabled on this node by passing the keyword parameter has_streaming => 1. This is disabled by default. @@ -834,10 +832,10 @@ sub enable_streaming 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 )); + $self->set_standby_mode(); return; } @@ -863,10 +861,26 @@ sub enable_restoring : qq{cp "$path/%f" "%p"}; $self->append_conf( - 'recovery.conf', qq( + 'postgresql.conf', qq( restore_command = '$copy_command' -standby_mode = on )); + $self->set_standby_mode(); + return; +} + +=pod + +=item $node->set_standby_mode() + +Place standby.signal file. + +=cut + +sub set_standby_mode +{ + my ($self) = @_; + + $self->append_conf('standby.signal', ''); return; } diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl index 8dff5fc7202..beb45551a24 100644 --- a/src/test/recovery/t/001_stream_rep.pl +++ b/src/test/recovery/t/001_stream_rep.pl @@ -131,7 +131,7 @@ is( $node_master->psql( qq[SELECT pg_create_physical_replication_slot('$slotname_1');]), 0, 'physical slot created on master'); -$node_standby_1->append_conf('recovery.conf', +$node_standby_1->append_conf('postgresql.conf', "primary_slot_name = $slotname_1"); $node_standby_1->append_conf('postgresql.conf', "wal_receiver_status_interval = 1"); @@ -142,7 +142,7 @@ is( $node_standby_1->psql( qq[SELECT pg_create_physical_replication_slot('$slotname_2');]), 0, 'physical slot created on intermediate replica'); -$node_standby_2->append_conf('recovery.conf', +$node_standby_2->append_conf('postgresql.conf', "primary_slot_name = $slotname_2"); $node_standby_2->append_conf('postgresql.conf', "wal_receiver_status_interval = 1"); diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl index e867479f206..f6f2e8b745b 100644 --- a/src/test/recovery/t/003_recovery_targets.pl +++ b/src/test/recovery/t/003_recovery_targets.pl @@ -23,7 +23,7 @@ sub test_recovery_standby foreach my $param_item (@$recovery_params) { - $node_standby->append_conf('recovery.conf', qq($param_item)); + $node_standby->append_conf('postgresql.conf', qq($param_item)); } $node_standby->start; diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl index a7ccb7b4a3b..79cbffb827b 100644 --- a/src/test/recovery/t/004_timeline_switch.pl +++ b/src/test/recovery/t/004_timeline_switch.pl @@ -47,12 +47,10 @@ $node_standby_1->psql('postgres', "SELECT pg_promote(wait_seconds => 300)", is($psql_out, 't', "promotion of standby with pg_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( + 'postgresql.conf', qq( primary_conninfo='$connstr_1 application_name=@{[$node_standby_2->name]}' -standby_mode=on 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 8909c4548bf..6c85c928c10 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; diff --git a/src/test/recovery/t/009_twophase.pl b/src/test/recovery/t/009_twophase.pl index 9ea3bd65fc1..dac2d4ec0d6 100644 --- a/src/test/recovery/t/009_twophase.pl +++ b/src/test/recovery/t/009_twophase.pl @@ -230,7 +230,7 @@ is($psql_rc, '0', "Restore of prepared transaction on promoted standby"); # restart old master as new standby $cur_standby->enable_streaming($cur_master); $cur_standby->append_conf( - 'recovery.conf', qq( + 'postgresql.conf', qq( recovery_target_timeline='latest' )); $cur_standby->start; @@ -268,7 +268,7 @@ is($psql_out, '1', # restart old master as new standby $cur_standby->enable_streaming($cur_master); $cur_standby->append_conf( - 'recovery.conf', qq( + 'postgresql.conf', qq( recovery_target_timeline='latest' )); $cur_standby->start; @@ -308,7 +308,7 @@ is($psql_out, '1', # restart old master as new standby $cur_standby->enable_streaming($cur_master); $cur_standby->append_conf( - 'recovery.conf', qq( + 'postgresql.conf', qq( recovery_target_timeline='latest' )); $cur_standby->start; diff --git a/src/test/recovery/t/010_logical_decoding_timelines.pl b/src/test/recovery/t/010_logical_decoding_timelines.pl index a76eea86a5a..4fbd386332a 100644 --- a/src/test/recovery/t/010_logical_decoding_timelines.pl +++ b/src/test/recovery/t/010_logical_decoding_timelines.pl @@ -76,7 +76,7 @@ $node_replica->init_from_backup( $node_master, $backup_name, has_streaming => 1, has_restoring => 1); -$node_replica->append_conf('recovery.conf', +$node_replica->append_conf('postgresql.conf', q[primary_slot_name = 'phys_slot']); $node_replica->start; diff --git a/src/test/recovery/t/012_subtransactions.pl b/src/test/recovery/t/012_subtransactions.pl index efc23d05598..e26cc9c2ce1 100644 --- a/src/test/recovery/t/012_subtransactions.pl +++ b/src/test/recovery/t/012_subtransactions.pl @@ -120,7 +120,7 @@ is($psql_out, '8128', "Visible"); ($node_master, $node_standby) = ($node_standby, $node_master); $node_standby->enable_streaming($node_master); $node_standby->append_conf( - 'recovery.conf', qq( + 'postgresql.conf', qq( recovery_target_timeline='latest' )); $node_standby->start; @@ -171,7 +171,7 @@ is($psql_out, '-1', "Not visible"); ($node_master, $node_standby) = ($node_standby, $node_master); $node_standby->enable_streaming($node_master); $node_standby->append_conf( - 'recovery.conf', qq( + 'postgresql.conf', qq( recovery_target_timeline='latest' )); $node_standby->start; @@ -212,7 +212,7 @@ is($psql_out, '-1', "Not visible"); ($node_master, $node_standby) = ($node_standby, $node_master); $node_standby->enable_streaming($node_master); $node_standby->append_conf( - 'recovery.conf', qq( + 'postgresql.conf', qq( recovery_target_timeline='latest' )); $node_standby->start; |