OSDN Git Service

Named restore points in recovery. Users can record named points, then
authorSimon Riggs <simon@2ndQuadrant.com>
Tue, 8 Feb 2011 19:39:08 +0000 (19:39 +0000)
committerSimon Riggs <simon@2ndQuadrant.com>
Tue, 8 Feb 2011 19:39:08 +0000 (19:39 +0000)
new recovery.conf parameter recovery_target_name allows PITR to
specify named points as recovery targets.

Jaime Casanova, reviewed by Euler Taveira de Oliveira, plus minor edits

doc/src/sgml/backup.sgml
doc/src/sgml/func.sgml
doc/src/sgml/recovery-config.sgml
src/backend/access/transam/recovery.conf.sample
src/backend/access/transam/xlog.c
src/include/access/xlog.h
src/include/access/xlog_internal.h
src/include/catalog/pg_control.h
src/include/catalog/pg_proc.h

index 62830cb..0fbf122 100644 (file)
@@ -1086,9 +1086,10 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
     the junior DBA dropped your main transaction table), just specify the
     required stopping point in <filename>recovery.conf</>.  You can specify
     the stop point, known as the <quote>recovery target</>, either by
-    date/time or by completion of a specific transaction ID.  As of this
-    writing only the date/time option is very usable, since there are no tools
-    to help you identify with any accuracy which transaction ID to use.
+    date/time, named restore point or by completion of a specific transaction
+    ID.  As of this writing only the date/time and named restore point options
+    are very usable, since there are no tools to help you identify with any
+    accuracy which transaction ID to use.
    </para>
 
    <note>
index e26e761..897078a 100644 (file)
@@ -13915,6 +13915,9 @@ SELECT set_config('log_statement_stats', 'off', false);
     <primary>backup</primary>
    </indexterm>
    <indexterm>
+    <primary>pg_create_restore_point</primary>
+   </indexterm>
+   <indexterm>
     <primary>pg_current_xlog_insert_location</primary>
    </indexterm>
    <indexterm>
@@ -13953,6 +13956,13 @@ SELECT set_config('log_statement_stats', 'off', false);
      <tbody>
       <row>
        <entry>
+        <literal><function>pg_create_restore_point(<parameter>name</> <type>text</>)</function></literal>
+        </entry>
+       <entry><type>text</type></entry>
+       <entry>Create a named point for performing restore (restricted to superusers)</entry>
+      </row>
+      <row>
+       <entry>
         <literal><function>pg_current_xlog_insert_location()</function></literal>
         </entry>
        <entry><type>text</type></entry>
index 190e589..0706f79 100644 (file)
@@ -143,6 +143,25 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
     <title>Recovery Target Settings</title>
      <variablelist>
 
+     <varlistentry id="recovery-target-name" xreflabel="recovery_target_name">
+      <term><varname>recovery_target_name</varname>
+           (<type>string</type>)
+      </term>
+      <indexterm>
+        <primary><varname>recovery_target_name</> recovery parameter</primary>
+      </indexterm>
+      <listitem>
+       <para>
+               This parameter specifies the named restore point, created with
+               <function>pg_create_restore_point()</> to which recovery will proceed.
+               At most one of <varname>recovery_target_name</>,
+               <xref linkend="recovery-target-time"> or
+               <xref linkend="recovery-target-xid"> can be specified.  The default is to
+               recover to the end of the WAL log.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="recovery-target-time" xreflabel="recovery_target_time">
       <term><varname>recovery_target_time</varname>
            (<type>timestamp</type>)
@@ -154,7 +173,8 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
        <para>
         This parameter specifies the time stamp up to which recovery
         will proceed.
-        At most one of <varname>recovery_target_time</> and
+        At most one of <varname>recovery_target_time</>,
+               <xref linkend="recovery-target-name"> or
         <xref linkend="recovery-target-xid"> can be specified.
         The default is to recover to the end of the WAL log.
         The precise stopping point is also influenced by
@@ -176,7 +196,8 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         start, transactions can complete in a different numeric order.
         The transactions that will be recovered are those that committed
         before (and optionally including) the specified one.
-        At most one of <varname>recovery_target_xid</> and
+        At most one of <varname>recovery_target_xid</>,
+               <xref linkend="recovery-target-name"> or
         <xref linkend="recovery-target-time"> can be specified.
         The default is to recover to the end of the WAL log.
         The precise stopping point is also influenced by
index f8f6f9b..0243b51 100644 (file)
 # 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, or
-# by timestamp. Recovery may either include or exclude the
+# 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
 # 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 = ''
index 5ba3e26..72a1a15 100644 (file)
@@ -185,15 +185,17 @@ static bool recoveryTargetInclusive = true;
 static bool recoveryPauseAtTarget = true;
 static TransactionId recoveryTargetXid;
 static TimestampTz recoveryTargetTime;
+static char *recoveryTargetName;
 
 /* options taken from recovery.conf for XLOG streaming */
 static bool StandbyMode = false;
 static char *PrimaryConnInfo = NULL;
 static char *TriggerFile = NULL;
 
-/* if recoveryStopsHere returns true, it saves actual stop xid/time here */
+/* if recoveryStopsHere returns true, it saves actual stop xid/time/name here */
 static TransactionId recoveryStopXid;
 static TimestampTz recoveryStopTime;
+static char recoveryStopName[MAXFNAMELEN];
 static bool recoveryStopAfter;
 
 /*
@@ -551,6 +553,13 @@ typedef struct xl_parameter_change
        int                     wal_level;
 } xl_parameter_change;
 
+/* logs restore point */
+typedef struct xl_restore_point
+{
+       TimestampTz     rp_time;
+       char            rp_name[MAXFNAMELEN];
+} xl_restore_point;
+
 /*
  * Flags set by interrupt handlers for later service in the redo loop.
  */
@@ -4391,6 +4400,13 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
                                 xlogfname,
                                 recoveryStopAfter ? "after" : "before",
                                 timestamptz_to_str(recoveryStopTime));
+       else if (recoveryTarget == RECOVERY_TARGET_NAME)
+               snprintf(buffer, sizeof(buffer),
+                               "%s%u\t%s\tat restore point \"%s\"\n",
+                                (srcfd < 0) ? "" : "\n",
+                                parentTLI,
+                                xlogfname,
+                                recoveryStopName);
        else
                snprintf(buffer, sizeof(buffer),
                                 "%s%u\t%s\tno recovery target specified\n",
@@ -5178,10 +5194,11 @@ readRecoveryCommandFile(void)
                else if (strcmp(item->name, "recovery_target_time") == 0)
                {
                        /*
-                        * if recovery_target_xid specified, then this overrides
-                        * recovery_target_time
+                        * if recovery_target_xid or recovery_target_name specified, then
+                        * this overrides recovery_target_time
                         */
-                       if (recoveryTarget == RECOVERY_TARGET_XID)
+                       if (recoveryTarget == RECOVERY_TARGET_XID ||
+                                       recoveryTarget == RECOVERY_TARGET_NAME)
                                continue;
                        recoveryTarget = RECOVERY_TARGET_TIME;
 
@@ -5197,6 +5214,26 @@ readRecoveryCommandFile(void)
                                        (errmsg("recovery_target_time = '%s'",
                                                        timestamptz_to_str(recoveryTargetTime))));
                }
+               else if (strcmp(item->name, "recovery_target_name") == 0)
+               {
+                       /*
+                        * if recovery_target_xid specified, then this overrides
+                        * recovery_target_name
+                        */
+                       if (recoveryTarget == RECOVERY_TARGET_XID)
+                               continue;
+                       recoveryTarget = RECOVERY_TARGET_NAME;
+
+                       recoveryTargetName = pstrdup(item->value);
+                       if (strlen(recoveryTargetName) >= MAXFNAMELEN)
+                               ereport(FATAL,
+                                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                                errmsg("recovery_target_name is too long")));
+
+                       ereport(DEBUG2,
+                                       (errmsg("recovery_target_name = '%s'",
+                                                       recoveryTargetName)));
+               }
                else if (strcmp(item->name, "recovery_target_inclusive") == 0)
                {
                        /*
@@ -5411,8 +5448,8 @@ exitArchiveRecovery(TimeLineID endTLI, uint32 endLogId, uint32 endLogSeg)
  * Returns TRUE if we are stopping, FALSE otherwise.  On TRUE return,
  * *includeThis is set TRUE if we should apply this record before stopping.
  *
- * We also track the timestamp of the latest applied COMMIT/ABORT record
- * in XLogCtl->recoveryLastXTime, for logging purposes.
+ * We also track the timestamp of the latest applied COMMIT/ABORT/RESTORE POINT
+ * record in XLogCtl->recoveryLastXTime, for logging purposes.
  * Also, some information is saved in recoveryStopXid et al for use in
  * annotating the new timeline's history file.
  */
@@ -5422,9 +5459,10 @@ recoveryStopsHere(XLogRecord *record, bool *includeThis)
        bool            stopsHere;
        uint8           record_info;
        TimestampTz recordXtime;
+       char            recordRPName[MAXFNAMELEN];
 
-       /* We only consider stopping at COMMIT or ABORT records */
-       if (record->xl_rmid != RM_XACT_ID)
+       /* We only consider stopping at COMMIT, ABORT or RESTORE POINT records */
+       if (record->xl_rmid != RM_XACT_ID && record->xl_rmid != RM_XLOG_ID)
                return false;
        record_info = record->xl_info & ~XLR_INFO_MASK;
        if (record_info == XLOG_XACT_COMMIT)
@@ -5441,6 +5479,14 @@ recoveryStopsHere(XLogRecord *record, bool *includeThis)
                recordXactAbortData = (xl_xact_abort *) XLogRecGetData(record);
                recordXtime = recordXactAbortData->xact_time;
        }
+       else if (record_info == XLOG_RESTORE_POINT)
+       {
+               xl_restore_point *recordRestorePointData;
+
+               recordRestorePointData = (xl_restore_point *) XLogRecGetData(record);
+               recordXtime = recordRestorePointData->rp_time;
+               strncpy(recordRPName, recordRestorePointData->rp_name, MAXFNAMELEN);
+       }
        else
                return false;
 
@@ -5466,6 +5512,20 @@ recoveryStopsHere(XLogRecord *record, bool *includeThis)
                if (stopsHere)
                        *includeThis = recoveryTargetInclusive;
        }
+       else if (recoveryTarget == RECOVERY_TARGET_NAME)
+       {
+               /*
+                * there can be many restore points that share the same name, so we stop
+                * at the first one
+                */
+               stopsHere = (strcmp(recordRPName, recoveryTargetName) == 0);
+
+               /*
+                * ignore recoveryTargetInclusive because this is not a transaction
+                * record
+                */
+               *includeThis = false;
+       }
        else
        {
                /*
@@ -5500,7 +5560,7 @@ recoveryStopsHere(XLogRecord *record, bool *includeThis)
                                                                recoveryStopXid,
                                                                timestamptz_to_str(recoveryStopTime))));
                }
-               else
+               else if (record_info == XLOG_XACT_ABORT)
                {
                        if (recoveryStopAfter)
                                ereport(LOG,
@@ -5513,6 +5573,15 @@ recoveryStopsHere(XLogRecord *record, bool *includeThis)
                                                                recoveryStopXid,
                                                                timestamptz_to_str(recoveryStopTime))));
                }
+               else
+               {
+                       strncpy(recoveryStopName, recordRPName, MAXFNAMELEN);
+
+                       ereport(LOG,
+                                       (errmsg("recovery stopping at restore point \"%s\", time %s",
+                                                               recoveryStopName,
+                                                               timestamptz_to_str(recoveryStopTime))));
+               }
 
                if (recoveryStopAfter)
                        SetLatestXTime(recordXtime);
@@ -5900,6 +5969,10 @@ StartupXLOG(void)
                        ereport(LOG,
                                        (errmsg("starting point-in-time recovery to %s",
                                                        timestamptz_to_str(recoveryTargetTime))));
+               else if (recoveryTarget == RECOVERY_TARGET_NAME)
+                       ereport(LOG,
+                                       (errmsg("starting point-in-time recovery to \"%s\"",
+                                                       recoveryTargetName)));
                else
                        ereport(LOG,
                                        (errmsg("starting archive recovery")));
@@ -7990,6 +8063,29 @@ RequestXLogSwitch(void)
 }
 
 /*
+ * Write a RESTORE POINT record
+ */
+XLogRecPtr
+XLogRestorePoint(const char *rpName)
+{
+       XLogRecPtr                              RecPtr;
+       XLogRecData                             rdata;
+       xl_restore_point                xlrec;
+
+       xlrec.rp_time = GetCurrentTimestamp();
+       strncpy(xlrec.rp_name, rpName, MAXFNAMELEN);
+
+       rdata.buffer = InvalidBuffer;
+       rdata.data = (char *) &xlrec;
+       rdata.len = sizeof(xl_restore_point);
+       rdata.next = NULL;
+
+       RecPtr = XLogInsert(RM_XLOG_ID, XLOG_RESTORE_POINT, &rdata);
+
+       return RecPtr;
+}
+
+/*
  * Check if any of the GUC parameters that are critical for hot standby
  * have changed, and update the value in pg_control file if necessary.
  */
@@ -8181,6 +8277,10 @@ xlog_redo(XLogRecPtr lsn, XLogRecord *record)
        {
                /* nothing to do here */
        }
+       else if (info == XLOG_RESTORE_POINT)
+       {
+               /* nothing to do here */
+       }
        else if (info == XLOG_BACKUP_END)
        {
                XLogRecPtr      startpoint;
@@ -8283,6 +8383,13 @@ xlog_desc(StringInfo buf, uint8 xl_info, char *rec)
        {
                appendStringInfo(buf, "xlog switch");
        }
+       else if (info == XLOG_RESTORE_POINT)
+       {
+               xl_restore_point *xlrec = (xl_restore_point *) rec;
+
+               appendStringInfo(buf, "restore point: %s", xlrec->rp_name);
+
+       }
        else if (info == XLOG_BACKUP_END)
        {
                XLogRecPtr      startpoint;
@@ -9081,6 +9188,51 @@ pg_switch_xlog(PG_FUNCTION_ARGS)
 }
 
 /*
+ * pg_create_restore_point: a named point for restore
+ */
+Datum
+pg_create_restore_point(PG_FUNCTION_ARGS)
+{
+       text            *restore_name = PG_GETARG_TEXT_P(0);
+       char            *restore_name_str;
+       XLogRecPtr      restorepoint;
+       char            location[MAXFNAMELEN];
+
+       if (!superuser())
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                (errmsg("must be superuser to create a restore point"))));
+
+       if (RecoveryInProgress())
+               ereport(ERROR,
+                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                (errmsg("recovery is in progress"),
+                                 errhint("WAL control functions cannot be executed during recovery."))));
+
+       if (!XLogIsNeeded())
+               ereport(ERROR,
+                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                         errmsg("WAL level not sufficient for creating a restore point"),
+                                errhint("wal_level must be set to \"archive\" or \"hot_standby\" at server start.")));
+
+       restore_name_str = text_to_cstring(restore_name);
+
+       if (strlen(restore_name_str) >= MAXFNAMELEN)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("value too long for restore point")));
+
+       restorepoint = XLogRestorePoint(restore_name_str);
+
+       /*
+        * As a convenience, return the WAL location of the restore point record
+        */
+       snprintf(location, sizeof(location), "%X/%X",
+                       restorepoint.xlogid, restorepoint.xrecoff);
+       PG_RETURN_TEXT_P(cstring_to_text(location));
+}
+
+/*
  * Report the current WAL write location (same format as pg_start_backup etc)
  *
  * This is useful for determining how much of WAL is visible to an external
index 122e96b..e7adead 100644 (file)
@@ -184,7 +184,8 @@ typedef enum
 {
        RECOVERY_TARGET_UNSET,
        RECOVERY_TARGET_XID,
-       RECOVERY_TARGET_TIME
+       RECOVERY_TARGET_TIME,
+       RECOVERY_TARGET_NAME
 } RecoveryTargetType;
 
 extern XLogRecPtr XactLastRecEnd;
@@ -302,6 +303,7 @@ extern void InitXLOGAccess(void);
 extern void CreateCheckPoint(int flags);
 extern bool CreateRestartPoint(int flags);
 extern void XLogPutNextOid(Oid nextOid);
+extern XLogRecPtr XLogRestorePoint(const char *rpName);
 extern XLogRecPtr GetRedoRecPtr(void);
 extern XLogRecPtr GetInsertRecPtr(void);
 extern XLogRecPtr GetFlushRecPtr(void);
index 6390113..eeccdce 100644 (file)
@@ -267,6 +267,7 @@ extern XLogRecPtr RequestXLogSwitch(void);
 extern Datum pg_start_backup(PG_FUNCTION_ARGS);
 extern Datum pg_stop_backup(PG_FUNCTION_ARGS);
 extern Datum pg_switch_xlog(PG_FUNCTION_ARGS);
+extern Datum pg_create_restore_point(PG_FUNCTION_ARGS);
 extern Datum pg_current_xlog_location(PG_FUNCTION_ARGS);
 extern Datum pg_current_xlog_insert_location(PG_FUNCTION_ARGS);
 extern Datum pg_last_xlog_receive_location(PG_FUNCTION_ARGS);
index ddf1398..fb458ac 100644 (file)
@@ -59,6 +59,7 @@ typedef struct CheckPoint
 #define XLOG_SWITCH                                            0x40
 #define XLOG_BACKUP_END                                        0x50
 #define XLOG_PARAMETER_CHANGE                  0x60
+#define        XLOG_RESTORE_POINT                              0x70
 
 
 /*
index 734f43a..30ff1b5 100644 (file)
@@ -3397,6 +3397,7 @@ DATA(insert OID = 2173 ( pg_stop_backup                   PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2
 DESCR("finish taking an online backup");
 DATA(insert OID = 2848 ( pg_switch_xlog                        PGNSP PGUID 12 1 0 0 f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ pg_switch_xlog _null_ _null_ _null_ ));
 DESCR("switch to new xlog file");
+DATA(insert OID = 3098 ( pg_create_restore_point       PGNSP PGUID 12 1 0 0 f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_create_restore_point _null_ _null_ _null_ ));
 DATA(insert OID = 2849 ( pg_current_xlog_location      PGNSP PGUID 12 1 0 0 f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ pg_current_xlog_location _null_ _null_ _null_ ));
 DESCR("current xlog write location");
 DATA(insert OID = 2852 ( pg_current_xlog_insert_location       PGNSP PGUID 12 1 0 0 f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ pg_current_xlog_insert_location _null_ _null_ _null_ ));