一、 pg中的pg_stop_backup函数
在执行pg_start_backup函数开启备份模式后,务必要执行pg_stop_backup函数结束备份 (详细参考下方源码)。
结束排他备份
postgres=# SELECT pg_start_backup('backup02',false,true);
pg_start_backup
-----------------
0/3000028
(1 row)
postgres=# SELECT pg_stop_backup(true);
NOTICE: WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup
pg_stop_backup
----------------
(0/4000138,,)
(1 row)
会自动删除backup_label文件
结束非排他备份
postgres=# SELECT pg_start_backup('pgdata backup',false,false);
pg_start_backup
-----------------
0/2000060
(1 row)
postgres=# SELECT pg_stop_backup(false);
NOTICE: WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup
pg_stop_backup
---------------------------------------------------------------------------
(0/5000088,"START WAL LOCATION: 0/2000060 (file 000000010000000000000002)+
CHECKPOINT LOCATION: 0/2000098 +
BACKUP METHOD: streamed +
BACKUP FROM: primary +
START TIME: 2022-07-14 18:39:47 CST +
LABEL: pgdata backup +
START TIMELINE: 1 +
","")
(1 row)
二、 do_pg_stop_backup函数
在源码中,pg_stop_backup实际对应的是do_pg_stop_backup函数。
1. 主要参数
- labelfile:若为空,停止排他备份;若不为空,停止该标签名对应的非排他备份
- waitforarchive:是否等待WAL日志被归档
- stoptli_p:时间线ID
2. 返回值
从该备份还原时必须存在的最后一个WAL位置,以及*.stoptli_p中相应的时间线ID
3. 主要流程
- 预检查,与start函数类似
- 如果是排他模式,删除backup_label文件。(对于非排他模式,backup_label中的信息由每个备份进程自己维护,在执行pg_stop_backup函数时返回给用户)
- 修改备份状态和计数器
- 如果当前已没有其他备份,关闭强制全页写 forcePageWrites
- 非排他模式,解析备份进程中的backup_label信息
- 强制日志切换
- 写一个备份结束的XLOG记录
- 创建备份历史文件,该文件包含 backup_label文件的内容及执行pg_stop_backup的时间信息
- 等待所需WAL文件归档完成(可选)
- 返回备份结束的WAL位置
/*
* do_pg_stop_backup
*
* Utility function called at the end of an online backup. It cleans up the backup state and can optionally wait for WAL segments to be archived.
*
* Returns the last WAL location that must be present to restore from this backup, and the corresponding timeline ID in *stoptli_p.
*/
XLogRecPtr
do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
{
bool exclusive = (labelfile == NULL);
bool backup_started_in_recovery = false;
XLogRecPtr startpoint;
XLogRecPtr stoppoint;
TimeLineID stoptli;
pg_time_t stamp_time;
char strfbuf[128];
char histfilepath[MAXPGPATH];
char startxlogfilename[MAXFNAMELEN];
char stopxlogfilename[MAXFNAMELEN];
char lastxlogfilename[MAXFNAMELEN];
char histfilename[MAXFNAMELEN];
char backupfrom[20];
XLogSegNo _logSegNo;
FILE *lfp;
FILE *fp;
char ch;
int seconds_before_warning;
int waits = 0;
bool reported_waiting = false;
char *remaining;
char *ptr;
uint32 hi,
lo;
/* 开头的预检查跟start函数类似,这里不再重复介绍 */
backup_started_in_recovery = RecoveryInProgress();
/*
* Currently only non-exclusive backup can be taken during recovery.
*/
if (backup_started_in_recovery && exclusive)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("WAL control functions cannot be executed during recovery.")));
/*
* During recovery, we don't need to check WAL level. Because, if WAL
* level is not sufficient, it's impossible to get here during recovery.
*/
if (!backup_started_in_recovery && !XLogIsNeeded())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("WAL level not sufficient for making an online backup"),
errhint("wal_level must be set to \"replica\" or \"logical\" at server start.")));
if (exclusive)
{
/*
* At first, mark that we're now stopping an exclusive backup, to
* ensure that there are no other sessions currently running
* pg_start_backup() or pg_stop_backup().
*/
WALInsertLockAcquireExclusive();
if (XLogCtl->Insert.exclusiveBackupState != EXCLUSIVE_BACKUP_IN_PROGRESS)
{
WALInsertLockRelease();
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("exclusive backup not in progress")));
}
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_STOPPING;
WALInsertLockRelease();
/*
* Remove backup_label. In case of failure, the state for an exclusive backup is switched back to in-progress.
* 同样下面代码如果运行失败,会执行回调函数pg_stop_backup_callback,将排他备份状态改回运行中
*/
PG_ENSURE_ERROR_CLEANUP(pg_stop_backup_callback, (Datum) BoolGetDatum(exclusive));
{
/*
* Read the existing label file into memory.将标签文件读入内存
*/
struct stat statbuf;
int r;
if (stat(BACKUP_LABEL_FILE, &statbuf))
{
/* should not happen per the upper checks */
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,
(errcode_for_file_access(),
errmsg("could not read file \"%s\": %m",
BACKUP_LABEL_FILE)));
}
labelfile = palloc(statbuf.st_size + 1);
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)));
durable_unlink(BACKUP_LABEL_FILE, ERROR);
/*
* Remove tablespace_map file if present, it is created only if there are tablespaces. 如果表空间映射文件存在,也将其删除
*/
durable_unlink(TABLESPACE_MAP, DEBUG1);
}
PG_END_ENSURE_ERROR_CLEANUP(pg_stop_backup_callback, (Datum) BoolGetDatum(exclusive));
}
/*
* OK to update backup counters, forcePageWrites and session-level lock.
*
* Note that CHECK_FOR_INTERRUPTS() must not occur while updating them. Otherwise they can be updated inconsistently, and which might cause do_pg_abort_backup() to fail.
*/
WALInsertLockAcquireExclusive();
if (exclusive)
{
/* 排他模式,修改备份状态为NONE */
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_NONE;
}
else
{
/* 非排他模式,计数器-1 */
Assert(XLogCtl->Insert.nonExclusiveBackups > 0);
XLogCtl->Insert.nonExclusiveBackups--;
}
/* 若当前已无备份(包括排他模式和非排他模式),关闭强制全页写 */
if (XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_NONE &&
XLogCtl->Insert.nonExclusiveBackups == 0)
{
XLogCtl->Insert.forcePageWrites = false;
}
/*
* Clean up session-level lock.
* 更新会话级备份状态,并释放插入锁
*/
sessionBackupState = SESSION_BACKUP_NONE;
WALInsertLockRelease();
/*
* Read and parse the START WAL LOCATION line,读取并解析labelfile中的START WAL LOCATION行。注意labelfile是非排他模式备份传入的标签文件名,跟前面删除的排他文件生成的backup_label文件不一样。
*/
if (sscanf(labelfile, "START WAL LOCATION: %X/%X (file %24s)%c",
&hi, &lo, startxlogfilename,
&ch) != 4 || ch != '\n')
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
startpoint = ((uint64) hi) << 32 | lo;
remaining = strchr(labelfile, '\n') + 1; /* %n is not portable enough */
/*
* 在剩余内容中解析 BACKUP FROM 行.
*/
ptr = strstr(remaining, "BACKUP FROM:");
if (!ptr || sscanf(ptr, "BACKUP FROM: %19s\n", backupfrom) != 1)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
if (strcmp(backupfrom, "standby") == 0 && !backup_started_in_recovery)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("the standby was promoted during online backup"),
errhint("This means that the backup being taken is corrupt "
"and should not be used. "
"Try taking another online backup.")));
/* 如果处于恢复阶段,要做一些设置,略 */
if (backup_started_in_recovery)
{
…
}
else
{
/*
* Write the backup-end xlog record,写backup-end的XLOG记录
*/
XLogBeginInsert();
XLogRegisterData((char *) (&startpoint), sizeof(startpoint));
stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END);
stoptli = ThisTimeLineID;
/*
* Force a switch to a new xlog segment file, 强制日志切换
*/
RequestXLogSwitch(false);
XLByteToPrevSeg(stoppoint, _logSegNo, wal_segment_size);
XLogFileName(stopxlogfilename, stoptli, _logSegNo, wal_segment_size);
/* Use the log timezone here, not the session timezone */
stamp_time = (pg_time_t) time(NULL);
pg_strftime(strfbuf, sizeof(strfbuf),
"%Y-%m-%d %H:%M:%S %Z",
pg_localtime(&stamp_time, log_timezone));
/*
* Write the backup history file,写备份历史文件
*/
XLByteToSeg(startpoint, _logSegNo, wal_segment_size);
BackupHistoryFilePath(histfilepath, stoptli, _logSegNo,
startpoint, wal_segment_size);
fp = AllocateFile(histfilepath, "w");
if (!fp)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m",
histfilepath)));
fprintf(fp, "START WAL LOCATION: %X/%X (file %s)\n",
LSN_FORMAT_ARGS(startpoint), startxlogfilename);
fprintf(fp, "STOP WAL LOCATION: %X/%X (file %s)\n",
LSN_FORMAT_ARGS(stoppoint), stopxlogfilename);
/*
* Transfer remaining lines including label and start timeline to history file.
*/
fprintf(fp, "%s", remaining);
fprintf(fp, "STOP TIME: %s\n", strfbuf);
fprintf(fp, "STOP TIMELINE: %u\n", stoptli);
if (fflush(fp) || ferror(fp) || FreeFile(fp))
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not write file \"%s\": %m",
histfilepath)));
/*
* 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 the archiver that history file may be archived immediately. 清理备份历史信息
*/
CleanupBackupHistory();
}
/*
* 如果是归档模式,且设置了waitforarchive,则等待所需日志归档完成
*/
if (waitforarchive &&
((!backup_started_in_recovery && XLogArchivingActive()) ||
(backup_started_in_recovery && XLogArchivingAlways())))
{
//等待所需日志归档完成,略
}
/*
* 如果不是归档模式,但又设置了waitforarchive,发送提醒给用户,需要自己保证备份期间的WAL文件存在
*/
else if (waitforarchive)
ereport(NOTICE,
(errmsg("WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup")));
/*
* We're done. As a convenience, return the ending WAL location.
* 函数执行结束,返回WAL结束位置
*/
if (stoptli_p)
*stoptli_p = stoptli;
return stoppoint;
}
三、 pg_stop_backup_callback函数
前面代码中有一段
PG_ENSURE_ERROR_CLEANUP(pg_stop_backup_callback, (Datum) BoolGetDatum(exclusive));
确保如果PG_ENSURE_ERROR_CLEANUP中的代码运行失败,则将排他备份状态改回运行中。
/*
* Error cleanup callback for pg_stop_backup
*/
static void
pg_stop_backup_callback(int code, Datum arg)
{
bool exclusive = DatumGetBool(arg);
/* Update backup status on failure */
WALInsertLockAcquireExclusive();
if (exclusive)
{
Assert(XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_STOPPING);
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_IN_PROGRESS;
}
WALInsertLockRelease();
}
参考
《PostgreSQL技术内幕:事务处理深度探索》第4章