mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
Support multiple concurrent pg_basebackup backups.
With this patch, pg_basebackup doesn't write a backup_label file in the data directory, so it doesn't interfere with a pg_start/stop_backup() based backup anymore. backup_label is still included in the backup, but it is injected directly into the tar stream. Heikki Linnakangas, reviewed by Fujii Masao and Magnus Hagander.
This commit is contained in:
parent
48c9de8028
commit
997b48ed96
@ -814,12 +814,13 @@ SELECT pg_stop_backup();
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
You can also use the <xref linkend="app-pgbasebackup"> tool to take
|
You can also use the <xref linkend="app-pgbasebackup"> tool to take
|
||||||
the backup, instead of manually copying the files. This tool will take
|
the backup, instead of manually copying the files. This tool will do
|
||||||
care of the <function>pg_start_backup()</>, copy and
|
the equivalent of <function>pg_start_backup()</>, copy and
|
||||||
<function>pg_stop_backup()</> steps automatically, and transfers the
|
<function>pg_stop_backup()</> steps automatically, and transfers the
|
||||||
backup over a regular <productname>PostgreSQL</productname> connection
|
backup over a regular <productname>PostgreSQL</productname> connection
|
||||||
using the replication protocol, instead of requiring filesystem level
|
using the replication protocol, instead of requiring filesystem level
|
||||||
access.
|
access. pg_basebackup does not interfere with filesystem level backups
|
||||||
|
taken using <function>pg_start_backup()</>/<function>pg_stop_backup()</>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -59,10 +59,9 @@ PostgreSQL documentation
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Only one backup can be concurrently active in
|
There can be multiple pg_basebackups running at the same time, but it is
|
||||||
<productname>PostgreSQL</productname>, meaning that only one instance of
|
better from a performance point of view to take only one backup, and copy
|
||||||
<application>pg_basebackup</application> can run at the same time
|
the the result.
|
||||||
against a single database cluster.
|
|
||||||
</para>
|
</para>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
|
@ -60,8 +60,6 @@
|
|||||||
|
|
||||||
|
|
||||||
/* File path names (all relative to $PGDATA) */
|
/* File path names (all relative to $PGDATA) */
|
||||||
#define BACKUP_LABEL_FILE "backup_label"
|
|
||||||
#define BACKUP_LABEL_OLD "backup_label.old"
|
|
||||||
#define RECOVERY_COMMAND_FILE "recovery.conf"
|
#define RECOVERY_COMMAND_FILE "recovery.conf"
|
||||||
#define RECOVERY_COMMAND_DONE "recovery.done"
|
#define RECOVERY_COMMAND_DONE "recovery.done"
|
||||||
|
|
||||||
@ -339,6 +337,15 @@ typedef struct XLogCtlInsert
|
|||||||
char *currpos; /* current insertion point in cache */
|
char *currpos; /* current insertion point in cache */
|
||||||
XLogRecPtr RedoRecPtr; /* current redo point for insertions */
|
XLogRecPtr RedoRecPtr; /* current redo point for insertions */
|
||||||
bool forcePageWrites; /* forcing full-page writes for PITR? */
|
bool forcePageWrites; /* forcing full-page writes for PITR? */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* exclusiveBackup is true if a backup started with pg_start_backup() is
|
||||||
|
* in progress, and nonExclusiveBackups is a counter indicating the number
|
||||||
|
* of streaming base backups currently in progress. forcePageWrites is
|
||||||
|
* set to true when either of these is non-zero.
|
||||||
|
*/
|
||||||
|
bool exclusiveBackup;
|
||||||
|
int nonExclusiveBackups;
|
||||||
} XLogCtlInsert;
|
} XLogCtlInsert;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -8352,16 +8359,38 @@ pg_start_backup(PG_FUNCTION_ARGS)
|
|||||||
|
|
||||||
backupidstr = text_to_cstring(backupid);
|
backupidstr = text_to_cstring(backupid);
|
||||||
|
|
||||||
startpoint = do_pg_start_backup(backupidstr, fast);
|
startpoint = do_pg_start_backup(backupidstr, fast, NULL);
|
||||||
|
|
||||||
snprintf(startxlogstr, sizeof(startxlogstr), "%X/%X",
|
snprintf(startxlogstr, sizeof(startxlogstr), "%X/%X",
|
||||||
startpoint.xlogid, startpoint.xrecoff);
|
startpoint.xlogid, startpoint.xrecoff);
|
||||||
PG_RETURN_TEXT_P(cstring_to_text(startxlogstr));
|
PG_RETURN_TEXT_P(cstring_to_text(startxlogstr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* do_pg_start_backup is the workhorse of the user-visible pg_start_backup()
|
||||||
|
* function. It creates the necessary starting checkpoint and constructs the
|
||||||
|
* backup label file.
|
||||||
|
*
|
||||||
|
* There are two kind of backups: exclusive and non-exclusive. An exclusive
|
||||||
|
* backup is started with pg_start_backup(), and there can be only one active
|
||||||
|
* at a time. The backup label file of an exclusive backup is written to
|
||||||
|
* $PGDATA/backup_label, and it is removed by pg_stop_backup().
|
||||||
|
*
|
||||||
|
* A non-exclusive backup is used for the streaming base backups (see
|
||||||
|
* src/backend/replication/basebackup.c). The difference to exclusive backups
|
||||||
|
* is that the backup label file is not written to disk. Instead, its would-be
|
||||||
|
* contents are returned in *labelfile, and the caller is responsible for
|
||||||
|
* including it in the backup archive as 'backup_label'. There can be many
|
||||||
|
* non-exclusive backups active at the same time, and they don't conflict
|
||||||
|
* with an exclusive backup either.
|
||||||
|
*
|
||||||
|
* Every successfully started non-exclusive backup must be stopped by calling
|
||||||
|
* do_pg_stop_backup() or do_pg_abort_backup().
|
||||||
|
*/
|
||||||
XLogRecPtr
|
XLogRecPtr
|
||||||
do_pg_start_backup(const char *backupidstr, bool fast)
|
do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile)
|
||||||
{
|
{
|
||||||
|
bool exclusive = (labelfile == NULL);
|
||||||
XLogRecPtr checkpointloc;
|
XLogRecPtr checkpointloc;
|
||||||
XLogRecPtr startpoint;
|
XLogRecPtr startpoint;
|
||||||
pg_time_t stamp_time;
|
pg_time_t stamp_time;
|
||||||
@ -8371,6 +8400,7 @@ do_pg_start_backup(const char *backupidstr, bool fast)
|
|||||||
uint32 _logSeg;
|
uint32 _logSeg;
|
||||||
struct stat stat_buf;
|
struct stat stat_buf;
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
StringInfoData labelfbuf;
|
||||||
|
|
||||||
if (!superuser() && !is_authenticated_user_replication_role())
|
if (!superuser() && !is_authenticated_user_replication_role())
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -8389,6 +8419,12 @@ do_pg_start_backup(const char *backupidstr, bool fast)
|
|||||||
errmsg("WAL level not sufficient for making an online backup"),
|
errmsg("WAL level not sufficient for making an online backup"),
|
||||||
errhint("wal_level must be set to \"archive\" or \"hot_standby\" at server start.")));
|
errhint("wal_level must be set to \"archive\" or \"hot_standby\" at server start.")));
|
||||||
|
|
||||||
|
if (strlen(backupidstr) > MAXPGPATH)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("backup label too long (max %d bytes)",
|
||||||
|
MAXPGPATH)));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mark backup active in shared memory. We must do full-page WAL writes
|
* Mark backup active in shared memory. We must do full-page WAL writes
|
||||||
* during an on-line backup even if not doing so at other times, because
|
* during an on-line backup even if not doing so at other times, because
|
||||||
@ -8407,14 +8443,20 @@ do_pg_start_backup(const char *backupidstr, bool fast)
|
|||||||
* ensure adequate interlocking against XLogInsert().
|
* ensure adequate interlocking against XLogInsert().
|
||||||
*/
|
*/
|
||||||
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
|
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
|
||||||
if (XLogCtl->Insert.forcePageWrites)
|
if (exclusive)
|
||||||
{
|
{
|
||||||
LWLockRelease(WALInsertLock);
|
if (XLogCtl->Insert.exclusiveBackup)
|
||||||
ereport(ERROR,
|
{
|
||||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
LWLockRelease(WALInsertLock);
|
||||||
errmsg("a backup is already in progress"),
|
ereport(ERROR,
|
||||||
errhint("Run pg_stop_backup() and try again.")));
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||||
|
errmsg("a backup is already in progress"),
|
||||||
|
errhint("Run pg_stop_backup() and try again.")));
|
||||||
|
}
|
||||||
|
XLogCtl->Insert.exclusiveBackup = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
XLogCtl->Insert.nonExclusiveBackups++;
|
||||||
XLogCtl->Insert.forcePageWrites = true;
|
XLogCtl->Insert.forcePageWrites = true;
|
||||||
LWLockRelease(WALInsertLock);
|
LWLockRelease(WALInsertLock);
|
||||||
|
|
||||||
@ -8432,7 +8474,7 @@ do_pg_start_backup(const char *backupidstr, bool fast)
|
|||||||
RequestXLogSwitch();
|
RequestXLogSwitch();
|
||||||
|
|
||||||
/* Ensure we release forcePageWrites if fail below */
|
/* Ensure we release forcePageWrites if fail below */
|
||||||
PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) 0);
|
PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Force a CHECKPOINT. Aside from being necessary to prevent torn
|
* Force a CHECKPOINT. Aside from being necessary to prevent torn
|
||||||
@ -8459,54 +8501,67 @@ do_pg_start_backup(const char *backupidstr, bool fast)
|
|||||||
XLByteToSeg(startpoint, _logId, _logSeg);
|
XLByteToSeg(startpoint, _logId, _logSeg);
|
||||||
XLogFileName(xlogfilename, ThisTimeLineID, _logId, _logSeg);
|
XLogFileName(xlogfilename, ThisTimeLineID, _logId, _logSeg);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct backup label file
|
||||||
|
*/
|
||||||
|
initStringInfo(&labelfbuf);
|
||||||
|
|
||||||
/* Use the log timezone here, not the session timezone */
|
/* Use the log timezone here, not the session timezone */
|
||||||
stamp_time = (pg_time_t) time(NULL);
|
stamp_time = (pg_time_t) time(NULL);
|
||||||
pg_strftime(strfbuf, sizeof(strfbuf),
|
pg_strftime(strfbuf, sizeof(strfbuf),
|
||||||
"%Y-%m-%d %H:%M:%S %Z",
|
"%Y-%m-%d %H:%M:%S %Z",
|
||||||
pg_localtime(&stamp_time, log_timezone));
|
pg_localtime(&stamp_time, log_timezone));
|
||||||
|
appendStringInfo(&labelfbuf, "START WAL LOCATION: %X/%X (file %s)\n",
|
||||||
|
startpoint.xlogid, startpoint.xrecoff, xlogfilename);
|
||||||
|
appendStringInfo(&labelfbuf, "CHECKPOINT LOCATION: %X/%X\n",
|
||||||
|
checkpointloc.xlogid, checkpointloc.xrecoff);
|
||||||
|
appendStringInfo(&labelfbuf, "START TIME: %s\n", strfbuf);
|
||||||
|
appendStringInfo(&labelfbuf, "LABEL: %s\n", backupidstr);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check for existing backup label --- implies a backup is already
|
* Okay, write the file, or return its contents to caller.
|
||||||
* running. (XXX given that we checked forcePageWrites above, maybe
|
|
||||||
* it would be OK to just unlink any such label file?)
|
|
||||||
*/
|
*/
|
||||||
if (stat(BACKUP_LABEL_FILE, &stat_buf) != 0)
|
if (exclusive)
|
||||||
{
|
{
|
||||||
if (errno != ENOENT)
|
/*
|
||||||
|
* Check for existing backup label --- implies a backup is already
|
||||||
|
* running. (XXX given that we checked exclusiveBackup above, maybe
|
||||||
|
* it would be OK to just unlink any such label file?)
|
||||||
|
*/
|
||||||
|
if (stat(BACKUP_LABEL_FILE, &stat_buf) != 0)
|
||||||
|
{
|
||||||
|
if (errno != ENOENT)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode_for_file_access(),
|
||||||
|
errmsg("could not stat file \"%s\": %m",
|
||||||
|
BACKUP_LABEL_FILE)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||||
|
errmsg("a backup is already in progress"),
|
||||||
|
errhint("If you're sure there is no backup in progress, remove file \"%s\" and try again.",
|
||||||
|
BACKUP_LABEL_FILE)));
|
||||||
|
|
||||||
|
fp = AllocateFile(BACKUP_LABEL_FILE, "w");
|
||||||
|
|
||||||
|
if (!fp)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode_for_file_access(),
|
(errcode_for_file_access(),
|
||||||
errmsg("could not stat file \"%s\": %m",
|
errmsg("could not create file \"%s\": %m",
|
||||||
BACKUP_LABEL_FILE)));
|
BACKUP_LABEL_FILE)));
|
||||||
|
fwrite(labelfbuf.data, labelfbuf.len, 1, fp);
|
||||||
|
if (fflush(fp) || ferror(fp) || FreeFile(fp))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode_for_file_access(),
|
||||||
|
errmsg("could not write file \"%s\": %m",
|
||||||
|
BACKUP_LABEL_FILE)));
|
||||||
|
pfree(labelfbuf.data);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ereport(ERROR,
|
*labelfile = labelfbuf.data;
|
||||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
||||||
errmsg("a backup is already in progress"),
|
|
||||||
errhint("If you're sure there is no backup in progress, remove file \"%s\" and try again.",
|
|
||||||
BACKUP_LABEL_FILE)));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Okay, write the file
|
|
||||||
*/
|
|
||||||
fp = AllocateFile(BACKUP_LABEL_FILE, "w");
|
|
||||||
if (!fp)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode_for_file_access(),
|
|
||||||
errmsg("could not create file \"%s\": %m",
|
|
||||||
BACKUP_LABEL_FILE)));
|
|
||||||
fprintf(fp, "START WAL LOCATION: %X/%X (file %s)\n",
|
|
||||||
startpoint.xlogid, startpoint.xrecoff, xlogfilename);
|
|
||||||
fprintf(fp, "CHECKPOINT LOCATION: %X/%X\n",
|
|
||||||
checkpointloc.xlogid, checkpointloc.xrecoff);
|
|
||||||
fprintf(fp, "START TIME: %s\n", strfbuf);
|
|
||||||
fprintf(fp, "LABEL: %s\n", backupidstr);
|
|
||||||
if (fflush(fp) || ferror(fp) || FreeFile(fp))
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode_for_file_access(),
|
|
||||||
errmsg("could not write file \"%s\": %m",
|
|
||||||
BACKUP_LABEL_FILE)));
|
|
||||||
}
|
}
|
||||||
PG_END_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) 0);
|
PG_END_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We're done. As a convenience, return the starting WAL location.
|
* We're done. As a convenience, return the starting WAL location.
|
||||||
@ -8518,9 +8573,26 @@ do_pg_start_backup(const char *backupidstr, bool fast)
|
|||||||
static void
|
static void
|
||||||
pg_start_backup_callback(int code, Datum arg)
|
pg_start_backup_callback(int code, Datum arg)
|
||||||
{
|
{
|
||||||
/* Turn off forcePageWrites on failure */
|
bool exclusive = DatumGetBool(arg);
|
||||||
|
|
||||||
|
/* Update backup counters and forcePageWrites on failure */
|
||||||
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
|
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
|
||||||
XLogCtl->Insert.forcePageWrites = false;
|
if (exclusive)
|
||||||
|
{
|
||||||
|
Assert(XLogCtl->Insert.exclusiveBackup);
|
||||||
|
XLogCtl->Insert.exclusiveBackup = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert(XLogCtl->Insert.nonExclusiveBackups > 0);
|
||||||
|
XLogCtl->Insert.nonExclusiveBackups--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!XLogCtl->Insert.exclusiveBackup &&
|
||||||
|
XLogCtl->Insert.nonExclusiveBackups == 0)
|
||||||
|
{
|
||||||
|
XLogCtl->Insert.forcePageWrites = false;
|
||||||
|
}
|
||||||
LWLockRelease(WALInsertLock);
|
LWLockRelease(WALInsertLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -8543,16 +8615,24 @@ pg_stop_backup(PG_FUNCTION_ARGS)
|
|||||||
XLogRecPtr stoppoint;
|
XLogRecPtr stoppoint;
|
||||||
char stopxlogstr[MAXFNAMELEN];
|
char stopxlogstr[MAXFNAMELEN];
|
||||||
|
|
||||||
stoppoint = do_pg_stop_backup();
|
stoppoint = do_pg_stop_backup(NULL);
|
||||||
|
|
||||||
snprintf(stopxlogstr, sizeof(stopxlogstr), "%X/%X",
|
snprintf(stopxlogstr, sizeof(stopxlogstr), "%X/%X",
|
||||||
stoppoint.xlogid, stoppoint.xrecoff);
|
stoppoint.xlogid, stoppoint.xrecoff);
|
||||||
PG_RETURN_TEXT_P(cstring_to_text(stopxlogstr));
|
PG_RETURN_TEXT_P(cstring_to_text(stopxlogstr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* do_pg_stop_backup is the workhorse of the user-visible pg_stop_backup()
|
||||||
|
* function.
|
||||||
|
|
||||||
|
* If labelfile is NULL, this stops an exclusive backup. Otherwise this stops
|
||||||
|
* the non-exclusive backup specified by 'labelfile'.
|
||||||
|
*/
|
||||||
XLogRecPtr
|
XLogRecPtr
|
||||||
do_pg_stop_backup(void)
|
do_pg_stop_backup(char *labelfile)
|
||||||
{
|
{
|
||||||
|
bool exclusive = (labelfile == NULL);
|
||||||
XLogRecPtr startpoint;
|
XLogRecPtr startpoint;
|
||||||
XLogRecPtr stoppoint;
|
XLogRecPtr stoppoint;
|
||||||
XLogRecData rdata;
|
XLogRecData rdata;
|
||||||
@ -8568,10 +8648,10 @@ do_pg_stop_backup(void)
|
|||||||
FILE *lfp;
|
FILE *lfp;
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
char ch;
|
char ch;
|
||||||
int ich;
|
|
||||||
int seconds_before_warning;
|
int seconds_before_warning;
|
||||||
int waits = 0;
|
int waits = 0;
|
||||||
bool reported_waiting = false;
|
bool reported_waiting = false;
|
||||||
|
char *remaining;
|
||||||
|
|
||||||
if (!superuser() && !is_authenticated_user_replication_role())
|
if (!superuser() && !is_authenticated_user_replication_role())
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -8591,38 +8671,88 @@ do_pg_stop_backup(void)
|
|||||||
errhint("wal_level must be set to \"archive\" or \"hot_standby\" at server start.")));
|
errhint("wal_level must be set to \"archive\" or \"hot_standby\" at server start.")));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* OK to clear forcePageWrites
|
* OK to update backup counters and forcePageWrites
|
||||||
*/
|
*/
|
||||||
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
|
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
|
||||||
XLogCtl->Insert.forcePageWrites = false;
|
if (exclusive)
|
||||||
|
XLogCtl->Insert.exclusiveBackup = false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The user-visible pg_start/stop_backup() functions that operate on
|
||||||
|
* exclusive backups can be called at any time, but for non-exclusive
|
||||||
|
* backups, it is expected that each do_pg_start_backup() call is
|
||||||
|
* matched by exactly one do_pg_stop_backup() call.
|
||||||
|
*/
|
||||||
|
Assert(XLogCtl->Insert.nonExclusiveBackups > 0);
|
||||||
|
XLogCtl->Insert.nonExclusiveBackups--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!XLogCtl->Insert.exclusiveBackup &&
|
||||||
|
XLogCtl->Insert.nonExclusiveBackups == 0)
|
||||||
|
{
|
||||||
|
XLogCtl->Insert.forcePageWrites = false;
|
||||||
|
}
|
||||||
LWLockRelease(WALInsertLock);
|
LWLockRelease(WALInsertLock);
|
||||||
|
|
||||||
/*
|
if (exclusive)
|
||||||
* Open the existing label file
|
|
||||||
*/
|
|
||||||
lfp = AllocateFile(BACKUP_LABEL_FILE, "r");
|
|
||||||
if (!lfp)
|
|
||||||
{
|
{
|
||||||
if (errno != ENOENT)
|
/*
|
||||||
|
* Read the existing label file into memory.
|
||||||
|
*/
|
||||||
|
struct stat statbuf;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (stat(BACKUP_LABEL_FILE, &statbuf))
|
||||||
|
{
|
||||||
|
if (errno != ENOENT)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode_for_file_access(),
|
||||||
|
errmsg("could not stat file \"%s\": %m",
|
||||||
|
BACKUP_LABEL_FILE)));
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||||
|
errmsg("a backup is not in progress")));
|
||||||
|
}
|
||||||
|
|
||||||
|
lfp = AllocateFile(BACKUP_LABEL_FILE, "r");
|
||||||
|
if (!lfp)
|
||||||
|
{
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode_for_file_access(),
|
(errcode_for_file_access(),
|
||||||
errmsg("could not read file \"%s\": %m",
|
errmsg("could not read file \"%s\": %m",
|
||||||
BACKUP_LABEL_FILE)));
|
BACKUP_LABEL_FILE)));
|
||||||
ereport(ERROR,
|
}
|
||||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
labelfile = palloc(statbuf.st_size + 1);
|
||||||
errmsg("a backup is not in progress")));
|
r = fread(labelfile, statbuf.st_size, 1, lfp);
|
||||||
|
labelfile[statbuf.st_size] = '\0';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Close and remove the backup label file
|
||||||
|
*/
|
||||||
|
if (r != 1 || ferror(lfp) || FreeFile(lfp))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode_for_file_access(),
|
||||||
|
errmsg("could not read file \"%s\": %m",
|
||||||
|
BACKUP_LABEL_FILE)));
|
||||||
|
if (unlink(BACKUP_LABEL_FILE) != 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode_for_file_access(),
|
||||||
|
errmsg("could not remove file \"%s\": %m",
|
||||||
|
BACKUP_LABEL_FILE)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read and parse the START WAL LOCATION line (this code is pretty crude,
|
* Read and parse the START WAL LOCATION line (this code is pretty crude,
|
||||||
* but we are not expecting any variability in the file format).
|
* but we are not expecting any variability in the file format).
|
||||||
*/
|
*/
|
||||||
if (fscanf(lfp, "START WAL LOCATION: %X/%X (file %24s)%c",
|
if (sscanf(labelfile, "START WAL LOCATION: %X/%X (file %24s)%c",
|
||||||
&startpoint.xlogid, &startpoint.xrecoff, startxlogfilename,
|
&startpoint.xlogid, &startpoint.xrecoff, startxlogfilename,
|
||||||
&ch) != 4 || ch != '\n')
|
&ch) != 4 || ch != '\n')
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||||
errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
|
errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
|
||||||
|
remaining = strchr(labelfile, '\n') + 1; /* %n is not portable enough */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write the backup-end xlog record
|
* Write the backup-end xlog record
|
||||||
@ -8665,8 +8795,7 @@ do_pg_stop_backup(void)
|
|||||||
fprintf(fp, "STOP WAL LOCATION: %X/%X (file %s)\n",
|
fprintf(fp, "STOP WAL LOCATION: %X/%X (file %s)\n",
|
||||||
stoppoint.xlogid, stoppoint.xrecoff, stopxlogfilename);
|
stoppoint.xlogid, stoppoint.xrecoff, stopxlogfilename);
|
||||||
/* transfer remaining lines from label to history file */
|
/* transfer remaining lines from label to history file */
|
||||||
while ((ich = fgetc(lfp)) != EOF)
|
fprintf(fp, "%s", remaining);
|
||||||
fputc(ich, fp);
|
|
||||||
fprintf(fp, "STOP TIME: %s\n", strfbuf);
|
fprintf(fp, "STOP TIME: %s\n", strfbuf);
|
||||||
if (fflush(fp) || ferror(fp) || FreeFile(fp))
|
if (fflush(fp) || ferror(fp) || FreeFile(fp))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -8674,20 +8803,6 @@ do_pg_stop_backup(void)
|
|||||||
errmsg("could not write file \"%s\": %m",
|
errmsg("could not write file \"%s\": %m",
|
||||||
histfilepath)));
|
histfilepath)));
|
||||||
|
|
||||||
/*
|
|
||||||
* Close and remove the backup label file
|
|
||||||
*/
|
|
||||||
if (ferror(lfp) || FreeFile(lfp))
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode_for_file_access(),
|
|
||||||
errmsg("could not read file \"%s\": %m",
|
|
||||||
BACKUP_LABEL_FILE)));
|
|
||||||
if (unlink(BACKUP_LABEL_FILE) != 0)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode_for_file_access(),
|
|
||||||
errmsg("could not remove file \"%s\": %m",
|
|
||||||
BACKUP_LABEL_FILE)));
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Clean out any no-longer-needed history files. As a side effect, this
|
* Clean out any no-longer-needed history files. As a side effect, this
|
||||||
* will post a .ready file for the newly created history file, notifying
|
* will post a .ready file for the newly created history file, notifying
|
||||||
@ -8769,28 +8884,27 @@ do_pg_stop_backup(void)
|
|||||||
/*
|
/*
|
||||||
* do_pg_abort_backup: abort a running backup
|
* do_pg_abort_backup: abort a running backup
|
||||||
*
|
*
|
||||||
* This does just the most basic steps of pg_stop_backup(), by taking the
|
* This does just the most basic steps of do_pg_stop_backup(), by taking the
|
||||||
* system out of backup mode, thus making it a lot more safe to call from
|
* system out of backup mode, thus making it a lot more safe to call from
|
||||||
* an error handler.
|
* an error handler.
|
||||||
|
*
|
||||||
|
* NB: This is only for aborting a non-exclusive backup that doesn't write
|
||||||
|
* backup_label. A backup started with pg_stop_backup() needs to be finished
|
||||||
|
* with pg_stop_backup().
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
do_pg_abort_backup(void)
|
do_pg_abort_backup(void)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* OK to clear forcePageWrites
|
|
||||||
*/
|
|
||||||
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
|
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
|
||||||
XLogCtl->Insert.forcePageWrites = false;
|
Assert(XLogCtl->Insert.nonExclusiveBackups > 0);
|
||||||
LWLockRelease(WALInsertLock);
|
XLogCtl->Insert.nonExclusiveBackups--;
|
||||||
|
|
||||||
/*
|
if (!XLogCtl->Insert.exclusiveBackup &&
|
||||||
* Remove backup label file
|
XLogCtl->Insert.nonExclusiveBackups == 0)
|
||||||
*/
|
{
|
||||||
if (unlink(BACKUP_LABEL_FILE) != 0)
|
XLogCtl->Insert.forcePageWrites = false;
|
||||||
ereport(ERROR,
|
}
|
||||||
(errcode_for_file_access(),
|
LWLockRelease(WALInsertLock);
|
||||||
errmsg("could not remove file \"%s\": %m",
|
|
||||||
BACKUP_LABEL_FILE)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -42,8 +42,10 @@ typedef struct
|
|||||||
|
|
||||||
|
|
||||||
static int64 sendDir(char *path, int basepathlen, bool sizeonly);
|
static int64 sendDir(char *path, int basepathlen, bool sizeonly);
|
||||||
static void sendFile(char *path, int basepathlen, struct stat * statbuf);
|
static void sendFile(char *readfilename, char *tarfilename,
|
||||||
static void _tarWriteHeader(char *filename, char *linktarget,
|
struct stat * statbuf);
|
||||||
|
static void sendFileWithContent(const char *filename, const char *content);
|
||||||
|
static void _tarWriteHeader(const char *filename, char *linktarget,
|
||||||
struct stat * statbuf);
|
struct stat * statbuf);
|
||||||
static void send_int8_string(StringInfoData *buf, int64 intval);
|
static void send_int8_string(StringInfoData *buf, int64 intval);
|
||||||
static void SendBackupHeader(List *tablespaces);
|
static void SendBackupHeader(List *tablespaces);
|
||||||
@ -87,8 +89,9 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir)
|
|||||||
{
|
{
|
||||||
XLogRecPtr startptr;
|
XLogRecPtr startptr;
|
||||||
XLogRecPtr endptr;
|
XLogRecPtr endptr;
|
||||||
|
char *labelfile;
|
||||||
|
|
||||||
startptr = do_pg_start_backup(opt->label, opt->fastcheckpoint);
|
startptr = do_pg_start_backup(opt->label, opt->fastcheckpoint, &labelfile);
|
||||||
|
|
||||||
PG_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0);
|
PG_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0);
|
||||||
{
|
{
|
||||||
@ -144,6 +147,10 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir)
|
|||||||
pq_sendint(&buf, 0, 2); /* natts */
|
pq_sendint(&buf, 0, 2); /* natts */
|
||||||
pq_endmessage(&buf);
|
pq_endmessage(&buf);
|
||||||
|
|
||||||
|
/* In the main tar, include the backup_label first. */
|
||||||
|
if (ti->path == NULL)
|
||||||
|
sendFileWithContent(BACKUP_LABEL_FILE, labelfile);
|
||||||
|
|
||||||
sendDir(ti->path == NULL ? "." : ti->path,
|
sendDir(ti->path == NULL ? "." : ti->path,
|
||||||
ti->path == NULL ? 1 : strlen(ti->path),
|
ti->path == NULL ? 1 : strlen(ti->path),
|
||||||
false);
|
false);
|
||||||
@ -164,7 +171,7 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir)
|
|||||||
}
|
}
|
||||||
PG_END_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0);
|
PG_END_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0);
|
||||||
|
|
||||||
endptr = do_pg_stop_backup();
|
endptr = do_pg_stop_backup(labelfile);
|
||||||
|
|
||||||
if (opt->includewal)
|
if (opt->includewal)
|
||||||
{
|
{
|
||||||
@ -299,8 +306,9 @@ parse_basebackup_options(List *options, basebackup_options *opt)
|
|||||||
/*
|
/*
|
||||||
* SendBaseBackup() - send a complete base backup.
|
* SendBaseBackup() - send a complete base backup.
|
||||||
*
|
*
|
||||||
* The function will take care of running pg_start_backup() and
|
* The function will put the system into backup mode like pg_start_backup()
|
||||||
* pg_stop_backup() for the user.
|
* does, so that the backup is consistent even though we read directly from
|
||||||
|
* the filesystem, bypassing the buffer cache.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
SendBaseBackup(BaseBackupCmd *cmd)
|
SendBaseBackup(BaseBackupCmd *cmd)
|
||||||
@ -423,7 +431,52 @@ SendBackupHeader(List *tablespaces)
|
|||||||
pq_puttextmessage('C', "SELECT");
|
pq_puttextmessage('C', "SELECT");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inject a file with given name and content in the output tar stream.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
sendFileWithContent(const char *filename, const char *content)
|
||||||
|
{
|
||||||
|
struct stat statbuf;
|
||||||
|
int pad, len;
|
||||||
|
|
||||||
|
len = strlen(content);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct a stat struct for the backup_label file we're injecting
|
||||||
|
* in the tar.
|
||||||
|
*/
|
||||||
|
/* Windows doesn't have the concept of uid and gid */
|
||||||
|
#ifdef WIN32
|
||||||
|
statbuf.st_uid = 0;
|
||||||
|
statbuf.st_gid = 0;
|
||||||
|
#else
|
||||||
|
statbuf.st_uid = geteuid();
|
||||||
|
statbuf.st_gid = getegid();
|
||||||
|
#endif
|
||||||
|
statbuf.st_mtime = time(NULL);
|
||||||
|
statbuf.st_mode = S_IRUSR | S_IWUSR;
|
||||||
|
statbuf.st_size = len;
|
||||||
|
|
||||||
|
_tarWriteHeader(filename, NULL, &statbuf);
|
||||||
|
/* Send the contents as a CopyData message */
|
||||||
|
pq_putmessage('d', content, len);
|
||||||
|
|
||||||
|
/* Pad to 512 byte boundary, per tar format requirements */
|
||||||
|
pad = ((len + 511) & ~511) - len;
|
||||||
|
if (pad > 0)
|
||||||
|
{
|
||||||
|
char buf[512];
|
||||||
|
MemSet(buf, 0, pad);
|
||||||
|
pq_putmessage('d', buf, pad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Include all files from the given directory in the output tar stream. If
|
||||||
|
* 'sizeonly' is true, we just calculate a total length and return ig, without
|
||||||
|
* actually sending anything.
|
||||||
|
*/
|
||||||
static int64
|
static int64
|
||||||
sendDir(char *path, int basepathlen, bool sizeonly)
|
sendDir(char *path, int basepathlen, bool sizeonly)
|
||||||
{
|
{
|
||||||
@ -446,6 +499,14 @@ sendDir(char *path, int basepathlen, bool sizeonly)
|
|||||||
strlen(PG_TEMP_FILE_PREFIX)) == 0)
|
strlen(PG_TEMP_FILE_PREFIX)) == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there's a backup_label file, it belongs to a backup started by
|
||||||
|
* the user with pg_start_backup(). It is *not* correct for this
|
||||||
|
* backup, our backup_label is injected into the tar separately.
|
||||||
|
*/
|
||||||
|
if (strcmp(de->d_name, BACKUP_LABEL_FILE) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if the postmaster has signaled us to exit, and abort
|
* Check if the postmaster has signaled us to exit, and abort
|
||||||
* with an error in that case. The error handler further up
|
* with an error in that case. The error handler further up
|
||||||
@ -532,7 +593,7 @@ sendDir(char *path, int basepathlen, bool sizeonly)
|
|||||||
/* Add size, rounded up to 512byte block */
|
/* Add size, rounded up to 512byte block */
|
||||||
size += ((statbuf.st_size + 511) & ~511);
|
size += ((statbuf.st_size + 511) & ~511);
|
||||||
if (!sizeonly)
|
if (!sizeonly)
|
||||||
sendFile(pathbuf, basepathlen, &statbuf);
|
sendFile(pathbuf, pathbuf + basepathlen + 1, &statbuf);
|
||||||
size += 512; /* Size of the header of the file */
|
size += 512; /* Size of the header of the file */
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -590,7 +651,7 @@ _tarChecksum(char *header)
|
|||||||
|
|
||||||
/* Given the member, write the TAR header & send the file */
|
/* Given the member, write the TAR header & send the file */
|
||||||
static void
|
static void
|
||||||
sendFile(char *filename, int basepathlen, struct stat * statbuf)
|
sendFile(char *readfilename, char *tarfilename, struct stat *statbuf)
|
||||||
{
|
{
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
char buf[TAR_SEND_SIZE];
|
char buf[TAR_SEND_SIZE];
|
||||||
@ -598,11 +659,11 @@ sendFile(char *filename, int basepathlen, struct stat * statbuf)
|
|||||||
pgoff_t len = 0;
|
pgoff_t len = 0;
|
||||||
size_t pad;
|
size_t pad;
|
||||||
|
|
||||||
fp = AllocateFile(filename, "rb");
|
fp = AllocateFile(readfilename, "rb");
|
||||||
if (fp == NULL)
|
if (fp == NULL)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(errcode_for_file_access()),
|
(errcode(errcode_for_file_access()),
|
||||||
errmsg("could not open file \"%s\": %m", filename)));
|
errmsg("could not open file \"%s\": %m", readfilename)));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Some compilers will throw a warning knowing this test can never be true
|
* Some compilers will throw a warning knowing this test can never be true
|
||||||
@ -611,9 +672,9 @@ sendFile(char *filename, int basepathlen, struct stat * statbuf)
|
|||||||
if (statbuf->st_size > MAX_TAR_MEMBER_FILELEN)
|
if (statbuf->st_size > MAX_TAR_MEMBER_FILELEN)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errmsg("archive member \"%s\" too large for tar format",
|
(errmsg("archive member \"%s\" too large for tar format",
|
||||||
filename)));
|
tarfilename)));
|
||||||
|
|
||||||
_tarWriteHeader(filename + basepathlen + 1, NULL, statbuf);
|
_tarWriteHeader(tarfilename, NULL, statbuf);
|
||||||
|
|
||||||
while ((cnt = fread(buf, 1, Min(sizeof(buf), statbuf->st_size - len), fp)) > 0)
|
while ((cnt = fread(buf, 1, Min(sizeof(buf), statbuf->st_size - len), fp)) > 0)
|
||||||
{
|
{
|
||||||
@ -660,7 +721,7 @@ sendFile(char *filename, int basepathlen, struct stat * statbuf)
|
|||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
_tarWriteHeader(char *filename, char *linktarget, struct stat * statbuf)
|
_tarWriteHeader(const char *filename, char *linktarget, struct stat * statbuf)
|
||||||
{
|
{
|
||||||
char h[512];
|
char h[512];
|
||||||
int lastSum = 0;
|
int lastSum = 0;
|
||||||
|
@ -312,8 +312,15 @@ extern void HandleStartupProcInterrupts(void);
|
|||||||
extern void StartupProcessMain(void);
|
extern void StartupProcessMain(void);
|
||||||
extern void WakeupRecovery(void);
|
extern void WakeupRecovery(void);
|
||||||
|
|
||||||
extern XLogRecPtr do_pg_start_backup(const char *backupidstr, bool fast);
|
/*
|
||||||
extern XLogRecPtr do_pg_stop_backup(void);
|
* Starting/stopping a base backup
|
||||||
|
*/
|
||||||
|
extern XLogRecPtr do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile);
|
||||||
|
extern XLogRecPtr do_pg_stop_backup(char *labelfile);
|
||||||
extern void do_pg_abort_backup(void);
|
extern void do_pg_abort_backup(void);
|
||||||
|
|
||||||
|
/* File path names (all relative to $PGDATA) */
|
||||||
|
#define BACKUP_LABEL_FILE "backup_label"
|
||||||
|
#define BACKUP_LABEL_OLD "backup_label.old"
|
||||||
|
|
||||||
#endif /* XLOG_H */
|
#endif /* XLOG_H */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user