From c016ce728139be95bb0dc7c4e5640507334c2339 Mon Sep 17 00:00:00 2001 From: Simon Riggs Date: Tue, 8 Feb 2011 19:39:08 +0000 Subject: [PATCH] Named restore points in recovery. Users can record named points, then 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 | 7 +- doc/src/sgml/func.sgml | 10 ++ doc/src/sgml/recovery-config.sgml | 25 ++- .../access/transam/recovery.conf.sample | 7 +- src/backend/access/transam/xlog.c | 170 +++++++++++++++++- src/include/access/xlog.h | 4 +- src/include/access/xlog_internal.h | 1 + src/include/catalog/pg_control.h | 1 + src/include/catalog/pg_proc.h | 1 + 9 files changed, 209 insertions(+), 17 deletions(-) diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml index 62830cb6ca..0fbf1225aa 100644 --- a/doc/src/sgml/backup.sgml +++ b/doc/src/sgml/backup.sgml @@ -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 recovery.conf. You can specify the stop point, known as the 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. diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index e26e7614c9..897078ac3e 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -13914,6 +13914,9 @@ SELECT set_config('log_statement_stats', 'off', false); backup + + pg_create_restore_point + pg_current_xlog_insert_location @@ -13951,6 +13954,13 @@ SELECT set_config('log_statement_stats', 'off', false); + + + pg_create_restore_point(name text) + + text + Create a named point for performing restore (restricted to superusers) + pg_current_xlog_insert_location() diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml index 190e589032..0706f79121 100644 --- a/doc/src/sgml/recovery-config.sgml +++ b/doc/src/sgml/recovery-config.sgml @@ -143,6 +143,25 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows Recovery Target Settings + + recovery_target_name + (string) + + + recovery_target_name recovery parameter + + + + This parameter specifies the named restore point, created with + pg_create_restore_point() to which recovery will proceed. + At most one of recovery_target_name, + or + can be specified. The default is to + recover to the end of the WAL log. + + + + recovery_target_time (timestamp) @@ -154,7 +173,8 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows This parameter specifies the time stamp up to which recovery will proceed. - At most one of recovery_target_time and + At most one of recovery_target_time, + or 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 recovery_target_xid and + At most one of recovery_target_xid, + or 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 f8f6f9b108..0243b51fdf 100644 --- a/src/backend/access/transam/recovery.conf.sample +++ b/src/backend/access/transam/recovery.conf.sample @@ -66,11 +66,14 @@ # 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 = '' diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 5ba3e26f81..72a1a158c0 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -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"))); @@ -7989,6 +8062,29 @@ RequestXLogSwitch(void) return RecPtr; } +/* + * 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; @@ -9080,6 +9187,51 @@ pg_switch_xlog(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text(location)); } +/* + * 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) * diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 122e96b5d1..e7adead9a2 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -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); diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h index 6390113de3..eeccdce31d 100644 --- a/src/include/access/xlog_internal.h +++ b/src/include/access/xlog_internal.h @@ -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); diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index ddf139857d..fb458acf83 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -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 /* diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 734f43a1e4..30ff1b5bb9 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -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_ )); -- GitLab