• postgresql源码学习(37)—— 备份还原① - do_pg_start_backup函数


    关于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位置
    1. /*
    2. * do_pg_start_backup
    3. *
    4. * Utility function called at the start of an online backup. It creates the necessary starting checkpoint and constructs the backup label file.
    5. * Every successfully started non-exclusive backup must be stopped by calling do_pg_stop_backup() or do_pg_abort_backup().
    6. */
    7. XLogRecPtr
    8. do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p,
    9. StringInfo labelfile, List **tablespaces,
    10. StringInfo tblspcmapfile)
    11. {
    12. bool exclusive = (labelfile == NULL);
    13. bool backup_started_in_recovery = false;
    14. XLogRecPtr checkpointloc;
    15. XLogRecPtr startpoint;
    16. TimeLineID starttli;
    17. pg_time_t stamp_time;
    18. char strfbuf[128];
    19. char xlogfilename[MAXFNAMELEN];
    20. XLogSegNo _logSegNo;
    21. struct stat stat_buf;
    22. FILE *fp;
    23. /* 当前是否在恢复阶段(包括有从库) */
    24. backup_started_in_recovery = RecoveryInProgress();
    25. /*
    26. * Currently only non-exclusive backup can be taken during recovery.
    27. 恢复阶段只能执行非排他备份,若执行排他备份,则直接报错
    28. */
    29. if (backup_started_in_recovery && exclusive)
    30. ereport(ERROR,
    31. (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    32. errmsg("recovery is in progress"),
    33. errhint("WAL control functions cannot be executed during recovery.")));
    34. /*
    35. * 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.
    36. 如果在恢复阶段(包括从库),也不需要检查WAL等级,因为如果WAL等级不满足要求是不可能在恢复阶段的,直接报错
    37. */
    38. if (!backup_started_in_recovery && !XLogIsNeeded())
    39. ereport(ERROR,
    40. (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    41. errmsg("WAL level not sufficient for making an online backup"),
    42. errhint("wal_level must be set to \"replica\" or \"logical\" at server start.")));
    43. /* 备份标签过长,报错 */
    44. if (strlen(backupidstr) > MAXPGPATH)
    45. ereport(ERROR,
    46. (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
    47. errmsg("backup label too long (max %d bytes)",
    48. MAXPGPATH)));
    49. /* 后面修改forcePageWrites参数需要先获取InsertLock */
    50. WALInsertLockAcquireExclusive();
    51. /* 如果是排他备份 */
    52. if (exclusive)
    53. {
    54. /*
    55. * 首先标记我们正在执行排他备份,以确认当前没有其他会话在在执行pg_start_backup() 或者 pg_stop_backup()
    56. */
    57. if (XLogCtl->Insert.exclusiveBackupState != EXCLUSIVE_BACKUP_NONE)
    58. {
    59. WALInsertLockRelease();
    60. ereport(ERROR,
    61. (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    62. errmsg("a backup is already in progress"),
    63. errhint("Run pg_stop_backup() and try again.")));
    64. }
    65. /* 标记开始排他备份 */
    66. XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_STARTING;
    67. }
    68. /* 非排他备份 */
    69. else
    70. /* 计数器+1 */
    71. XLogCtl->Insert.nonExclusiveBackups++;
    72. /* 强制开启全页写 */
    73. XLogCtl->Insert.forcePageWrites = true;
    74. WALInsertLockRelease();
    75. /* Ensure we release forcePageWrites if fail below,如果下面代码运行失败,确保要取消forcePageWrites */
    76. PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
    77. {
    78. bool gotUniqueStartpoint = false;
    79. DIR *tblspcdir;
    80. struct dirent *de;
    81. tablespaceinfo *ti;
    82. int datadirpathlen;
    83. /* 创建检查点前强制执行日志切换(如果当前在恢复阶段,则不强制切换)
    84. */
    85. if (!backup_started_in_recovery)
    86. RequestXLogSwitch(false);
    87. do
    88. {
    89. bool checkpointfpw;
    90. /*
    91. * 强制创建检查点
    92. * 如果在恢复阶段,尽可能创建一个restartpoint,我们用最近一次的restartpoint作为备份开始的检查点。
    93. * 如果用户使用了fast参数,则以 CHECKPOINT_IMMEDIATE方式创建检查点.
    94. */
    95. RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT |
    96. (fast ? CHECKPOINT_IMMEDIATE : 0));
    97. /* 从控制文件中获取检查点位置、redo点位置、时间线ID、全页写设置信息 */
    98. LWLockAcquire(ControlFileLock, LW_SHARED);
    99. checkpointloc = ControlFile->checkPoint;
    100. startpoint = ControlFile->checkPointCopy.redo;
    101. starttli = ControlFile->checkPointCopy.ThisTimeLineID;
    102. checkpointfpw = ControlFile->checkPointCopy.fullPageWrites;
    103. LWLockRelease(ControlFileLock);
    104. /* 如果处于恢复阶段,要做一些设置,略 */
    105. /*
    106. * 如果有两个基础备份同时在运行,需要确保它们的检查点开始位置不同,因为pg在end-of-backup WAL 记录中会用其作为基础备份的唯一标记,并写入备份历史文件
    107. */
    108. WALInsertLockAcquireExclusive();
    109. if (XLogCtl->Insert.lastBackupStart < startpoint)
    110. {
    111. XLogCtl->Insert.lastBackupStart = startpoint;
    112. gotUniqueStartpoint = true;
    113. }
    114. WALInsertLockRelease();
    115. } while (!gotUniqueStartpoint);
    116. XLByteToSeg(startpoint, _logSegNo, wal_segment_size);
    117. XLogFileName(xlogfilename, starttli, _logSegNo, wal_segment_size);
    118. /*
    119. * Construct tablespace_map file. If caller isn't interested in this, we make a local StringInfo. 构建表空间映射文件
    120. */
    121. if (tblspcmapfile == NULL)
    122. tblspcmapfile = makeStringInfo();
    123. datadirpathlen = strlen(DataDir);
    124. /* Collect information about all tablespaces,收集所有表空间信息 */
    125. tblspcdir = AllocateDir("pg_tblspc");
    126. while ((de = ReadDir(tblspcdir, "pg_tblspc")) != NULL)
    127. {
    128. }
    129. FreeDir(tblspcdir);
    130. /*
    131. * Construct backup label file. If caller isn't interested in this, we make a local StringInfo. 构建backup_label文件
    132. */
    133. if (labelfile == NULL)
    134. labelfile = makeStringInfo();
    135. /* Use the log timezone here, not the session timezone,填充backup_label文件内容 */
    136. stamp_time = (pg_time_t) time(NULL);
    137. pg_strftime(strfbuf, sizeof(strfbuf),
    138. "%Y-%m-%d %H:%M:%S %Z",
    139. pg_localtime(&stamp_time, log_timezone));
    140. appendStringInfo(labelfile, "START WAL LOCATION: %X/%X (file %s)\n",
    141. LSN_FORMAT_ARGS(startpoint), xlogfilename);
    142. appendStringInfo(labelfile, "CHECKPOINT LOCATION: %X/%X\n",
    143. LSN_FORMAT_ARGS(checkpointloc));
    144. appendStringInfo(labelfile, "BACKUP METHOD: %s\n",
    145. exclusive ? "pg_start_backup" : "streamed");
    146. appendStringInfo(labelfile, "BACKUP FROM: %s\n",
    147. backup_started_in_recovery ? "standby" : "primary");
    148. appendStringInfo(labelfile, "START TIME: %s\n", strfbuf);
    149. appendStringInfo(labelfile, "LABEL: %s\n", backupidstr);
    150. appendStringInfo(labelfile, "START TIMELINE: %u\n", starttli);
    151. /* 写backup_label文件,略 */
    152. /* 写备份表空间映射文件,略 */
    153. }
    154. }
    155. PG_END_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
    156. /*
    157. * Mark that start phase has correctly finished for an exclusive backup. 标记排他备份的开始阶段已正确结束
    158. */
    159. if (exclusive)
    160. {
    161. WALInsertLockAcquireExclusive();
    162. XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_IN_PROGRESS;
    163. /* Set session-level lock */
    164. sessionBackupState = SESSION_BACKUP_EXCLUSIVE;
    165. WALInsertLockRelease();
    166. }
    167. else
    168. sessionBackupState = SESSION_BACKUP_NON_EXCLUSIVE;
    169. /*
    170. * We're done. As a convenience, return the starting WAL location.
    171. * 运行结束,返回起始的WAL位置
    172. */
    173. if (starttli_p)
    174. *starttli_p = starttli;
    175. return startpoint;
    176. }

    三、 pg_start_backup_callback函数

    前面代码中有一段

    PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));

    确保如果PG_ENSURE_ERROR_CLEANUP中的代码运行失败,务必要取消掉forcePageWrites。

    1. /* Error cleanup callback for pg_start_backup */
    2. static void
    3. pg_start_backup_callback(int code, Datum arg)
    4. {
    5. bool exclusive = DatumGetBool(arg);
    6. /* Update backup counters and forcePageWrites on failure,更新备份计数器和forcePageWrites状态 */
    7. WALInsertLockAcquireExclusive();
    8. if (exclusive)
    9. {
    10. Assert(XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_STARTING);
    11. XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_NONE;
    12. }
    13. else
    14. {
    15. Assert(XLogCtl->Insert.nonExclusiveBackups > 0);
    16. XLogCtl->Insert.nonExclusiveBackups--;
    17. }
    18. if (XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_NONE &&
    19. XLogCtl->Insert.nonExclusiveBackups == 0)
    20. {
    21. XLogCtl->Insert.forcePageWrites = false;
    22. }
    23. WALInsertLockRelease();
    24. }

    参考

    《PostgreSQL技术内幕:事务处理深度探索》第4章

    pg 备份恢复(一)—— 热备份_Hehuyi_In的博客-CSDN博客_pg数据库热备份

    postgresql源码学习(34)—— 事务日志⑩ - 全页写机制_Hehuyi_In的博客-CSDN博客

  • 相关阅读:
    【tgcalls】Instance接口的实例类的创建
    Windows安装MongoDB
    SpringBoot 如何使用 CORS 进行跨域资源共享
    决策树(decision tree)
    【redis】ssm项目整合redis,redis注解式缓存及应用场景,redis的击穿、穿透、雪崩的解决方案
    杰哥教你面试之一百问系列:java中高级多线程concurrent的使用
    python用cv2画图(line, rectangle, text等)
    【c#】关于web api发布
    408知识框架总结——计算机网络
    基于电商平台的商品的关键词文本匹配任务 有代码有数据
  • 原文地址:https://blog.csdn.net/Hehuyi_In/article/details/126320885