Hi,
Here is a patch that implements "named restore points".
It allows DBAs to specify an exact point to which they can recover
but that point will have a name, so they have a better control of when
they want to stop recovery (ie: DBA's won't depend of remember
specific times, dates and such).
This adds a new function: pg_create_restore_point(text) (i'm not
wedded with the name so if someone wants to suggest something better,
that's fine with me), a new xlog record and a new recovery_target
parameter in recovery.conf
--
Jaime Casanova www.2ndQuadrant.com
Professional PostgreSQL: Soporte y capacitación de PostgreSQL
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index db7c834..e12720c 100644
*** a/doc/src/sgml/backup.sgml
--- b/doc/src/sgml/backup.sgml
*************** restore_command = 'cp /mnt/server/archiv
*** 1075,1083 ****
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.
</para>
<note>
--- 1075,1084 ----
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, a named restore point or by completion of a specific transaction ID.
! As of this writing, there are no tools to help you identify with any accuracy
! which transaction ID to use so only date/time and named restore points are
! useful.
</para>
<note>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 04769f1..d335104 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT set_config('log_statement_stats',
*** 13930,13935 ****
--- 13930,13938 ----
<primary>pg_switch_xlog</primary>
</indexterm>
<indexterm>
+ <primary>pg_create_restore_point</primary>
+ </indexterm>
+ <indexterm>
<primary>pg_xlogfile_name</primary>
</indexterm>
<indexterm>
*************** SELECT set_config('log_statement_stats',
*** 13988,13993 ****
--- 13991,14003 ----
</row>
<row>
<entry>
+ <literal><function>pg_create_restore_point(<parameter>name</> <type>text</> )</function></literal>
+ </entry>
+ <entry><type>text</type></entry>
+ <entry>Establish a named point for restore (restricted to superusers)</entry>
+ </row>
+ <row>
+ <entry>
<literal><function>pg_xlogfile_name(<parameter>location</> <type>text</>)</function></literal>
</entry>
<entry><type>text</type></entry>
diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml
index bd9dfd1..b3fc001 100644
*** a/doc/src/sgml/recovery-config.sgml
--- b/doc/src/sgml/recovery-config.sgml
*************** restore_command = 'copy "C:\\server\\arc
*** 144,149 ****
--- 144,171 ----
<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 a 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.
+ The precise stopping point is also influenced by
+ <xref linkend="recovery-target-inclusive">.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="recovery-target-time" xreflabel="recovery_target_time">
<term><varname>recovery_target_time</varname>
(<type>timestamp</type>)
*************** restore_command = 'copy "C:\\server\\arc
*** 155,161 ****
<para>
This parameter specifies the time stamp up to which recovery
will proceed.
! At most one of <varname>recovery_target_time</> and
<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
--- 177,184 ----
<para>
This parameter specifies the time stamp up to which recovery
will proceed.
! 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
*************** restore_command = 'copy "C:\\server\\arc
*** 177,183 ****
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
<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
--- 200,207 ----
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</>,
! <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
diff --git a/src/backend/access/transam/recovery.conf.sample b/src/backend/access/transam/recovery.conf.sample
index f8f6f9b..552aae1 100644
*** a/src/backend/access/transam/recovery.conf.sample
--- b/src/backend/access/transam/recovery.conf.sample
***************
*** 66,76 ****
# 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
# transaction(s) with the recovery target value (ie, stop either
# just after or just before the given target, respectively).
#
#recovery_target_time = '' # e.g. '2004-07-14 22:39:00 EST'
#
#recovery_target_xid = ''
--- 66,78 ----
# 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, a named point
! # 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 = ''
+ #
#recovery_target_time = '' # e.g. '2004-07-14 22:39:00 EST'
#
#recovery_target_xid = ''
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 5b6a230..a84e3f4 100644
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
*************** static RecoveryTargetType recoveryTarget
*** 186,191 ****
--- 186,192 ----
static bool recoveryTargetInclusive = true;
static TransactionId recoveryTargetXid;
static TimestampTz recoveryTargetTime;
+ static char *recoveryTargetName;
/* options taken from recovery.conf for XLOG streaming */
static bool StandbyMode = false;
*************** static char *TriggerFile = NULL;
*** 195,200 ****
--- 196,202 ----
/* if recoveryStopsHere returns true, it saves actual stop xid/time here */
static TransactionId recoveryStopXid;
static TimestampTz recoveryStopTime;
+ static char recoveryStopNamedRestorePoint[MAXFNAMELEN];
static bool recoveryStopAfter;
/*
*************** typedef struct xl_parameter_change
*** 541,546 ****
--- 543,557 ----
int wal_level;
} xl_parameter_change;
+ /*
+ * named restore points
+ */
+ typedef struct xl_named_restore_points
+ {
+ TimestampTz xtime;
+ char name[MAXFNAMELEN];
+ } xl_named_restore_points;
+
/*
* Flags set by interrupt handlers for later service in the redo loop.
*/
*************** writeTimeLineHistory(TimeLineID newTLI,
*** 4378,4383 ****
--- 4389,4402 ----
xlogfname,
recoveryStopAfter ? "after" : "before",
timestamptz_to_str(recoveryStopTime));
+ else if (recoveryTarget == RECOVERY_TARGET_NAME)
+ snprintf(buffer, sizeof(buffer),
+ "%s%u\t%s\t%s named restore point %s\n",
+ (srcfd < 0) ? "" : "\n",
+ parentTLI,
+ xlogfname,
+ recoveryStopAfter ? "after" : "before",
+ recoveryStopNamedRestorePoint);
else
snprintf(buffer, sizeof(buffer),
"%s%u\t%s\tno recovery target specified\n",
*************** readRecoveryCommandFile(void)
*** 5101,5108 ****
--- 5120,5142 ----
ereport(DEBUG2,
(errmsg("recovery_target_timeline = latest")));
}
+ else if (strcmp(item->name, "recovery_target_name") == 0)
+ {
+ recoveryTargetName = pstrdup(item->value);
+ ereport(DEBUG2,
+ (errmsg("recovery_target_name = '%s'",
+ recoveryTargetName)));
+ recoveryTarget = RECOVERY_TARGET_NAME;
+ }
else if (strcmp(item->name, "recovery_target_xid") == 0)
{
+ /*
+ * if recovery_target_name specified, then this overrides
+ * recovery_target_xid
+ */
+ if (recoveryTarget == RECOVERY_TARGET_NAME)
+ continue;
+
errno = 0;
recoveryTargetXid = (TransactionId) strtoul(item->value, NULL, 0);
if (errno == EINVAL || errno == ERANGE)
*************** readRecoveryCommandFile(void)
*** 5117,5126 ****
else if (strcmp(item->name, "recovery_target_time") == 0)
{
/*
! * if recovery_target_xid specified, then this overrides
! * recovery_target_time
*/
! if (recoveryTarget == RECOVERY_TARGET_XID)
continue;
recoveryTarget = RECOVERY_TARGET_TIME;
--- 5151,5160 ----
else if (strcmp(item->name, "recovery_target_time") == 0)
{
/*
! * if recovery_target_xid or recovery_target_name specified,
! * then this overrides recovery_target_time
*/
! if (recoveryTarget == RECOVERY_TARGET_XID || recoveryTarget == RECOVERY_TARGET_NAME)
continue;
recoveryTarget = RECOVERY_TARGET_TIME;
*************** exitArchiveRecovery(TimeLineID endTLI, u
*** 5358,5371 ****
static bool
recoveryStopsHere(XLogRecord *record, bool *includeThis)
{
bool stopsHere;
uint8 record_info;
TimestampTz recordXtime;
! /* We only consider stopping at COMMIT or ABORT records */
if (record->xl_rmid != RM_XACT_ID)
! return false;
record_info = record->xl_info & ~XLR_INFO_MASK;
if (record_info == XLOG_XACT_COMMIT)
{
xl_xact_commit *recordXactCommitData;
--- 5392,5418 ----
static bool
recoveryStopsHere(XLogRecord *record, bool *includeThis)
{
+ bool couldStop;
bool stopsHere;
uint8 record_info;
TimestampTz recordXtime;
! /*
! * We could stop at COMMIT or ABORT records
! */
! couldStop = true;
if (record->xl_rmid != RM_XACT_ID)
! couldStop = false;
! /*
! * Or when we found a named restore point
! */
record_info = record->xl_info & ~XLR_INFO_MASK;
+ if ((record->xl_rmid == RM_XLOG_ID) && (record_info == XLOG_RESTORE_POINT))
+ couldStop = true;
+
+ if (!couldStop)
+ return false;
+
if (record_info == XLOG_XACT_COMMIT)
{
xl_xact_commit *recordXactCommitData;
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5380,5385 ****
--- 5427,5439 ----
recordXactAbortData = (xl_xact_abort *) XLogRecGetData(record);
recordXtime = recordXactAbortData->xact_time;
}
+ else if (record_info == XLOG_RESTORE_POINT)
+ {
+ xl_named_restore_points *recordNamedRestorePoint;
+
+ recordNamedRestorePoint = (xl_named_restore_points *) XLogRecGetData(record);
+ recordXtime = recordNamedRestorePoint->xtime;
+ }
else
return false;
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5405,5411 ****
if (stopsHere)
*includeThis = recoveryTargetInclusive;
}
! else
{
/*
* there can be many transactions that share the same commit time, so
--- 5459,5465 ----
if (stopsHere)
*includeThis = recoveryTargetInclusive;
}
! else if (recoveryTarget == RECOVERY_TARGET_TIME)
{
/*
* there can be many transactions that share the same commit time, so
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5419,5424 ****
--- 5473,5492 ----
if (stopsHere)
*includeThis = false;
}
+ else
+ {
+ char name[MAXFNAMELEN];
+
+ strncpy(name, ((xl_named_restore_points *) XLogRecGetData(record))->name, MAXFNAMELEN);
+ stopsHere = (strncmp(name, recoveryTargetName, MAXFNAMELEN) == 0);
+
+ /*
+ * Because this record does nothing but hold the restore point name
+ * applying or not this record is just waste so recoveryTargetInclusive
+ * is meaningless in this case
+ */
+ *includeThis = false;
+ }
if (stopsHere)
{
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5426,5431 ****
--- 5494,5502 ----
recoveryStopTime = recordXtime;
recoveryStopAfter = *includeThis;
+ if (recoveryTarget == RECOVERY_TARGET_NAME)
+ strncpy(recoveryStopNamedRestorePoint, recoveryTargetName, MAXFNAMELEN);
+
if (record_info == XLOG_XACT_COMMIT)
{
if (recoveryStopAfter)
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5439,5445 ****
recoveryStopXid,
timestamptz_to_str(recoveryStopTime))));
}
! else
{
if (recoveryStopAfter)
ereport(LOG,
--- 5510,5516 ----
recoveryStopXid,
timestamptz_to_str(recoveryStopTime))));
}
! else if (record_info == XLOG_XACT_ABORT)
{
if (recoveryStopAfter)
ereport(LOG,
*************** recoveryStopsHere(XLogRecord *record, bo
*** 5452,5457 ****
--- 5523,5533 ----
recoveryStopXid,
timestamptz_to_str(recoveryStopTime))));
}
+ else
+ ereport(LOG,
+ (errmsg("recovery stopping at named point %s, time %s",
+ recoveryStopNamedRestorePoint,
+ timestamptz_to_str(recoveryStopTime))));
if (recoveryStopAfter)
SetLatestXTime(recordXtime);
*************** XLogPutNextOid(Oid nextOid)
*** 7774,7779 ****
--- 7850,7877 ----
}
/*
+ * Write a restore point record
+ */
+ XLogRecPtr
+ XLogRestorePointName(char *name)
+ {
+ XLogRecPtr RecPtr;
+ XLogRecData rdata;
+ xl_named_restore_points xlrec;
+
+ xlrec.xtime = GetCurrentTimestamp();
+ strncpy(xlrec.name, name, MAXFNAMELEN);
+
+ rdata.data = (char *) &xlrec;
+ rdata.len = sizeof(xl_named_restore_points);
+ rdata.buffer = InvalidBuffer;
+ rdata.next = NULL;
+ RecPtr = XLogInsert(RM_XLOG_ID, XLOG_RESTORE_POINT, &rdata);
+
+ return RecPtr;
+ }
+
+ /*
* Write an XLOG SWITCH record.
*
* Here we just blindly issue an XLogInsert request for the record.
*************** xlog_redo(XLogRecPtr lsn, XLogRecord *re
*** 7988,7993 ****
--- 8086,8095 ----
{
/* nothing to do here */
}
+ else if (info == XLOG_RESTORE_POINT)
+ {
+ /* nothing to do here */
+ }
else if (info == XLOG_SWITCH)
{
/* nothing to do here */
*************** xlog_desc(StringInfo buf, uint8 xl_info,
*** 8127,8132 ****
--- 8229,8244 ----
xlrec.max_locks_per_xact,
wal_level_str);
}
+ else if (info == XLOG_RESTORE_POINT)
+ {
+ xl_named_restore_points xlrec;
+ char name[MAXFNAMELEN];
+
+ memcpy(&xlrec, rec, sizeof(xl_named_restore_points));
+ strncpy(name, xlrec.name, MAXFNAMELEN);
+
+ appendStringInfo(buf, "named restore point: '%s'", name);
+ }
else
appendStringInfo(buf, "UNKNOWN");
}
*************** do_pg_abort_backup(void)
*** 8755,8760 ****
--- 8867,8909 ----
}
/*
+ * 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 (!XLogArchivingActive())
+ ereport(NOTICE,
+ (errmsg("WAL archiving is not enabled; you must ensure that WAL segments are copied through other means for restore points to be usefull for you")));
+
+ restore_name_str = text_to_cstring(restore_name);
+ restorepoint = XLogRestorePointName(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));
+ }
+
+ /*
* pg_switch_xlog: switch to next xlog file
*/
Datum
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 74d3427..3394152 100644
*** a/src/include/access/xlog.h
--- b/src/include/access/xlog.h
*************** typedef enum
*** 184,190 ****
{
RECOVERY_TARGET_UNSET,
RECOVERY_TARGET_XID,
! RECOVERY_TARGET_TIME
} RecoveryTargetType;
extern XLogRecPtr XactLastRecEnd;
--- 184,191 ----
{
RECOVERY_TARGET_UNSET,
RECOVERY_TARGET_XID,
! RECOVERY_TARGET_TIME,
! RECOVERY_TARGET_NAME
} RecoveryTargetType;
extern XLogRecPtr XactLastRecEnd;
*************** extern void InitXLOGAccess(void);
*** 302,307 ****
--- 303,309 ----
extern void CreateCheckPoint(int flags);
extern bool CreateRestartPoint(int flags);
extern void XLogPutNextOid(Oid nextOid);
+ extern XLogRecPtr XLogRestorePointName(char *name);
extern XLogRecPtr GetRedoRecPtr(void);
extern XLogRecPtr GetInsertRecPtr(void);
extern XLogRecPtr GetFlushRecPtr(void);
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index ba64035..dd5a252 100644
*** a/src/include/access/xlog_internal.h
--- b/src/include/access/xlog_internal.h
*************** extern XLogRecPtr RequestXLogSwitch(void
*** 266,271 ****
--- 266,272 ----
*/
extern Datum pg_start_backup(PG_FUNCTION_ARGS);
extern Datum pg_stop_backup(PG_FUNCTION_ARGS);
+ extern Datum pg_create_restore_point(PG_FUNCTION_ARGS);
extern Datum pg_switch_xlog(PG_FUNCTION_ARGS);
extern Datum pg_current_xlog_location(PG_FUNCTION_ARGS);
extern Datum pg_current_xlog_insert_location(PG_FUNCTION_ARGS);
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index ddf1398..43c1abe 100644
*** a/src/include/catalog/pg_control.h
--- b/src/include/catalog/pg_control.h
*************** typedef struct CheckPoint
*** 59,64 ****
--- 59,65 ----
#define XLOG_SWITCH 0x40
#define XLOG_BACKUP_END 0x50
#define XLOG_PARAMETER_CHANGE 0x60
+ #define XLOG_RESTORE_POINT 0x70
/*
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f8b5d4d..b037109 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2172 ( pg_start_backup
*** 3395,3400 ****
--- 3395,3402 ----
DESCR("prepare for taking an online backup");
DATA(insert OID = 2173 ( pg_stop_backup PGNSP PGUID 12 1 0 0 f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ pg_stop_backup _null_ _null_ _null_ ));
DESCR("finish taking an online backup");
+ DATA(insert OID = 2847 ( 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_ ));
+ DESCR("create a named restore point");
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 = 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_ ));
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers