On Tue, Aug 23, 2016 at 12:49 AM, Robert Haas <robertmh...@gmail.com> wrote: > On Mon, Aug 22, 2016 at 8:28 AM, Michael Paquier > <michael.paqu...@gmail.com> wrote: >> On Mon, Aug 22, 2016 at 7:12 PM, Adrien Nayrat <adrien.nay...@dalibo.com> >> wrote: >>> As Julien said, there is nothing to notice that error comes from >>> recovery.conf. >>> My fear would be that an user encounters an error like this. Il will be >>> difficult to link to the recovery.conf. >> >> Thinking a bit wider than that, we may want to know such context for >> normal GUC parameters as well, and that's not the case now. Perhaps >> there is actually a reason why that's not done for GUCs, but it seems >> that it would be useful there as well. That would give another reason >> to move all that under the GUC umbrella. > > Maybe so, but that's been tried multiple times without success. If > you think an error context is useful here, and I bet it is, I'd say > just add it and be done with it.
This has finished by being less ugly than I thought, so I implemented it as attached. Patch 0001 introduces recovery_target_lsn, and patch 0002 sets up an error context callback generating things like that on failure: FATAL: invalid input syntax for type pg_lsn: "popo" CONTEXT: line 11 of configuration file "recovery.conf", parameter "recovery_target_lsn" -- Michael
From 6f73e9b02ee9806dd73571e1190983748fcad03d Mon Sep 17 00:00:00 2001 From: Michael Paquier <mich...@otacoo.com> Date: Tue, 23 Aug 2016 16:12:03 +0900 Subject: [PATCH 1/2] Introduce recovery_target_lsn in recovery.conf This new recovery target can be used to stop recovery once a given WAL position is reached (LSN). Tests and documentation are added. --- doc/src/sgml/recovery-config.sgml | 24 +++++++-- src/backend/access/transam/recovery.conf.sample | 6 ++- src/backend/access/transam/xlog.c | 69 +++++++++++++++++++++++++ src/include/access/xlog.h | 1 + src/test/recovery/t/003_recovery_targets.pl | 28 ++++++++-- 5 files changed, 119 insertions(+), 9 deletions(-) diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml index 26af221..ad977d3 100644 --- a/doc/src/sgml/recovery-config.sgml +++ b/doc/src/sgml/recovery-config.sgml @@ -157,9 +157,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows 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 <varname>recovery_target</>, - <varname>recovery_target_name</>, <varname>recovery_target_time</>, or - <varname>recovery_target_xid</> can be used; if more than one of these - is specified in the configuration file, the last entry will be used. + <varname>recovery_target_lsn</>, <varname>recovery_target_name</>, + <varname>recovery_target_time</>, or <varname>recovery_target_xid</> + can be used; if more than one of these is specified in the configuration + file, the last entry will be used. </para> <variablelist> @@ -232,6 +233,23 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows </para> </listitem> </varlistentry> + + <varlistentry id="recovery-target-lsn" xreflabel="recovery_target_lsn"> + <term><varname>recovery_target_lsn</varname> (<type>pg_lsn</type>) + <indexterm> + <primary><varname>recovery_target_lsn</> recovery parameter</primary> + </indexterm> + </term> + <listitem> + <para> + This parameter specifies the LSN of the write-ahead log stream up to + which recovery will proceed. The precise stopping point is also + influenced by <xref linkend="recovery-target-inclusive">. This + parameter is parsed using the system data type + <link linkend="datatype-pg-lsn"><type>pg_lsn</></link>. + </para> + </listitem> + </varlistentry> </variablelist> <para> diff --git a/src/backend/access/transam/recovery.conf.sample b/src/backend/access/transam/recovery.conf.sample index b777400..7a16751 100644 --- a/src/backend/access/transam/recovery.conf.sample +++ b/src/backend/access/transam/recovery.conf.sample @@ -67,8 +67,8 @@ # must set a recovery target. # # You may set a recovery target either by transactionId, by name, -# or by timestamp. Recovery may either include or exclude the -# transaction(s) with the recovery target value (ie, stop either +# by timestamp or by WAL position (LSN). Recovery may either include or +# exclude the transaction(s) with the recovery target value (ie, stop either # just after or just before the given target, respectively). # # @@ -78,6 +78,8 @@ # #recovery_target_xid = '' # +#recovery_target_lsn = '' # e.g. '0/70006B8' +# #recovery_target_inclusive = true # # diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f13f9c1..e3985ee 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -67,6 +67,7 @@ #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" @@ -254,6 +255,7 @@ 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; @@ -275,6 +277,7 @@ static bool fast_promote = false; */ static TransactionId recoveryStopXid; static TimestampTz recoveryStopTime; +static XLogRecPtr recoveryStopLSN; static char recoveryStopName[MAXFNAMELEN]; static bool recoveryStopAfter; @@ -5078,6 +5081,23 @@ readRecoveryCommandFile(void) (errmsg_internal("recovery_target_name = '%s'", recoveryTargetName))); } + else if (strcmp(item->name, "recovery_target_lsn") == 0) + { + recoveryTarget = RECOVERY_TARGET_LSN; + + /* + * Convert the LSN string given by the user to XLogRecPtr form. + */ + recoveryTargetLSN = + DatumGetLSN(DirectFunctionCall3(pg_lsn_in, + CStringGetDatum(item->value), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1))); + ereport(DEBUG2, + (errmsg_internal("recovery_target_lsn = '%X/%X'", + (uint32) (recoveryTargetLSN >> 32), + (uint32) recoveryTargetLSN))); + } else if (strcmp(item->name, "recovery_target") == 0) { if (strcmp(item->value, "immediate") == 0) @@ -5388,11 +5408,29 @@ recoveryStopsBefore(XLogReaderState *record) recoveryStopAfter = false; recoveryStopXid = InvalidTransactionId; + recoveryStopLSN = InvalidXLogRecPtr; recoveryStopTime = 0; recoveryStopName[0] = '\0'; return true; } + /* Check if target LSN has been reached */ + if (recoveryTarget == RECOVERY_TARGET_LSN && + !recoveryTargetInclusive && + record->ReadRecPtr >= recoveryTargetLSN) + { + recoveryStopAfter = false; + recoveryStopXid = InvalidTransactionId; + recoveryStopLSN = record->ReadRecPtr; + recoveryStopTime = 0; + recoveryStopName[0] = '\0'; + ereport(LOG, + (errmsg("recovery stopping before WAL position (LSN) \"%X/%X\"", + (uint32) (recoveryStopLSN >> 32), + (uint32) recoveryStopLSN))); + return true; + } + /* Otherwise we only consider stopping before COMMIT or ABORT records. */ if (XLogRecGetRmid(record) != RM_XACT_ID) return false; @@ -5467,6 +5505,7 @@ recoveryStopsBefore(XLogReaderState *record) recoveryStopAfter = false; recoveryStopXid = recordXid; recoveryStopTime = recordXtime; + recoveryStopLSN = InvalidXLogRecPtr; recoveryStopName[0] = '\0'; if (isCommit) @@ -5520,6 +5559,7 @@ recoveryStopsAfter(XLogReaderState *record) { recoveryStopAfter = true; recoveryStopXid = InvalidTransactionId; + recoveryStopLSN = InvalidXLogRecPtr; (void) getRecordTimestamp(record, &recoveryStopTime); strlcpy(recoveryStopName, recordRestorePointData->rp_name, MAXFNAMELEN); @@ -5531,6 +5571,23 @@ recoveryStopsAfter(XLogReaderState *record) } } + /* Check if the target LSN has been reached */ + if (recoveryTarget == RECOVERY_TARGET_LSN && + recoveryTargetInclusive && + record->ReadRecPtr >= recoveryTargetLSN) + { + recoveryStopAfter = true; + recoveryStopXid = InvalidTransactionId; + recoveryStopLSN = record->ReadRecPtr; + recoveryStopTime = 0; + recoveryStopName[0] = '\0'; + ereport(LOG, + (errmsg("recovery stopping after WAL position (LSN) \"%X/%X\"", + (uint32) (recoveryStopLSN >> 32), + (uint32) recoveryStopLSN))); + return true; + } + if (rmid != RM_XACT_ID) return false; @@ -5586,6 +5643,7 @@ recoveryStopsAfter(XLogReaderState *record) recoveryStopAfter = true; recoveryStopXid = recordXid; recoveryStopTime = recordXtime; + recoveryStopLSN = InvalidXLogRecPtr; recoveryStopName[0] = '\0'; if (xact_info == XLOG_XACT_COMMIT || @@ -5617,6 +5675,7 @@ recoveryStopsAfter(XLogReaderState *record) recoveryStopAfter = true; recoveryStopXid = InvalidTransactionId; recoveryStopTime = 0; + recoveryStopLSN = InvalidXLogRecPtr; recoveryStopName[0] = '\0'; return true; } @@ -6043,6 +6102,11 @@ StartupXLOG(void) ereport(LOG, (errmsg("starting point-in-time recovery to \"%s\"", recoveryTargetName))); + else if (recoveryTarget == RECOVERY_TARGET_LSN) + ereport(LOG, + (errmsg("starting point-in-time recovery to WAL position (LSN) \"%X/%X\"", + (uint32) (recoveryTargetLSN >> 32), + (uint32) recoveryTargetLSN))); else if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE) ereport(LOG, (errmsg("starting point-in-time recovery to earliest consistent point"))); @@ -7112,6 +7176,11 @@ StartupXLOG(void) "%s %s\n", recoveryStopAfter ? "after" : "before", timestamptz_to_str(recoveryStopTime)); + else if (recoveryTarget == RECOVERY_TARGET_LSN) + snprintf(reason, sizeof(reason), + "at LSN %X/%X\n", + (uint32 ) (recoveryStopLSN >> 32), + (uint32) recoveryStopLSN); else if (recoveryTarget == RECOVERY_TARGET_NAME) snprintf(reason, sizeof(reason), "at restore point \"%s\"", diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 14b7f7f..c9f332c 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -83,6 +83,7 @@ typedef enum RECOVERY_TARGET_XID, RECOVERY_TARGET_TIME, RECOVERY_TARGET_NAME, + RECOVERY_TARGET_LSN, RECOVERY_TARGET_IMMEDIATE } RecoveryTargetType; diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl index d1f6d78..a82545b 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 => 7; +use Test::More tests => 9; # Create and test a standby from given backup, with a certain # recovery target. @@ -86,6 +86,16 @@ my $lsn4 = $node_master->safe_psql('postgres', "SELECT pg_create_restore_point('$recovery_name');"); +# And now for a recovery target LSN +$node_master->safe_psql('postgres', + "INSERT INTO tab_int VALUES (generate_series(4001,5000))"); +my $recovery_lsn = $node_master->safe_psql('postgres', "SELECT pg_current_xlog_location()"); +my $lsn5 = + $node_master->safe_psql('postgres', "SELECT pg_current_xlog_location();"); + +$node_master->safe_psql('postgres', + "INSERT INTO tab_int VALUES (generate_series(5001,6000))"); + # Force archiving of WAL file $node_master->safe_psql('postgres', "SELECT pg_switch_xlog()"); @@ -102,6 +112,9 @@ test_recovery_standby('time', 'standby_3', $node_master, \@recovery_params, @recovery_params = ("recovery_target_name = '$recovery_name'"); test_recovery_standby('name', 'standby_4', $node_master, \@recovery_params, "4000", $lsn4); +@recovery_params = ("recovery_target_lsn = '$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 @@ -111,16 +124,23 @@ test_recovery_standby('name', 'standby_4', $node_master, \@recovery_params, "recovery_target_xid = '$recovery_txid'", "recovery_target_time = '$recovery_time'"); test_recovery_standby('name + XID + time', - 'standby_5', $node_master, \@recovery_params, "3000", $lsn3); + '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_6', $node_master, \@recovery_params, "2000", $lsn2); + '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_7', $node_master, \@recovery_params, "4000", $lsn4); + '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); -- 2.9.3
From 82a591ca8be457f8be4f13080ededc5daabf3670 Mon Sep 17 00:00:00 2001 From: Michael Paquier <mich...@otacoo.com> Date: Tue, 23 Aug 2016 16:28:05 +0900 Subject: [PATCH 2/2] Introduce error callback when parsing recovery.conf At the moment an error occurs when recovery.conf is parsed, generate a context message related to the parameter used, as follows: CONTEXT: line 11 of configuration file "recovery.conf", parameter foo This is useful to understand from where a parsing failure is coming in recovery.conf, particularly for parameters like recovery_target_time and recovery_target_lsn that do not have recovery-specific error messages. --- src/backend/access/transam/xlog.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index e3985ee..9097841 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -4935,6 +4935,20 @@ str_time(pg_time_t tnow) } /* + * error context callback for parsing of recovery.conf + * + * The argument for the error context must be of type ConfigVariable. + */ +static void +readRecoveryErrorCallback(void *arg) +{ + ConfigVariable *item = (ConfigVariable *) arg; + + errcontext("line %d of configuration file \"%s\", parameter \"%s\"", + item->sourceline, item->filename, item->name); +} + +/* * See if there is a recovery command file (recovery.conf), and if so * read in parameters for archive recovery and XLOG streaming. * @@ -4950,6 +4964,7 @@ readRecoveryCommandFile(void) *head = NULL, *tail = NULL; bool recoveryTargetActionSet = false; + ErrorContextCallback errcallback; fd = AllocateFile(RECOVERY_COMMAND_FILE, "r"); @@ -4971,8 +4986,15 @@ readRecoveryCommandFile(void) FreeFile(fd); + /* set up callback to identify file parsing information on failure */ + errcallback.callback = readRecoveryErrorCallback; + errcallback.previous = error_context_stack; + error_context_stack = &errcallback; + for (item = head; item; item = item->next) { + errcallback.arg = (void *) item; + if (strcmp(item->name, "restore_command") == 0) { recoveryRestoreCommand = pstrdup(item->value); @@ -5179,6 +5201,9 @@ readRecoveryCommandFile(void) item->name))); } + /* Done, clean up */ + error_context_stack = errcallback.previous; + /* * Check for compulsory parameters */ -- 2.9.3
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers