一、 备份流程
pg_rman 的主备份流程由 do_backup() 函数协调,该函数位于 backup.c 文件中 backup.c:821-1041 。
1. 流程图


2. 详细步骤
-
参数验证:验证必需参数如 PGDATA、备份模式等 backup.c:842-862
-
获取备份目录锁 :使用
catalog_lock()防止并发备份 backup.c:912-922 -
初始化备份状态:设置备份状态为 RUNNING,初始化时间戳和大小统计 backup.c:924-938
-
备份数据库文件 :调用
do_backup_database()执行主要备份 backup.c:969 -
备份归档WAL :调用
do_backup_arclog()备份已归档的WAL文件 backup.c:972 -
备份服务器日志 :如果配置了
with_serverlog,调用do_backup_srvlog()backup.c:975 -
更新状态:将备份状态更新为 DONE backup.c:979-983
-
清理旧文件:根据保留策略删除旧备份和日志文件 backup.c:1004-1012
-
释放锁 :调用
catalog_unlock()释放目录锁 backup.c:1037-1038
3. 分支流程
全量备份 vs 增量备份分支
在 do_backup_database() 函数中,根据备份模式选择不同的处理路径 backup.c:193-232 :

后面我们会继续学习
备库服务器备份分支
如果检测到是从备库备份,会执行特殊处理 backup.c:868-878 :

快照脚本分支
如果存在快照脚本,会使用快照方式进行备份 backup.c:241-343 :

错误处理分支
使用 pgut_atexit_push() 注册清理函数,确保在错误时正确清理 backup.c:960-966 :
说明
- 所有分支最终都会汇聚到主流程的状态更新和清理步骤 backup.c:979-1041
- 增量备份需要同一时间线上的有效全量备份作为基准 backup.c:200-216
- 从备库备份不支持快照脚本方式 backup.c:289-301
- 备份过程中会生成
backup_label和tablespace_map文件用于恢复 backup.c:269-272
二、 主备份函数 do_backup()
1. 函数签名和参数处理
do_backup() 函数接收 pgBackupOption 结构体参数,包含所有备份配置选项 backup.c:821-837 :
cpp
int
do_backup(pgBackupOption bkupopt)
{
parray *backup_list;
parray *files_database;
parray *files_arclog;
parray *files_srvlog;
int ret;
char path[MAXPGPATH];
/* repack the necessary options */
int keep_arclog_files = bkupopt.keep_arclog_files;
int keep_arclog_days = bkupopt.keep_arclog_days;
int keep_srvlog_files = bkupopt.keep_srvlog_files;
int keep_srvlog_days = bkupopt.keep_srvlog_days;
int keep_data_generations = bkupopt.keep_data_generations;
int keep_data_days = bkupopt.keep_data_days;
| 参数 | 类型 | 默认值 | 作用对象 |
|---|---|---|---|
keep_arclog_files |
int | KEEP_INFINITE |
归档WAL文件数量 |
keep_arclog_days |
int | KEEP_INFINITE |
归档WAL保留天数 |
keep_srvlog_files |
int | KEEP_INFINITE |
服务器日志文件数量 |
keep_srvlog_days |
int | KEEP_INFINITE |
服务器日志保留天数 |
keep_data_generations |
int | KEEP_INFINITE |
数据备份保留代数 |
keep_data_days |
int | KEEP_INFINITE |
数据备份保留天数 |
2. 初始化和验证阶段
必需参数验证
PGDATA路径必须指定 backup.c:842-845BACKUP_MODE必须有效 backup.c:847-850- 归档WAL备份需要
ARCLOG_PATHbackup.c:853-856 - 服务器日志备份需要
SRVLOG_PATHbackup.c:859-862
cpp
/* PGDATA and BACKUP_MODE are always required */
if (pgdata == NULL)
ereport(ERROR,
(errcode(ERROR_ARGS),
errmsg("required parameter not specified: PGDATA (-D, --pgdata)")));
if (current.backup_mode == BACKUP_MODE_INVALID)
ereport(ERROR,
(errcode(ERROR_ARGS),
errmsg("required parameter not specified: BACKUP_MODE (-b, --backup-mode)")));
/* ARCLOG_PATH is required only when backup archive WAL */
if (HAVE_ARCLOG(¤t) && arclog_path == NULL)
ereport(ERROR,
(errcode(ERROR_ARGS),
errmsg("required parameter not specified: ARCLOG_PATH (-A, --arclog-path)")));
/* SRVLOG_PATH is required only when backup serverlog */
if (current.with_serverlog && srvlog_path == NULL)
ereport(ERROR,
(errcode(ERROR_ARGS),
errmsg("required parameter not specified: SRVLOG_PATH (-S, --srvlog-path)")));
备库检测
通过检查 recovery.conf 或 standby.signal 文件判断是否从备用服务器备份 backup.c:868-878 。如果是备用服务器,必须提供主机和端口信息。
cpp
if (get_standby_signal_filepath(path, sizeof(path)))
{
if (!bkupopt.standby_host || !bkupopt.standby_port)
ereport(ERROR,
(errcode(ERROR_SYSTEM),
errmsg("please specify both standby host and port")));
current.is_from_standby = true;
}
else
current.is_from_standby = false;
压缩支持检查
检查 zlib 支持并在不可用时发出警告 backup.c:880-888 。
cpp
#ifndef HAVE_LIBZ
if (current.compress_data)
{
ereport(WARNING,
(errmsg("this pg_rman build does not support compression"),
errhint("Please build PostgreSQL with zlib to use compression.")));
current.compress_data = false;
}
#endif
控制文件和系统标识符验证
读取 PostgreSQL 控制文件获取 WAL 段大小,验证系统标识符匹配 backup.c:890-900 。
cpp
controlFile = get_controlfile(pgdata, &crc_ok);
if (!crc_ok)
ereport(WARNING,
(errmsg("control file appears to be corrupt"),
errdetail("Calculated CRC checksum does not match value stored in file.")));
wal_segment_size = controlFile->xlog_seg_size;
pg_free(controlFile);
/* Check that we're working with the correct database cluster */
check_system_identifier();
3. 备份执行阶段
目录锁和状态初始化
获取备份目录的排他锁,防止并发备份 backup.c:912-923 。初始化备份状态为 BACKUP_STATUS_RUNNING backup.c:925-939 。
cpp
/* get exclusive lock of backup catalog */
ret = catalog_lock();
if (ret == -1)
ereport(ERROR,
(errcode(ERROR_SYSTEM),
errmsg("could not lock backup catalog")));
else if (ret == 1)
ereport(ERROR,
(errcode(ERROR_ALREADY_RUNNING),
errmsg("could not lock backup catalog"),
errdetail("Another pg_rman is just running. Skip this backup.")));
/* initialize backup result */
current.status = BACKUP_STATUS_RUNNING;
current.tli = 0; /* get from result of pg_backup_start() */
current.start_lsn = current.stop_lsn = (XLogRecPtr) 0;
current.start_time = time(NULL);
current.end_time = (time_t) 0;
current.total_data_bytes = BYTES_INVALID;
current.read_data_bytes = BYTES_INVALID;
current.read_arclog_bytes = BYTES_INVALID;
current.read_srvlog_bytes = BYTES_INVALID;
current.write_bytes = 0; /* write_bytes is valid always */
current.block_size = BLCKSZ;
current.wal_block_size = XLOG_BLCKSZ;
current.recovery_xid = 0;
current.recovery_time = (time_t) 0;
备份目录创建
创建备份目录结构并写入初始的 backup.ini 文件 backup.c:941-948 。
cpp
/* create backup directory and backup.ini */
if (!check)
{
if (pgBackupCreateDir(¤t))
ereport(ERROR,
(errcode(ERROR_SYSTEM),
errmsg("could not create backup directory")));
pgBackupWriteIni(¤t);
}
elog(DEBUG, "destination directories of backup are initialized");
错误处理机制注册
使用 pgut_atexit_push() 注册清理函数 backup_cleanup() backup.c:960-966 。
cpp
/* set the error processing function for the backup process */
pgut_atexit_push(backup_cleanup, NULL);
/*
* Signal for backup_cleanup() that there may actually be some cleanup
* for it to do from this point on.
*/
in_backup = true;
三阶段备份执行
按顺序执行三种备份:
- 数据库文件备份:
do_backup_database(backup_list, bkupopt)backup.c:969 - 归档WAL备份:
do_backup_arclog(backup_list)backup.c:972 - 服务器日志备份:
do_backup_srvlog(backup_list)backup.c:975
cpp
/* backup data */
files_database = do_backup_database(backup_list, bkupopt);
/* backup archived WAL */
files_arclog = do_backup_arclog(backup_list);
/* backup serverlog */
files_srvlog = do_backup_srvlog(backup_list);
4. 清理和完成阶段
状态更新
备份完成后更新状态为 BACKUP_STATUS_DONE 并记录结束时间 backup.c:980-983 。
cpp
/* update backup status to DONE */
current.end_time = time(NULL);
current.status = BACKUP_STATUS_DONE;
if (!check)
pgBackupWriteIni(¤t);
旧文件清理
根据保留策略删除旧的归档WAL和服务器日志文件 backup.c:1004-1009 。
cpp
/*
* Delete old files (archived WAL and serverlog) after update of status.
*/
if (HAVE_ARCLOG(¤t))
delete_old_files(arclog_path, files_arclog, keep_arclog_files,
keep_arclog_days, true);
if (current.with_serverlog)
delete_old_files(srvlog_path, files_srvlog, keep_srvlog_files,
keep_srvlog_days, false);
备份清理
删除旧的数据备份文件 backup.c:1012
cpp
/* Delete old backup files after all backup operation. */
pgBackupDelete(keep_data_generations, keep_data_days);
三、 do_backup_database() 函数
函数签名和初始化
static parray *do_backup_database(parray *backup_list, pgBackupOption bkupopt)
函数接收现有备份列表和备份选项,返回备份的文件列表 backup.c:80-93 。
前置检查和准备
1. 服务器版本检查
调用 check_server_version() 验证 PostgreSQL 版本兼容性 backup.c:94 。
2. 数据校验和初始化
调用 init_data_checksum_enabled() 从控制文件读取数据校验和设置 backup.c:96 。
3. 备份模式验证
检查当前备份模式是否需要数据库备份 backup.c:98-127 :
- 对于归档WAL备份,检查是否存在有效的全量备份
- 如果没有有效备份且设置了
full_backup_on_error,自动转为全量备份
备份执行流程
1. PostgreSQL 备份启动
调用 pg_backup_start() 通知 PostgreSQL 开始备份 backup.c:136-138 :
- 生成包含时间戳的备份标签
- 执行平滑检查点(如果配置)
- 获取备份起始 LSN
2. 备用服务器重启点
如果从备用服务器备份,执行重启点确保数据一致性 backup.c:141-151 。
3. 目录结构生成
生成 mkdirs.sh 脚本用于恢复时重建目录结构 backup.c:154-178 。
4. 增量备份基准查找
对于增量备份,查找上次验证的备份作为基准 backup.c:185-232 :
- 获取基准备份的文件列表
- 设置 LSN 基准用于增量过滤
文件备份实现
1. 快照脚本检查
检查是否存在快照脚本,决定使用快照备份还是文件复制备份 backup.c:241-243 。
2. 普通文件复制备份
如果没有快照脚本:
- 列出 PGDATA 中的所有文件 backup.c:252
- 根据备份模式记录调试信息 backup.c:254-257
- 调用
backup_files()执行实际文件复制 backup.c:263
3. 快照备份
如果存在快照脚本:
- 验证不支持从备用服务器进行快照备份 backup.c:289-301
- 执行表空间快照和挂载操作
- 处理快照卷中的文件
备份完成和清理
1. PostgreSQL 备份停止
调用 pg_backup_stop() 结束备份模式并获取备份标签文件 backup.c:269 。
2. 文件列表生成
创建 file_database.txt 记录所有备份的文件 backup.c:278 。
关键实现细节
错误处理机制
- 使用
pgut_atexit_push()/pgut_atexit_pop()确保异常时正确清理 backup_cleanup()函数处理备份失败时的清理工作- 严格的参数验证和状态检查
并发控制
- 通过目录锁防止多个备份同时运行
- 备份状态跟踪确保操作原子性
性能优化
- 增量备份只复制变更的文件
- 压缩选项减少存储空间
- 进度显示支持大备份的监控
四、 三种备份的细节
三种备份的判断条件
1. 数据库文件备份判断条件
do_backup_database() 的执行条件基于 HAVE_DATABASE(¤t) 宏 backup.c:98-99 :
#define HAVE_DATABASE(backup) ((backup)->backup_mode >= BACKUP_MODE_INCREMENTAL)
- 全量备份 (
BACKUP_MODE_FULL):始终执行 - 增量备份 (
BACKUP_MODE_INCREMENTAL):需要存在已验证的基准备份 - 归档WAL备份 (
BACKUP_MODE_ARCHIVE):仅在没有有效全量备份时执行
2. 归档WAL备份判断条件
do_backup_arclog() 的执行条件基于 HAVE_ARCLOG(¤t) 宏 backup.c:616-617 :
#define HAVE_ARCLOG(backup) ((backup)->backup_mode >= BACKUP_MODE_ARCHIVE)
- 仅在备份模式为
BACKUP_MODE_ARCHIVE或更高时执行 - 需要配置
arclog_path参数 backup.c:853-856
3. 服务器日志备份判断条件
do_backup_srvlog() 的执行条件基于 current.with_serverlog 标志 backup.c:748-749 :
- 仅在用户明确指定
-s/--with-serverlog选项时执行 - 需要配置
srvlog_path参数 backup.c:859-862
实现细节对比
数据库文件备份实现
核心流程:
- 调用
pg_backup_start()通知 PostgreSQL 开始备份 backup.c:136-138 - 生成目录结构脚本
mkdirs.shbackup.c:154-178 - 查找增量备份基准(如需要) backup.c:193-232
- 执行文件备份(支持快照和普通复制) backup.c:241-263
- 调用
pg_backup_stop()结束备份 backup.c:269
特殊处理:
- 支持从备用服务器备份,需要执行重启点 backup.c:141-151
- 增量备份依赖 LSN 比较和文件修改时间 backup.c:224-230
归档WAL备份实现
核心流程:
- 检查备份模式,不满足条件直接返回 backup.c:616-617
- 如果数据库未备份,调用
pg_switch_wal()切换WAL backup.c:629-630 - 查找上次归档备份作为增量基准 backup.c:636-645
- 列出并过滤WAL文件(基于时间戳) backup.c:647-669
- 备份WAL文件到
arclog/目录 backup.c:671-674 - 特殊处理时间线历史文件 backup.c:709-721
关键特点:
- 基于WAL文件名的时间戳过滤,只备份到
stop_lsn为止的文件 - 独立于数据库备份,可以单独执行
服务器日志备份实现
核心流程:
- 检查
with_serverlog标志 backup.c:748-749 - 查找上次服务器日志备份作为增量基准 backup.c:764-773
- 列出服务器日志文件 backup.c:775-777
- 备份文件到
srvlog/目录 backup.c:779-780
关键特点:
- 最简单的备份类型,仅复制日志文件
- 不需要与PostgreSQL服务器交互
- 支持基于文件修改时间的增量备份
代码实现的主要区别
| 特性 | 数据库文件备份 | 归档WAL备份 | 服务器日志备份 |
|---|---|---|---|
| 入口函数 | do_backup_database() backup.c:80-93 |
do_backup_arclog() backup.c:602-603 |
do_backup_srvlog() backup.c:736-737 |
| 判断条件 | backup_mode >= BACKUP_MODE_INCREMENTAL |
backup_mode >= BACKUP_MODE_ARCHIVE |
with_serverlog == true |
| PostgreSQL交互 | 需要(pg_backup_start/stop) | 需要(pg_switch_wal) | 不需要 |
| 增量基准 | 基于LSN和文件列表 | 基于文件列表 | 基于文件列表 |
| 特殊处理 | 备用服务器重启点、快照支持 | WAL切换、时间线历史 | 无 |
| 目标目录 | database/ |
arclog/ |
srvlog/ |
执行顺序和依赖关系
在 do_backup() 主函数中,三种备份按固定顺序执行:
- 数据库文件备份:首先执行,为其他备份提供时间基准
- 归档WAL备份 :其次执行,依赖数据库备份的
stop_lsn - 服务器日志备份:最后执行,独立于前两者
这种顺序确保了备份的一致性和完整性,数据库备份提供的LSN信息用于限制WAL备份的范围。
Notes
- 三种备份可以独立配置,但数据库备份是其他备份的基础
- 归档WAL备份和服务器日志备份都支持增量模式,但实现机制不同
- 数据库备份是最复杂的,涉及与PostgreSQL的深度集成
- 每种备份都有独立的文件列表和元数据管理机制
未完待续
参考:https://deepwiki.com/ossc-db/pg_rman/4.1-backup-operations#main-backup-flow