关于pg备份的基础知识,参考 https://blog.csdn.net/Hehuyi_In/article/details/102641959
一、 pg中的pg_start_backup函数
pg的备份本质是是通过直接复制磁盘数据实现的,在全页写机制的文章中我们提到过,这可能会导致数据不一致。因此,在复制数据前必须做一些准备工作。
postgresql源码学习(34)—— 事务日志⑩ - 全页写机制_Hehuyi_In的博客-CSDN博客
1. 主要作用
pg_start_backup函数进行创建基础备份的准备工作(详细参考下方源码)。
2. 函数参数
注意这里指的是pg中pg_start_backup函数的参数,而不是源码中的函数参数。
pg_start_backup(label text [,fast boolean[,exclusive boolean]])
参数含义:
- label:用户定义的备份标签,一般使用备份文件名加日期
- fast:是否尽快开始备份,默认是false;若设置为true,会创建CHECKPOINT_IMMEDIATE类型的检查点
- exclusive:指定是否为排他备份
3. 备份模式
- 排他模式:运行期间其他会话不能再执行其他排他模式的备份(但非排他模式的可以)
- 非排他模式:运行期间其他会话可以执行排他或非排他模式的备份
非排他备份
函数返回值是备份开始的WAL位置(LSN),可以转为对应的日志文件
select pg_walfile_name('0/2000060');
排他备份
再发起一个排他备份,会报错
但发起非排他模式的可以
4. pg_is_in_backup函数
pg中还有一个pg_is_in_backup函数,用于检查当前是否在执行排他备份,但不能用它检查是否有非排他的备份在进行。
5. backup_label文件
排他备份会在$PGDATA目录下创建backup_label文件
backup_label文件包含以下几项 :
- START WAL LOCATION: WAL起始位置
- CHECKPOINT LOCATION:由该命令创建的检查点LSN位置
- BACKUP METHOD:备份方式,值为pg_start_backup或pg_basebackup,若只是配置流复制则为streamed
- BACKUP FROM:是在主库还是从库做的基础备份
- LABEL:在pg_start_backup中指定的标签
- STARTTIME:执行pg_start_backup的时间戳
如果是非排他模式,backup_label中的信息由每个备份进程自己维护,在执行pg_stop_backup函数时返回给用户。
二、 源码中的do_pg_start_backup函数
在源码中,pg_start_backup实际对应的是do_pg_start_backup函数。
1. 主要参数
- backupidstr:用户定义的备份标签,同前面介绍
- fast:是否尽快开始备份,同前面介绍
- starttli_p:起始时间线
- labelfile:标签文件
- tablespaces:表空间
- tblspcmapfile:表空间映射文件
2. 返回值
备份开始的WAL位置(LSN)
3. 主要流程
- 预检查,包括是否在恢复阶段、WAL日志等级、备份标签长度等
- 检查WAL等级
- 判断是否为排他备份
- 开启强制全页写 forcePageWrites
- 强制日志切换
- 强制创建检查点
- 从控制文件中获取检查点位置、redo点位置、时间线ID、全页写设置信息
- 返回备份开始的WAL位置
/*
* do_pg_start_backup
*
* Utility function called at the start of an online backup. It creates the necessary starting checkpoint and constructs the backup label file.
* Every successfully started non-exclusive backup must be stopped by calling do_pg_stop_backup() or do_pg_abort_backup().
*/
XLogRecPtr
do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p,
StringInfo labelfile, List **tablespaces,
StringInfo tblspcmapfile)
{
bool exclusive = (labelfile == NULL);
bool backup_started_in_recovery = false;
XLogRecPtr checkpointloc;
XLogRecPtr startpoint;
TimeLineID starttli;
pg_time_t stamp_time;
char strfbuf[128];
char xlogfilename[MAXFNAMELEN];
XLogSegNo _logSegNo;
struct stat stat_buf;
FILE *fp;
/* 当前是否在恢复阶段(包括有从库) */
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.
如果在恢复阶段(包括从库),也不需要检查WAL等级,因为如果WAL等级不满足要求是不可能在恢复阶段的,直接报错
*/
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 (strlen(backupidstr) > MAXPGPATH)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("backup label too long (max %d bytes)",
MAXPGPATH)));
/* 后面修改forcePageWrites参数需要先获取InsertLock */
WALInsertLockAcquireExclusive();
/* 如果是排他备份 */
if (exclusive)
{
/*
* 首先标记我们正在执行排他备份,以确认当前没有其他会话在在执行pg_start_backup() 或者 pg_stop_backup()
*/
if (XLogCtl->Insert.exclusiveBackupState != EXCLUSIVE_BACKUP_NONE)
{
WALInsertLockRelease();
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("a backup is already in progress"),
errhint("Run pg_stop_backup() and try again.")));
}
/* 标记开始排他备份 */
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_STARTING;
}
/* 非排他备份 */
else
/* 计数器+1 */
XLogCtl->Insert.nonExclusiveBackups++;
/* 强制开启全页写 */
XLogCtl->Insert.forcePageWrites = true;
WALInsertLockRelease();
/* Ensure we release forcePageWrites if fail below,如果下面代码运行失败,确保要取消forcePageWrites */
PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
{
bool gotUniqueStartpoint = false;
DIR *tblspcdir;
struct dirent *de;
tablespaceinfo *ti;
int datadirpathlen;
/* 创建检查点前强制执行日志切换(如果当前在恢复阶段,则不强制切换)
*/
if (!backup_started_in_recovery)
RequestXLogSwitch(false);
do
{
bool checkpointfpw;
/*
* 强制创建检查点
* 如果在恢复阶段,尽可能创建一个restartpoint,我们用最近一次的restartpoint作为备份开始的检查点。
* 如果用户使用了fast参数,则以 CHECKPOINT_IMMEDIATE方式创建检查点.
*/
RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT |
(fast ? CHECKPOINT_IMMEDIATE : 0));
/* 从控制文件中获取检查点位置、redo点位置、时间线ID、全页写设置信息 */
LWLockAcquire(ControlFileLock, LW_SHARED);
checkpointloc = ControlFile->checkPoint;
startpoint = ControlFile->checkPointCopy.redo;
starttli = ControlFile->checkPointCopy.ThisTimeLineID;
checkpointfpw = ControlFile->checkPointCopy.fullPageWrites;
LWLockRelease(ControlFileLock);
/* 如果处于恢复阶段,要做一些设置,略 */
…
/*
* 如果有两个基础备份同时在运行,需要确保它们的检查点开始位置不同,因为pg在end-of-backup WAL 记录中会用其作为基础备份的唯一标记,并写入备份历史文件
*/
WALInsertLockAcquireExclusive();
if (XLogCtl->Insert.lastBackupStart < startpoint)
{
XLogCtl->Insert.lastBackupStart = startpoint;
gotUniqueStartpoint = true;
}
WALInsertLockRelease();
} while (!gotUniqueStartpoint);
XLByteToSeg(startpoint, _logSegNo, wal_segment_size);
XLogFileName(xlogfilename, starttli, _logSegNo, wal_segment_size);
/*
* Construct tablespace_map file. If caller isn't interested in this, we make a local StringInfo. 构建表空间映射文件
*/
if (tblspcmapfile == NULL)
tblspcmapfile = makeStringInfo();
datadirpathlen = strlen(DataDir);
/* Collect information about all tablespaces,收集所有表空间信息 */
tblspcdir = AllocateDir("pg_tblspc");
while ((de = ReadDir(tblspcdir, "pg_tblspc")) != NULL)
{
…
}
FreeDir(tblspcdir);
/*
* Construct backup label file. If caller isn't interested in this, we make a local StringInfo. 构建backup_label文件
*/
if (labelfile == NULL)
labelfile = makeStringInfo();
/* Use the log timezone here, not the session timezone,填充backup_label文件内容 */
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));
appendStringInfo(labelfile, "START WAL LOCATION: %X/%X (file %s)\n",
LSN_FORMAT_ARGS(startpoint), xlogfilename);
appendStringInfo(labelfile, "CHECKPOINT LOCATION: %X/%X\n",
LSN_FORMAT_ARGS(checkpointloc));
appendStringInfo(labelfile, "BACKUP METHOD: %s\n",
exclusive ? "pg_start_backup" : "streamed");
appendStringInfo(labelfile, "BACKUP FROM: %s\n",
backup_started_in_recovery ? "standby" : "primary");
appendStringInfo(labelfile, "START TIME: %s\n", strfbuf);
appendStringInfo(labelfile, "LABEL: %s\n", backupidstr);
appendStringInfo(labelfile, "START TIMELINE: %u\n", starttli);
/* 写backup_label文件,略 */
/* 写备份表空间映射文件,略 */
…
}
}
PG_END_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
/*
* Mark that start phase has correctly finished for an exclusive backup. 标记排他备份的开始阶段已正确结束
*/
if (exclusive)
{
WALInsertLockAcquireExclusive();
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_IN_PROGRESS;
/* Set session-level lock */
sessionBackupState = SESSION_BACKUP_EXCLUSIVE;
WALInsertLockRelease();
}
else
sessionBackupState = SESSION_BACKUP_NON_EXCLUSIVE;
/*
* We're done. As a convenience, return the starting WAL location.
* 运行结束,返回起始的WAL位置
*/
if (starttli_p)
*starttli_p = starttli;
return startpoint;
}
三、 pg_start_backup_callback函数
前面代码中有一段
PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
确保如果PG_ENSURE_ERROR_CLEANUP中的代码运行失败,务必要取消掉forcePageWrites。
/* Error cleanup callback for pg_start_backup */
static void
pg_start_backup_callback(int code, Datum arg)
{
bool exclusive = DatumGetBool(arg);
/* Update backup counters and forcePageWrites on failure,更新备份计数器和forcePageWrites状态 */
WALInsertLockAcquireExclusive();
if (exclusive)
{
Assert(XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_STARTING);
XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_NONE;
}
else
{
Assert(XLogCtl->Insert.nonExclusiveBackups > 0);
XLogCtl->Insert.nonExclusiveBackups--;
}
if (XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_NONE &&
XLogCtl->Insert.nonExclusiveBackups == 0)
{
XLogCtl->Insert.forcePageWrites = false;
}
WALInsertLockRelease();
}
参考
《PostgreSQL技术内幕:事务处理深度探索》第4章