mirror of
https://github.com/postgres/postgres.git
synced 2025-07-27 12:41:57 +03:00
Add support for incremental backup.
To take an incremental backup, you use the new replication command UPLOAD_MANIFEST to upload the manifest for the prior backup. This prior backup could either be a full backup or another incremental backup. You then use BASE_BACKUP with the INCREMENTAL option to take the backup. pg_basebackup now has an --incremental=PATH_TO_MANIFEST option to trigger this behavior. An incremental backup is like a regular full backup except that some relation files are replaced with files with names like INCREMENTAL.${ORIGINAL_NAME}, and the backup_label file contains additional lines identifying it as an incremental backup. The new pg_combinebackup tool can be used to reconstruct a data directory from a full backup and a series of incremental backups. Patch by me. Reviewed by Matthias van de Meent, Dilip Kumar, Jakub Wartak, Peter Eisentraut, and Álvaro Herrera. Thanks especially to Jakub for incredibly helpful and extensive testing. Discussion: http://postgr.es/m/CA+TgmoYOYZfMCyOXFyC-P+-mdrZqm5pP2N7S-r0z3_402h9rsA@mail.gmail.com
This commit is contained in:
@ -76,11 +76,12 @@ Node *replication_parse_result;
|
||||
%token K_EXPORT_SNAPSHOT
|
||||
%token K_NOEXPORT_SNAPSHOT
|
||||
%token K_USE_SNAPSHOT
|
||||
%token K_UPLOAD_MANIFEST
|
||||
|
||||
%type <node> command
|
||||
%type <node> base_backup start_replication start_logical_replication
|
||||
create_replication_slot drop_replication_slot identify_system
|
||||
read_replication_slot timeline_history show
|
||||
read_replication_slot timeline_history show upload_manifest
|
||||
%type <list> generic_option_list
|
||||
%type <defelt> generic_option
|
||||
%type <uintval> opt_timeline
|
||||
@ -114,6 +115,7 @@ command:
|
||||
| read_replication_slot
|
||||
| timeline_history
|
||||
| show
|
||||
| upload_manifest
|
||||
;
|
||||
|
||||
/*
|
||||
@ -307,6 +309,15 @@ timeline_history:
|
||||
}
|
||||
;
|
||||
|
||||
/* UPLOAD_MANIFEST doesn't currently accept any arguments */
|
||||
upload_manifest:
|
||||
K_UPLOAD_MANIFEST
|
||||
{
|
||||
UploadManifestCmd *cmd = makeNode(UploadManifestCmd);
|
||||
|
||||
$$ = (Node *) cmd;
|
||||
}
|
||||
|
||||
opt_physical:
|
||||
K_PHYSICAL
|
||||
| /* EMPTY */
|
||||
@ -411,6 +422,7 @@ ident_or_keyword:
|
||||
| K_EXPORT_SNAPSHOT { $$ = "export_snapshot"; }
|
||||
| K_NOEXPORT_SNAPSHOT { $$ = "noexport_snapshot"; }
|
||||
| K_USE_SNAPSHOT { $$ = "use_snapshot"; }
|
||||
| K_UPLOAD_MANIFEST { $$ = "upload_manifest"; }
|
||||
;
|
||||
|
||||
%%
|
||||
|
@ -136,6 +136,7 @@ EXPORT_SNAPSHOT { return K_EXPORT_SNAPSHOT; }
|
||||
NOEXPORT_SNAPSHOT { return K_NOEXPORT_SNAPSHOT; }
|
||||
USE_SNAPSHOT { return K_USE_SNAPSHOT; }
|
||||
WAIT { return K_WAIT; }
|
||||
UPLOAD_MANIFEST { return K_UPLOAD_MANIFEST; }
|
||||
|
||||
{space}+ { /* do nothing */ }
|
||||
|
||||
@ -303,6 +304,7 @@ replication_scanner_is_replication_command(void)
|
||||
case K_DROP_REPLICATION_SLOT:
|
||||
case K_READ_REPLICATION_SLOT:
|
||||
case K_TIMELINE_HISTORY:
|
||||
case K_UPLOAD_MANIFEST:
|
||||
case K_SHOW:
|
||||
/* Yes; push back the first token so we can parse later. */
|
||||
repl_pushed_back_token = first_token;
|
||||
|
@ -58,6 +58,7 @@
|
||||
#include "access/xlogrecovery.h"
|
||||
#include "access/xlogutils.h"
|
||||
#include "backup/basebackup.h"
|
||||
#include "backup/basebackup_incremental.h"
|
||||
#include "catalog/pg_authid.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "commands/dbcommands.h"
|
||||
@ -137,6 +138,17 @@ bool wake_wal_senders = false;
|
||||
*/
|
||||
static XLogReaderState *xlogreader = NULL;
|
||||
|
||||
/*
|
||||
* If the UPLOAD_MANIFEST command is used to provide a backup manifest in
|
||||
* preparation for an incremental backup, uploaded_manifest will be point
|
||||
* to an object containing information about its contexts, and
|
||||
* uploaded_manifest_mcxt will point to the memory context that contains
|
||||
* that object and all of its subordinate data. Otherwise, both values will
|
||||
* be NULL.
|
||||
*/
|
||||
static IncrementalBackupInfo *uploaded_manifest = NULL;
|
||||
static MemoryContext uploaded_manifest_mcxt = NULL;
|
||||
|
||||
/*
|
||||
* These variables keep track of the state of the timeline we're currently
|
||||
* sending. sendTimeLine identifies the timeline. If sendTimeLineIsHistoric,
|
||||
@ -233,6 +245,9 @@ static void XLogSendLogical(void);
|
||||
static void WalSndDone(WalSndSendDataCallback send_data);
|
||||
static XLogRecPtr GetStandbyFlushRecPtr(TimeLineID *tli);
|
||||
static void IdentifySystem(void);
|
||||
static void UploadManifest(void);
|
||||
static bool HandleUploadManifestPacket(StringInfo buf, off_t *offset,
|
||||
IncrementalBackupInfo *ib);
|
||||
static void ReadReplicationSlot(ReadReplicationSlotCmd *cmd);
|
||||
static void CreateReplicationSlot(CreateReplicationSlotCmd *cmd);
|
||||
static void DropReplicationSlot(DropReplicationSlotCmd *cmd);
|
||||
@ -660,6 +675,143 @@ SendTimeLineHistory(TimeLineHistoryCmd *cmd)
|
||||
pq_endmessage(&buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle UPLOAD_MANIFEST command.
|
||||
*/
|
||||
static void
|
||||
UploadManifest(void)
|
||||
{
|
||||
MemoryContext mcxt;
|
||||
IncrementalBackupInfo *ib;
|
||||
off_t offset = 0;
|
||||
StringInfoData buf;
|
||||
|
||||
/*
|
||||
* parsing the manifest will use the cryptohash stuff, which requires a
|
||||
* resource owner
|
||||
*/
|
||||
Assert(CurrentResourceOwner == NULL);
|
||||
CurrentResourceOwner = ResourceOwnerCreate(NULL, "base backup");
|
||||
|
||||
/* Prepare to read manifest data into a temporary context. */
|
||||
mcxt = AllocSetContextCreate(CurrentMemoryContext,
|
||||
"incremental backup information",
|
||||
ALLOCSET_DEFAULT_SIZES);
|
||||
ib = CreateIncrementalBackupInfo(mcxt);
|
||||
|
||||
/* Send a CopyInResponse message */
|
||||
pq_beginmessage(&buf, 'G');
|
||||
pq_sendbyte(&buf, 0);
|
||||
pq_sendint16(&buf, 0);
|
||||
pq_endmessage_reuse(&buf);
|
||||
pq_flush();
|
||||
|
||||
/* Recieve packets from client until done. */
|
||||
while (HandleUploadManifestPacket(&buf, &offset, ib))
|
||||
;
|
||||
|
||||
/* Finish up manifest processing. */
|
||||
FinalizeIncrementalManifest(ib);
|
||||
|
||||
/*
|
||||
* Discard any old manifest information and arrange to preserve the new
|
||||
* information we just got.
|
||||
*
|
||||
* We assume that MemoryContextDelete and MemoryContextSetParent won't
|
||||
* fail, and thus we shouldn't end up bailing out of here in such a way as
|
||||
* to leave dangling pointrs.
|
||||
*/
|
||||
if (uploaded_manifest_mcxt != NULL)
|
||||
MemoryContextDelete(uploaded_manifest_mcxt);
|
||||
MemoryContextSetParent(mcxt, CacheMemoryContext);
|
||||
uploaded_manifest = ib;
|
||||
uploaded_manifest_mcxt = mcxt;
|
||||
|
||||
/* clean up the resource owner we created */
|
||||
WalSndResourceCleanup(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process one packet received during the handling of an UPLOAD_MANIFEST
|
||||
* operation.
|
||||
*
|
||||
* 'buf' is scratch space. This function expects it to be initialized, doesn't
|
||||
* care what the current contents are, and may override them with completely
|
||||
* new contents.
|
||||
*
|
||||
* The return value is true if the caller should continue processing
|
||||
* additional packets and false if the UPLOAD_MANIFEST operation is complete.
|
||||
*/
|
||||
static bool
|
||||
HandleUploadManifestPacket(StringInfo buf, off_t *offset,
|
||||
IncrementalBackupInfo *ib)
|
||||
{
|
||||
int mtype;
|
||||
int maxmsglen;
|
||||
|
||||
HOLD_CANCEL_INTERRUPTS();
|
||||
|
||||
pq_startmsgread();
|
||||
mtype = pq_getbyte();
|
||||
if (mtype == EOF)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_CONNECTION_FAILURE),
|
||||
errmsg("unexpected EOF on client connection with an open transaction")));
|
||||
|
||||
switch (mtype)
|
||||
{
|
||||
case 'd': /* CopyData */
|
||||
maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
|
||||
break;
|
||||
case 'c': /* CopyDone */
|
||||
case 'f': /* CopyFail */
|
||||
case 'H': /* Flush */
|
||||
case 'S': /* Sync */
|
||||
maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
|
||||
break;
|
||||
default:
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
||||
errmsg("unexpected message type 0x%02X during COPY from stdin",
|
||||
mtype)));
|
||||
maxmsglen = 0; /* keep compiler quiet */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Now collect the message body */
|
||||
if (pq_getmessage(buf, maxmsglen))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_CONNECTION_FAILURE),
|
||||
errmsg("unexpected EOF on client connection with an open transaction")));
|
||||
RESUME_CANCEL_INTERRUPTS();
|
||||
|
||||
/* Process the message */
|
||||
switch (mtype)
|
||||
{
|
||||
case 'd': /* CopyData */
|
||||
AppendIncrementalManifestData(ib, buf->data, buf->len);
|
||||
return true;
|
||||
|
||||
case 'c': /* CopyDone */
|
||||
return false;
|
||||
|
||||
case 'H': /* Sync */
|
||||
case 'S': /* Flush */
|
||||
/* Ignore these while in CopyOut mode as we do elsewhere. */
|
||||
return true;
|
||||
|
||||
case 'f':
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_QUERY_CANCELED),
|
||||
errmsg("COPY from stdin failed: %s",
|
||||
pq_getmsgstring(buf))));
|
||||
}
|
||||
|
||||
/* Not reached. */
|
||||
Assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle START_REPLICATION command.
|
||||
*
|
||||
@ -1801,7 +1953,7 @@ exec_replication_command(const char *cmd_string)
|
||||
cmdtag = "BASE_BACKUP";
|
||||
set_ps_display(cmdtag);
|
||||
PreventInTransactionBlock(true, cmdtag);
|
||||
SendBaseBackup((BaseBackupCmd *) cmd_node);
|
||||
SendBaseBackup((BaseBackupCmd *) cmd_node, uploaded_manifest);
|
||||
EndReplicationCommand(cmdtag);
|
||||
break;
|
||||
|
||||
@ -1863,6 +2015,14 @@ exec_replication_command(const char *cmd_string)
|
||||
}
|
||||
break;
|
||||
|
||||
case T_UploadManifestCmd:
|
||||
cmdtag = "UPLOAD_MANIFEST";
|
||||
set_ps_display(cmdtag);
|
||||
PreventInTransactionBlock(true, cmdtag);
|
||||
UploadManifest();
|
||||
EndReplicationCommand(cmdtag);
|
||||
break;
|
||||
|
||||
default:
|
||||
elog(ERROR, "unrecognized replication command node tag: %u",
|
||||
cmd_node->type);
|
||||
|
Reference in New Issue
Block a user