ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PostgreSQL物理备份利器pg_rman:从部署到恢复实战指南

PostgreSQL物理备份利器pg_rman:从部署到恢复实战指南

1. 为什么我最终把备份方案换成了 pg_rman

先说结论:如果你维护的是 PostgreSQL 生产库,还在用 pg_dump 定时跑逻辑备份,或者手工拼接 WAL 归档来做恢复,那大概率会在某个凌晨的故障里体会什么叫束手无策。pg_rman 这个工具,本质上是把 PostgreSQL 的物理备份和基于 WAL 的时间点恢复(PITR)整合成了一整套可管理的命令行流程,解决了手工操作中最容易出错的三个问题:备份文件组织混乱、增量备份难以判断基线、恢复时找不到该回放哪些 WAL 日志。

我刚接手某业务库的时候,备份方案还是最原始的“凌晨 pg_dump 全量 + 每半小时 WAL 归档脚本”。看起来每天都备份了,但真到了要恢复的时候,问题接二连三:dump 出来的文件恢复耗时数小时,且只恢复到 dump 时刻的状态;WAL 归档脚本自己管理目录,没有任何校验机制,某天归档目录快满了,脚本直接静默失败,我完全不知道。后来换了 pg_rman,这些由“备份元数据”和“目录结构自管理”带来的问题基本一次性解决。

先说下 pg_rman 的背景,它最早由某位日本数据库工程师主导开发,借鉴了 Oracle RMAN 的设计思路,专门为 PostgreSQL 提供一套统一的备份恢复接口。它不修改 PostgreSQL 本身的代码,而是在操作系统层通过调用 PostgreSQL 的备份协议接口来工作。核心特点可以归纳为:在线全量备份、基于块级的变化增量备份、自动管理备份文件的生命周期、配合 WAL 归档实现任意时间点恢复。

这篇文章我会尽量把 pg_rman 从部署到日常运维,到真正的故障恢复演练,全部讲清楚,包括我实际踩过的坑。适合谁看?正在为 PostgreSQL 选型备份方案的人,被恢复流程折磨过的 DBA,以及想搞清楚增量备份原理的开发运维同学。

2. 先把 pg_rman 的核心机制搞明白

2.1 备份目录里到底存了什么东西

pg_rman 最让我喜欢的一点,是它的备份目录结构是自解释的。你初始化一个 BACKUP_PATH 之后,它会在里面创建几个固定子目录,每个都对应一类东西。我拿一个实际的例子来说明:

BACKUP_PATH=/pg_rman_data ├── backup/ # 放每次备份的元数据和数据文件列表 ├── archive/ # WAL 归档文件的存放位置 ├── log/ # pg_rman 自身运行日志 ├── pg_rman.ini # 配置文件 └── srvlog/ # PostgreSQL 服务日志复制(可选)

backup/目录下是这样组织的:每执行一次备份,就会生成一个带时间戳的目录,比如20240115_000000,里面有一个名为backup.ini的文件,这是 pg_rman 认为最重要的元数据文件。这个文件里记录了本次备份的类型、起始和结束 LSN、备份时的 checkpoint 位置、数据库的系统标识符、WAL 文件的起止区间等。恢复的时候,pg_rman 就是靠这些信息来判断“这条备份链路是否完整,能从哪个时间点接续”。

举个例子,backup.ini中的关键字段:

BACKUP_MODE=FULL START_LSN=0/16000060 STOP_LSN=0/17000060 START_TIME='2024-01-15 00:00:00 JST' END_TIME='2024-01-15 00:01:50 JST'

这些信息不仅是 pg_rman 自己在用,你灾难恢复的时候,如果对某个备份文件的完整性存疑,也可以手动打开这个文件核对 LSN 范围,对照 WAL 归档目录里的文件名判断该回放哪些日志。我后面在“问题排查”那部分会详细说。

2.2 全量、增量、归档三种模式各自解决什么问题

pg_rman 的备份模式用-b参数控制,常用值有三个:full、incremental、archive。我分别说下它们的定位。

全量备份(full):把整个数据库集群的数据文件完整复制一份。这里的“完整”指的是 PostgreSQL 数据目录里的 base、pg_wal、pg_xact、pg_multixact、pg_tblspc 等所有物理文件,当然也有过滤,比如它会跳过 postmaster.pid、postgresql.auto.conf.tmp 这类运行时临时文件。全量备份是一个独立可用的基线,没有它,后续的增量备份和归档恢复都无从谈起。

增量备份(incremental):这是 pg_rman 最核心的卖点。它不是像 rsync 那样比较文件修改时间然后整文件复制,而是通过 PostgreSQL 内置的页级变更追踪机制(从内核层面记录数据块的变化),识别出自上次备份以来真正发生变化的那些数据块,只复制包含这些块的页面文件。效果直观来说:一个 1TB 的库,如果日变更量只有 2GB,那增量备份的时间可以缩短一个数量级,备份文件体积也小得多。它有一个硬性前提:必须先有一个全量备份作为基础。没有基线就做增量,pg_rman 会直接报错。

归档备份(archive):专门复制 WAL 归档文件。它不复制数据文件,只管把归档目录里新产生的 WAL 段文件收集到备份目录中。这个模式通常配合资源紧张的场景使用,比如你的存储只能保留最近 N 个 WAL 归档,但你又不想做全量备份时,可以单独做 archive 备份来固化某一时间段的 WAL。实际运维中我很少单独用它,但了解它的位置是有必要的,因为pg_rman show的输出里会把 archive 备份单独列出,你得能看懂它。

2.3 一个备份周期的完整生命周期

理解了模式之后,再看它在执行备份时到底做了哪些步骤。我用全量备份举例,pg_rman 执行pg_rman backup -b full时,大致的动作序列是:

  1. 检查前置条件:连接数据库、确认存在超级用户权限、确认archive_mode=on、确认 WAL 归档命令可执行、确认备份目录权限正确。
  2. 发起基准备份:调用 PostgreSQL 的pg_backup_start()(或者老版本的pg_start_backup()),产生一个数据库内一致的备份起点,记录 START_LSN。
  3. 复制数据文件:把数据目录下所有需要的文件复制到backup/时间戳/目录,这一步耗时最长。
  4. 结束备份:调用pg_backup_stop(),记录 STOP_LSN。此时数据库会自动切换到一个新的 WAL 段,确保从 START_LSN 到 STOP_LSN 的 WAL 都在归档路径里了。
  5. 复制 WAL 归档:把从 START_LSN 到 STOP_LSN 之间需要的 WAL 段文件复制到本次备份目录下的arf子目录,保证这个备份目录自身是一个完整可用的恢复点。
  6. 写入backup.ini元数据。

增量备份的流程类似,只是第 2 步换成了获取一个增量备份起点,第 3 步通过块级变更信息只复制变化的块。这也解释了一个很多新手困惑的现象:为什么 pg_rman 全量备份完成之后,归档目录里的 WAL 文件也会多出几个——那是pg_backup_stop()触发 WAL 切换产生的正常行为,不是故障。

3. 首次部署的完整步骤

3.1 PostgreSQL 侧的前置配置

pg_rman 是外部工具,但它备份的是 PostgreSQL 的物理文件,所以数据库本身必须先做好两件事:开启 WAL 归档、设置归档命令。

第一,确认archive_mode。PostgreSQL 里这个参数默认是 off,pg_rman 无法在不开归档的情况下工作。修改postgresql.conf:

archive_mode = on archive_command = 'test ! -f /pg_rman_data/archive/%f && cp %p /pg_rman_data/archive/%f'

archive_command我建议写成带test ! -f的防覆盖形式。原因是 PostgreSQL 在归档时,如果同名 WAL 文件已经存在,直接cp会覆盖旧文件。WAL 段文件有循环复用机制,在极端情况下可能出现同名但内容不同的情况(同一 WAL 段生命周期内数据库被重新初始化过等),防覆盖可以避免归档目录被莫名覆盖成错误文件。这条命令里的/pg_rman_data/archive必须和你后续在 pg_rman.ini 里设置的ARCLOG_PATH保持一致,否则 pg_rman 校验归档路径时会出现令人困惑的错误。

第二,确认wal_level。PostgreSQL 10 以上,archive_mode=on已经隐含要求wal_level=replica(或更高级别),如果你之前为了省 IO 把wal_level设成了minimal,启动归档时数据库会直接拒绝启动。这点检查一下即可。

第三,规划好备份目录的权限。pg_rman 通常以postgres操作系统用户执行(因为它需要读写数据库数据目录同权限的文件),所以 BACKUP_PATH 的属主一定要是postgres,并且归档路径、日志路径也都一样。我见过有人把 BACKUP_PATH 挂在 root 拥有的目录下,结果pg_rman init成功但 backup 时报权限错误,排查半天。

3.2 编译安装 pg_rman

pg_rman 通过源码编译安装的方式最稳妥,因为它需要和你正在运行的 PostgreSQL 主版本严格匹配。官方发布的是源码包,你要和 PostgreSQL 源码配合编译。大致过程:

# 下载 pg_rman 源码并解压 curl -O https://.../pg_rman-1.3.16.tar.gz tar xvf pg_rman-1.3.16.tar.gz cd pg_rman-1.3.16 # 指定 PostgreSQL 的 pg_config 路径进行编译 make PG_CONFIG=/usr/pgsql-15/bin/pg_config sudo make install

编译时有个容易踩的坑:如果你的系统上有多个 PostgreSQL 版本,一定要用PG_CONFIG指定正确的 pg_config。否则编译出来的 pg_rman 连接的是一个版本的 PostgreSQL,运行时却面对另一个版本的数据目录,轻则备份失败,重则在恢复时把错误版本的库文件写进生产目录。安装完验证一下版本:

pg_rman --version

如果输出的版本和当前 PostgreSQL 版本能对得上,说明依赖匹配正常。比如 PostgreSQL 14 一般对应 pg_rman 1.3.x 的某个新的版本,主版本号不能差太远。

编译安装之外,也可以用包管理器,但我不推荐在生产环境用发行版自带的旧版本 pg_rman。原因在于,PostgreSQL 每个大版本都可能调整控制文件或数据文件布局,旧版 pg_rman 可能不认识新版的数据目录结构,备份出来基本不可用。所以我的习惯是:升级 PostgreSQL 大版本时,一定同步升级 pg_rman。

3.3 初始化备份目录并验证配置

安装完成后,第一步是写 pg_rman.ini。可以直接把文件放在 BACKUP_PATH 下,也可以放在执行用户的家目录(~/.pg_rman.ini)。我建议放在 BACKUP_PATH 下,因为这样整个备份目录迁移到新机器时配置跟着走,不会遗忘。

我的常用配置模板:

BACKUP_PATH='/pg_rman_data' ARCLOG_PATH='/pg_rman_data/archive' SRVLOG_PATH='/var/lib/pgsql/15/data/log' COMPRESSION=1 COMPRESS_LEVEL=5 BACKUP_MODE=INCREMENTAL KEEP_DATA_GENERATIONS=3 KEEP_DATA_DAYS=14 SMOOTH_CHECKPOINT=OFF

简单解释几个关键项:

  • BACKUP_PATH:备份根目录,必须存在且属主正确。
  • ARCLOG_PATH:WAL 归档目录,和archive_command里写的目录保持一致。
  • COMPRESSION=1:启用压缩,COMPRESS_LEVEL是压缩级别,范围 1-9。这个开启后全量备份和增量备份的落盘文件都会走 zlib。压缩级别我一般用 5 或 6,再高性价比很差。
  • BACKUP_MODE=INCREMENTAL:默认备份模式,如果你不指定-b参数,就按这个模式执行。
  • KEEP_DATA_GENERATIONS=3:保留最近 3 轮备份周期。所谓一轮,指的是一个全量备份及其之后的增量备份集合。
  • KEEP_ARCLOG_FILES(可选):保留 WAL 归档文件数量上限,我建议显式设一个,避免归档文件无限堆积。
  • SMOOTH_CHECKPOINT:控制是否强制平滑检查点。OFF 表示让 PostgreSQL 自己决定检查点行为,通常备份时瞬间 IO 会略大;如果想减少备份对业务的影响,可以设为 ON,代价是备份时间变长。

写完之后执行初始化:

pg_rman init -B /pg_rman_data

这条命令会创建默认目录结构,并校验pg_rman.ini中声明的路径是否可用。如果只是想快速验证配置有没有问题,可以再用pg_rman check,它会模拟一次非实际的备份前校验,检查归档命令是否可执行、数据库可否连接等。我第一次部署时没跑 check,直接备份,结果报了一堆错,后来养成习惯:先 check,再 backup。

4. 日常备份操作与自动化

4.1 手动备份的常用命令示范

部署完成后,先做一次手动全量备份验证全链路。我建议第一次全量放在业务低峰期,因为尽管 pg_rman 支持在线备份,不阻塞读写,但复制大量数据文件依然会产生 IO 压力。

# 全量备份 pg_rman backup -b full -B /pg_rman_data # 查看备份列表 pg_rman show -B /pg_rman_data

show的输出是我日常检查备份最常用的接口,它有几种输出格式,默认是以天为单位按日期分组摘要,show detail显示每次备份的完整元数据:

pg_rman show detail -B /pg_rman_data

输出里最需要关注的是Status字段。备份刚完成时显示RUNNING,完成后校验通过显示OK,校验失败显示ERROR。如果看到ERROR,说明这个备份文件的完整性问题,不能作为恢复来源。

增量备份和全量备份的命令差异只差一个参数:

# 基于上一次备份的增量备份 pg_rman backup -b incremental -B /pg_rman_data

要注意 pg_rman 的增量是基于“上一次成功的备份”,不要求必须是全量。它的内部基线选择逻辑是:先用上次备份的backup.ini里的 LSN 作为起点,然后通过持续追踪的块级变更信息判断需要备份哪些块。你完全可以设计成:周一全量,周二到周日全做增量,每次增量都叠加。恢复时它会自动选择最近的全量备份加后续所有增量进行合并。

还有几个命令是必须记住的:

# 校验备份完整性,不实际恢复 pg_rman validate -B /pg_rman_data # 删除不再需要的备份(逻辑删除,仅标记) pg_rman delete 20240115_000000 -B /pg_rman_data # 物理清理标记为删除的备份文件 pg_rman purge -B /pg_rman_data

很多人对delete和purge的区别容易混淆。delete是逻辑删除,它只是在一个特殊的系统表中记录这个备份已标记删除,在show里你会看到一个 DELETED 标记,但文件还占着磁盘空间。purge才会真正释放磁盘。所以完整的清理套路是:先delete,再purge。不过在生产环境中,我基本都是靠自动清理策略,很少手工 delete。

4.2 配置自动清理策略

手工管理备份的保留策略很难不出错,尤其是备份文件越来越大的时候。pg_rman 的自动清理策略通过 pg_rman.ini 里的几个KEEP_*参数实现,它会在每次 backup 成功之后自动执行“标记过期备份并 purge”的动作。

我这里解释一下KEEP_DATA_GENERATIONS和KEEP_DATA_DAYS的用途。KEEP_DATA_GENERATIONS=3的意思是保留最近 3 轮“备份世代”。什么是世代的划分?一个全量备份算第一个世代,它之后的连续增量备份都归入这个世代;如果中间又做了一次全量备份,那从第二个全量开始就算新世代。这样配置后,pg_rman 会确保磁盘上始终保留完整可用的最近 3 轮链路,第 4 轮及更早的备份会被自动标记删除并清理。

KEEP_DATA_DAYS=14是一个时间维度上的保底策略,它保证即使某个世代包含的备份跨天数很多,也至少按天数保留 14 天的数据。如果两个参数同时配置,pg_rman 遵循“取两者中保留更多”的规则。

用这套自动清理策略之后,我基本不再手工清理旧备份,磁盘使用量也能维持稳定。但有个前提:备份目录所在文件系统的空间监控绝对不能省。自动清理只在“执行成功”的备份之后触发,如果连续几天的备份都失败了,旧的没被标记,磁盘依然会被撑满。所以监控磁盘 + 定时检查pg_rman show的备份状态是必须做的。

4.3 定时任务与备份健康巡检

生产环境中备份的日常执行靠 cron 定时任务,我的设计思路是高频增量 + 定期全量。一个典型的每日策略是:

  • 每天 20:00 执行一次全量备份。
  • 每小时执行一次增量备份。
  • WAL 归档由 PostgreSQL 实时开启,不做额外定时。

但更常见的做法是“每日凌晨全量 + 每 15 或 30 分钟增量”。这个频率取决于你的 RPO 目标。增量备份很轻量,频率拉高一点对系统的影响可接受;全量则尽量安排在业务最低谷。

cron 配置里有个细节:必须设置好 PATH 和 LD_LIBRARY_PATH。pg_rman 编译安装后,如果二进制在/usr/pgsql-15/bin这种非标准路径,cron 的默认 PATH 里是没有它的,会导致任务失败。我的做法是写一个 shell 脚本,初始化必要的环境变量后再执行 pg_rman:

#!/bin/bash export PATH=/usr/pgsql-15/bin:$PATH export LD_LIBRARY_PATH=/usr/pgsql-15/lib:$LD_LIBRARY_PATH LOG_FILE=/pg_rman_data/log/cron_backup.log echo "===== $(date '+%F %T') =====" >> $LOG_FILE pg_rman backup -b incremental -B /pg_rman_data >> $LOG_FILE 2>&1 # 检查本次备份是否成功,失败时发出告警 if ! grep -q "INFO: backup complete" $LOG_FILE; then echo "pg_rman backup FAILED at $(date '+%F %T')" >> $LOG_FILE # 这里可以调用告警脚本发送通知 fi

备份健康巡检最直接的方法是检查pg_rman show输出中每个备份的Status和Timeline属性。我用一个简单的脚本每天巡检:统计最近 24 小时内成功备份的数量、是否有状态非 OK 的备份,如果不符合预期就发告警。

pg_rman show -B /pg_rman_data | awk 'NR>2 {print $1, $2, $3}' | while read ts type status do if [ "$status" != "OK" ] && [ "$status" != "DELETED" ]; then echo "Abnormal backup found: $ts $type $status" fi done

这部分建议务必做到自动化巡检。我见过太多系统,备份任务一直在跑,但没人去看成功率,等要恢复时才发现最近一个星期全在报错。

5. 恢复是备份的最终目的

5.1 恢复前必须想清楚的三件事

备份做得再好,恢复时手忙脚乱等于白做。我在每次恢复演练前,会确认三件事。

第一,恢复目标是什么。是恢复到“整库某一时刻”的完整状态,还是只需要某个表?pg_rman 是物理备份工具,它的恢复粒度是整个数据库集群,不是单表。如果你只需要单表,用 pg_rman 恢复整个实例再抽表,效率极低,应该优先考虑逻辑备份手段。

第二,恢复目标时间点。你要明确恢复点是在某个时间之前,还是到某一个精确的 LSN。pg_rman 支持通过--recovery-target-time、--recovery-target-xid指定恢复目标,也支持恢复到最新可用状态。一般来说,误删数据的场景恢复到一个明确的时间点,故障切换场景则恢复到最后一致状态。

第三,恢复后的实例用途。是顶替原生产实例对外提供服务,还是在临时环境分析数据?如果是顶替生产,恢复完成后需要重新配置网络、同步账号权限;如果只是临时分析,恢复路径和空间规划可以宽松一些。

5.2 整库恢复实操流程

假设现在要恢复到备份链路中最新的可用状态,操作步骤如下。

首先校验备份完整性,防止恢复一半发现备份文件损坏:

pg_rman validate -B /pg_rman_data

validate会校验备份目录中所有文件的校验和。如果输出中显示OK且没有 ERROR,才进入下一步。这一步会读取所有备份文件,大库会比较耗时,但它能避免在恢复过程中才暴露损坏问题,值得等待。

然后执行恢复:

pg_rman restore -B /pg_rman_data

这里有个非常关键的细节:restore不是直接往已有的数据目录里覆盖文件,它会要求你指定一个目标数据目录。默认情况下,pg_rman 会尝试恢复到由PGDATA环境变量指定的目录。但更稳妥的做法是显式指定:

pg_rman restore -B /pg_rman_data --destination /var/lib/pgsql/15/data_restore

恢复期间 pg_rman 会把备份中所有数据文件、WAL 归档文件拷贝过去,同时自动为你生成recovery.signal文件(PostgreSQL 12 及以上版本)和必要的恢复参数。这意味着恢复过程本身已经把 WAL 重放需要的信息准备好了。

之后你要做的就是把原数据目录替换掉,或者把data_restore作为新的数据目录启动。我实际生产中会用一套干净的系统做恢复演练,流程大致是:

  1. 停掉原实例:pg_ctl stop -D /var/lib/pgsql/15/data -m fast
  2. 将原数据目录改名留作备份:mv /var/lib/pgsql/15/data /var/lib/pgsql/15/data_bak
  3. 执行pg_rman restore --destination /var/lib/pgsql/15/data
  4. 启动新实例:pg_ctl start -D /var/lib/pgsql/15/data

启动完成后,PostgreSQL 会处于恢复模式,自动应用 recovery.signal 后的 WAL。此时你观察日志,看到recovery complete等字样,说明已经追上归档中的最新 WAL。这个状态下的库还是只读的,如果确认数据没问题,手动执行pg_walreplay(实际是 pg_ctl promote)或配置archive_mode=on并在启动后执行select pg_wal_replay_resume()完成提级。

5.3 PITR 到指定时间的操作细节

PITR(Point-In-Time Recovery)是物理备份中最有价值的能力。假设我们收到“某张表在 14:30 被误删除,需要恢复到 14:29:30 这个时间点”这样的需求,命令如下:

pg_rman restore -B /pg_rman_data \ --recovery-target-time "2024-01-15 14:29:30" \ --recovery-target-inclusive=true

--recovery-target-inclusive这个参数要解释一下。它决定恢复目标是“包含指定时间点之前最后一次提交的事务”还是“精确停在该时间点”,默认是 true。大多数误删场景,你希望包含目标时间之前所有已提交的事务,所以保持 true 即可。个别极端情况,比如要排除目标时间那一个事务,可以设成 false。

执行 restore 之后,pg_rman 会自动在目标数据目录生成recovery.signal和postgresql.auto.conf中的恢复目标参数。启动 PostgreSQL 后,它会从归档 WAL 回放,到达恢复目标后自动停止,日志中会出现类似下面的信息:

LOG: recovery stopping before commit of transaction 123456 LOG: recovery has finished

PITR 有一个必须牢记的坑:时间点要准确,但精确到秒只是一个参考。WAL 中记录的是事务提交时的 LSN,时间字段是事务提交时刻的服务端时间。如果你的应用服务器和数据库服务器时间不一致,或者目标时间恰好落在某个长事务中间,恢复到的时间点可能会偏差。要精确定位到某个具体事务,用--recovery-target-xid更可靠,这个值可以从故障会话的日志或者日志分析工具中获取。

6. 踩坑经验与调优记录

6.1 高频错误与排查速查

我把这一两年实际使用中遇到的、以及给其他同事排查过的高频问题整理成一张表格,基本上覆盖了日常 90% 的报错场景。

现象 / 报错根本原因解决办法
ERROR: archive_command failurearchive_command 执行失败,归档没起来用 postgres 用户手动执行 archive_command 的命令,检查权限、目录、路径
ERROR: backup is not taken之前没有成功备份,直接做增量先做一次 full 备份
ERROR: invalid data directorypg_rman 版本和 PostgreSQL 版本不匹配用匹配版本的 PG_CONFIG 重新编译
ERROR: permission denied备份目录或归档目录不属于 postgres 用户chown 修复属主;确认 BACKUP_PATH 可写
备份状态一直是 RUNNING备份过程异常中断,残留状态检查日志,删除对应 backup 目录并重新备份
validate时出现backup file not found备份文件缺失或目录不完整检查 BACKUP_PATH 下文件是否被外部清理过;必要时重建备份
恢复后实例反复重启失败recovery.signal丢失或损坏重新执行 restore,并在启动前确认恢复目标参数存在

我再单独说一个非常容易忽视的问题:pg_rman 在执行备份时会连接 PostgreSQL,如果数据库所在主机发生过 IPv6/IPv4 双栈切换,或者 pg_hba.conf 里local认证方式变了,备份就会失败。报错往往是FATAL: password authentication failed。这时你排查的不是备份工具,而是 PostgreSQL 的连接认证配置。这类病状最容易迷惑人。

6.2 备份策略调优心得

版本更新到 1.3.x 以后,pg_rman 对增量备份的实现做了很多优化,但在实际调优中我发现几个可以显著改善体验的参数组合。

第一,压缩级别别拉太高。我建议COMPRESSION=1但COMPRESS_LEVEL=5左右就是甜点区。压缩率提升不再明显,反而消耗大量 CPU。全量备份时间本来就很长,再用高压缩级别会让备份窗口拉长一倍。如果你对磁盘空间不是极端敏感,甚至可以直接设COMPRESSION=0,靠文件系统层压缩或者干脆不压。我的经验是:压缩级别 6 已经能把 dump 文件减小约 60%,再高只增加 2% 的空间收益,CPU 却多烧一倍。

第二,SMOOTH_CHECKPOINT对业务的影响要实测。如果你在备份过程中观察到业务出现明显的 IO 抖动,把SMOOTH_CHECKPOINT=ON打开,让检查点分散执行。但注意这会延长备份时间,尤其是大库,可能从 30 分钟变成 1 小时。这需要你在备份窗口和业务影响之间做权衡。

第三,定时任务频率不要盲目照抄网上方案。有些人推荐“每 5 分钟一个增量”,但如果你的库写入量很小,增量备份的块变化极少,备份本身反而成为开销。我建议按 RPO 需求反推:比如业务允许丢失 10 分钟数据,那增量频率 10 分钟或 15 分钟就足够;不必追求更细。WAL 归档本身就是持续的,恢复时最多会追到归档中的最新 WAL,所以增量备份频率其实只是影响恢复时回放的数据量,不是决定 RPO 的唯一因素。真正决定 RPO 的是 WAL 归档的及时性。

第四,恢复演练必须定期做,不能只在文档里写“我们有备份”。我至少每月做一次从备份到启动实例的完整恢复演练,每次恢复都用一个全新的实例目录,避免旧文件导致假象成功。演练过程中记录下实际耗时,这个时间是你将来评估 RTO 的唯一可靠依据。有一次我在演练时发现恢复速度比预期慢好几倍,排查后发现是备份时压缩率高,恢复时解压成了瓶颈。从此我把恢复时间纳入了备份策略的调优指标。

最后分享一个小习惯:备份目录里我会单独建一个文件,记录每周恢复演练的结论,包括恢复耗时、WAL 回放是否顺利、是否有异常报错。别小看这份记录,真正的故障来临时,你需要的不是临场回忆命令,而是上一次成功恢复的路径和参数。

pg_rman 不是万能的,但在我维护过的 PostgreSQL 环境里,它是把“备份恢复”这件事变得可靠又省心的工具。把基础配置做扎实,把自动清理策略配好,把恢复流程演练熟,剩下的大多数夜晚都可以安心睡觉。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进