一、 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 backuppg_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 backuppg_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章

postgresql源码学习(38)—— 备份还原② - do_pg_stop_backup函数相关推荐

  1. PostgreSQL源码学习(1)--PG13代码结构

    PostgreSQL源码学习(1)–PG13代码结构 PostgreSQL代码结构 Bootstrap:用于支持Bootstrap运行模式,该模式主要用来创建初始的模板数据库. Main:主程序模块, ...

  2. PostgreSQL源码学习(一)编译安装与GDB入门

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 PostgreSQL源码学习(一)编译安装与GDB入门 前言 一.安装PostgreSQL 1.获取源码 2.配置 3.编译 3.安装 ...

  3. postgresql源码学习(27)—— 事务日志⑦-日志落盘上层函数 XLogFlush

    一. 预备知识 1. XLOG什么时候需要落盘 事务commit之前 log buffer被覆盖之前 后台进程定期落盘 2. 两个核心结构体 这两个结构体定义代码在xlog.c,它们在日志落盘过程中非 ...

  4. postgresql源码学习(51)—— 提交日志CLOG 原理 用途 管理函数

    一. CLOG是什么 CLOG(commit log)记录事务的最终状态. 物理上,是$PGDATA/pg_xact目录下的一些文件 逻辑上,是一个数组,下标为事务id,值为事务最终状态 1. 事务最 ...

  5. postgresql源码学习(49)—— MVCC⑤-cmin与cmax 同事务内的可见性判断

    一. 难以理解的场景 postgresql源码学习(十九)-- MVCC④-可见性判断 HeapTupleSatisfiesMVCC函数_Hehuyi_In的博客-CSDN博客 在前篇的可见性判断中有 ...

  6. postgresql源码学习(53)—— vacuum②-lazy vacuum之heap_vacuum_rel函数

    一. table_relation_vacuum函数 1. 函数定义 前篇最后(https://blog.csdn.net/Hehuyi_In/article/details/128749517),我 ...

  7. postgresql源码学习(52)—— vacuum①-准备工作与主要流程

    关于vacuum的基础知识,参考,本篇从源码层继续学习 https://blog.csdn.net/Hehuyi_In/article/details/102992065 https://blog.c ...

  8. postgresql源码学习(57)—— pg中的四种动态库加载方法

    一. 基础知识 1. 什么是库 库其实就是一些通用代码,可以在程序中重复使用,比如一些数学函数,可以不需要自己编写,直接调用相关函数即可实现,避免重复造轮子. 在linux中,支持两种类型的库: 1. ...

  9. postgresql源码学习(一)—— 源码编译安装与gdb调试入门

    一. postgresql源码编译安装 因为只是用来调试的测试环境,把基本的软件装好和库建好就可以,一切从简. 1. 创建用户和目录 mkdir -p /data/postgres/base/ mkd ...

  10. postgresql源码学习(九)—— 常规锁②-强弱锁与Fast Path

    一. 强锁与弱锁 根据兼容性表,彼此相容的3个锁(1-3级,AccessShareLock.RowShareLock.RowExclusiveLock)是弱锁,4级锁ShareUpdateExclus ...

最新文章

  1. 关于QSqlTableModel的使用说明(QT上创建本地SQL)
  2. 福利满满 | 天元MegEngine贡献者计划全面启动!
  3. 软件工程开篇自我介绍
  4. 网络技术术语英汉对照
  5. 正式发布!Azure Functions OpenAPI Extension
  6. 【Python 必会技巧】判断字符串是否为字母/数字/大小写/空白字符/有效标识符/可打印字符
  7. python列表转换成数字_Python中列表元素转为数字的方法分析
  8. Typora如何设置图片的默认保存路径
  9. Google将推游戏内广告技术 游戏中插入视频广告
  10. 软件测试——第三次作业
  11. ArcEngine创建IFeature的三种方法
  12. MySQL 和 MySQL Workbench图形化安装教程
  13. python 3d游戏 源码_毕设3D游戏《天鹰教》源码
  14. cso是什么职位(企业cso是什么职位)
  15. Linux系统无法在spyder5中输入中文的解决办法
  16. 根据银行卡号查询银行卡名称
  17. eToken 身份认证
  18. Radio RDS简介
  19. streamx编译,streamx-console初始化启动踩坑
  20. P4043 [AHOI2014/JSOI2014] 上下界最小费用流

热门文章

  1. SEO入门:网站站内优化流程
  2. win10升级助手_Win10自带杀毒软件如此强大,大家却不爱用,究竟是为什么呢?...
  3. TP5和TP3.2区别
  4. 数字化名词解释—数字化转型
  5. Pigeon发布流程
  6. mysql column specified twice_Mysql抛出Column 'descriptions' specified twice异常解决方法
  7. 电信光猫 TEWA-708E 登录超级管理员和开启DMZ
  8. Android11裁剪,Andorid 11调用系统裁剪
  9. java-web阶段性总结
  10. java网关详解_一篇让你彻底理解网关是什么的文章