On 06/09/2016 02:33 PM, Michael Paquier wrote: > On Wed, May 25, 2016 at 1:32 AM, Michael Paquier > <michael.paqu...@gmail.com> wrote: >> On Tue, May 24, 2016 at 9:29 AM, Alvaro Herrera >> <alvhe...@2ndquadrant.com> wrote: >>> Christoph Berg wrote: >>>> Re: Michael Paquier 2016-05-24 >>>> <CAB7nPqQRXsC8=ozh6GpjLnpZ=meoouzoaabzx28n2bjsmv2...@mail.gmail.com> >>>>> Yeah, that's really something that covers only a narrow case, though >>>>> if we don't have it when we need it we're limited to some hacks. >>>>> Perhaps people who have the advanced level to use such a thing have >>>>> the level to use hacks anyway.. >>>> >>>> I'd think recovery_target_lsn would be more useful in practice than >>>> the existing recovery_target_xid. So I don't see why it shouldn't just >>>> be added, also given it's likely very unobtrusive to do so. >>> >>> Also, see >>> http://www.postgresql.org/message-id/56bd0e4e.5050...@2ndquadrant.com >> >> Looking at xlog.c it is not that complicated, and we could add tests >> in 003_recovery_targets.pl at the same time. Perhaps somebody looking >> for a first participation would be interested in this small project? > > Oh, well. I have implemented it as attached by introducing > recovery_target_lsn as a new recovery parameter. This takes into > account recovery_target_inclusive and stops at the precise point of a > record without being influenced by the xact records, in a way similar > recovery_target_name. Tests and documentation are added, and this is > part of the next CF. > > > >
Hi, I reviewed this patch rebased to deal with f6ced51f9188ad5806219471a0b40a91dde923aa, and minor adjustment (see below) It do the job. However if you use an incorrect recovery_target_lsn you get this message : "FATAL: invalid input syntax for type pg_lsn: "0RT5/4"" I added a PG_TRY/PG_CATCH section in order to get a more explicit message : FATAL: wrong recovery_target_lsn (must be pg_lsn type) I am not sure if it is the best solution? Thanks to Julien Rouhaud for his help. -- Adrien NAYRAT http://dalibo.com - http://dalibo.org
diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml index 26af221..e131b7d 100644 --- a/doc/src/sgml/recovery-config.sgml +++ b/doc/src/sgml/recovery-config.sgml @@ -232,6 +232,21 @@ 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">. + </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..32f6903 100644 --- a/src/backend/access/transam/recovery.conf.sample +++ b/src/backend/access/transam/recovery.conf.sample @@ -67,7 +67,7 @@ # 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 +# by timestamp or by 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..762c7bb 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,34 @@ 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. + */ + PG_TRY(); + { + recoveryTargetLSN = + DatumGetLSN(DirectFunctionCall3(pg_lsn_in, + CStringGetDatum(item->value), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1))); + } + PG_CATCH(); + { + ereport(FATAL, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("wrong recovery_target_lsn (must be pg_lsn type)", + MAXFNAMELEN - 1))); + } + PG_END_TRY(); + 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,8 +5419,26 @@ 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 LSN \"%X/%X\"", + (uint32) (recoveryStopLSN >> 32), + (uint32) recoveryStopLSN))); return true; } @@ -5467,6 +5516,7 @@ recoveryStopsBefore(XLogReaderState *record) recoveryStopAfter = false; recoveryStopXid = recordXid; recoveryStopTime = recordXtime; + recoveryStopLSN = InvalidXLogRecPtr; recoveryStopName[0] = '\0'; if (isCommit) @@ -5520,6 +5570,7 @@ recoveryStopsAfter(XLogReaderState *record) { recoveryStopAfter = true; recoveryStopXid = InvalidTransactionId; + recoveryStopLSN = InvalidXLogRecPtr; (void) getRecordTimestamp(record, &recoveryStopTime); strlcpy(recoveryStopName, recordRestorePointData->rp_name, MAXFNAMELEN); @@ -5531,6 +5582,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 LSN \"%X/%X\"", + (uint32) (recoveryStopLSN >> 32), + (uint32) recoveryStopLSN))); + return true; + } + if (rmid != RM_XACT_ID) return false; @@ -5586,6 +5654,7 @@ recoveryStopsAfter(XLogReaderState *record) recoveryStopAfter = true; recoveryStopXid = recordXid; recoveryStopTime = recordXtime; + recoveryStopLSN = InvalidXLogRecPtr; recoveryStopName[0] = '\0'; if (xact_info == XLOG_XACT_COMMIT || @@ -5617,6 +5686,7 @@ recoveryStopsAfter(XLogReaderState *record) recoveryStopAfter = true; recoveryStopXid = InvalidTransactionId; recoveryStopTime = 0; + recoveryStopLSN = InvalidXLogRecPtr; recoveryStopName[0] = '\0'; return true; } @@ -6043,6 +6113,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 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 +7187,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);
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers