异地备份策略
本地备份能应对磁盘损坏、误删除、实例崩溃,但无法应对机房级故障:火灾、水淹、电力长时间中断、机房网络被切断、以及勒索软件加密了整片存储。这些场景的共同点是,本地那份备份和数据一起消失。异地备份的作用是把恢复能力从“同一个故障域”里搬出来。
PostgreSQL 自身并不提供“异地”这个概念——它只负责产生基础备份和 WAL 归档,至于这些产物落到哪里,由你的 archive_command、pg_basebackup 的输出目标和外围的传输机制决定。换句话说,异地备份是一半数据库工程、一半基础设施工程:数据库侧保证产物可恢复、可校验,基础设施侧保证产物可靠的到达另一个故障域并且不被篡改。
这一节讨论如何设计这样一套策略。重点不在于某个命令怎么写,而在于几个决策点上怎么选:备份要不要加密、用什么介质、多久同步一次、怎么验证、恢复时怎么把数据弄回来。
前置知识
建议先阅读:灾难恢复流程
基础概念
什么是“异地”
“异地”的关键不是地理距离,而是故障域。判断标准是:当主站点发生灾难时,备份所在的位置是否依然可用。据此可以分为三层:
| 层级 | 位置 | 能抵御什么 | 典型代价 |
|---|---|---|---|
| 同机 | 另一块磁盘 / 另一台服务器 | 磁盘损坏、实例误操作 | 低 |
| 同城 | 同城另一机房,通常几公里到几十公里 | 机房级故障、局部电力/网络中断 | 中 |
| 异地 | 几百公里以上或跨区域 | 城市级灾害、区域性网络中断 | 高(带宽/延迟/成本) |
同城和异地是互补关系而不是替代关系:同城副本用于快速恢复(延迟低、回传快),异地副本用于兜底极端场景。只做同城,遇到城市级灾害会一起损失;只做异地,日常恢复时数据回传会很慢。
异地备份与 3-2-1 原则
3-2-1 原则是备份业界最通行的经验法则:
- 3 份数据副本(1 份生产 + 2 份备份)。
- 2 种不同介质或存储技术(例如本地磁盘 + 对象存储)。
- 1 份位于异地。
把这个原则落到 PostgreSQL 上,一个典型组合是:
| 副本 | 位置 | 形式 | 用途 |
|---|---|---|---|
| 第 1 份 | 生产库 | 在线数据目录 | 正常服务 |
| 第 2 份 | 本地备份存储 | pg_basebackup 产物 + WAL 归档 | 日常快速恢复 |
| 第 3 份 | 异地 | 同步过去的备份产物 + WAL 归档 | 机房/城市级灾难兜底 |
3-2-1 的精神不是死守数字,而是:不要让所有副本共享同一个失效原因。同一块存储上的三份备份,等价于一份。
异地备份搬的是什么
需要搬到异地的,不只是“某个时间点的基础备份文件”,而是一条完整的恢复链:
一份基础备份(base backup)
+ 该基础备份之后、直到灾难发生前的所有 WAL 归档
+ 恢复所需的配置信息(时间线、恢复目标、归档路径约定)
+ 校验清单(backup_manifest)与校验工具所需的上下文只要链条中缺一环(比如某个 WAL 段没同步过去),恢复就会在中间停住。因此异地备份的完整性不是“每个文件都传完了”,而是链条首尾闭合。
三种常见的异地落地方式
按数据如何到达异地,可以分成三类:
- 网络同步:把归档命令或备份脚本的输出直接写到异地的共享存储、对象存储、或远端主机。优点是可以做到近实时;缺点是依赖网络,且网络故障时本地归档会积压。
- 物理搬运:把备份介质(磁带、硬盘)定期运到异地。优点是带宽零成本、天然离线(防勒索);缺点是周期长,RPO 通常以天计。
- 云跨区域复制:备份先写到本地/就近对象存储,再由存储服务自身的跨区域复制能力同步到远端区域。优点是把同步可靠性交给存储服务;缺点是成本较高,且要多信任一层云厂商的配置。
现实中往往是组合使用:WAL 走网络同步(数据量小、要求 RPO 低),基础备份走物理搬运或云复制(数据量大、可以容忍延迟)。
传输方式与恢复方式必须是成对的
设计异地策略时最常见的失误,是只考虑“怎么送出去”,不考虑“怎么拿回来”。两者必须成对设计:
- 如果 WAL 通过
rsync推到远端主机,恢复时就要能从远端主机rsync拉回。 - 如果备份进了对象存储的冷归档层,恢复前要预留解冻时间,无法“立刻下载”。
- 如果备份在离线磁带上,需要先确认磁带库/驱动器可用,并有人能执行上带操作。
恢复路径必须在平时验证过,否则灾难当天才发现“备份在,但取不出来”。
异地备份的加密要求
异地备份离开了自己的物理边界,加密从“可选”变成“默认应该做”:
| 环节 | 保护什么 | 关注点 |
|---|---|---|
| 传输中 | 防窃听 | 使用加密通道(TLS、SSH、加密的对象存储 API) |
| 静态时 | 防介质丢失/被拖走 | 备份文件本身或所在存储加密 |
| 密钥 | 决定能否解密恢复 | 密钥不能和备份放在同一个地方 |
密钥管理是加密备份的致命点:如果密钥和备份一起被勒索软件加密,加密的备份就变成了不可恢复的砖头。密钥应当单独保存,并且恢复流程中要有“如何取得密钥”的明确步骤。
不可变备份与防篡改
勒索软件的典型行为是先找到并加密备份,再加密生产库。因此现代备份体系引入了“不可变”概念:
- WORM(Write Once Read Many):写入后不可修改,只能等保留期到期。
- 对象锁 / Object Lock:云对象存储提供的保留策略,可设定在若干天内禁止删除或覆盖。
- 离线/气隙(air-gap):备份介质与网络物理隔离,例如定期离线的磁盘或磁带。
这些机制的共同目的是:让备份在保留期内无法被篡改或删除。它的代价是灵活性下降——误传错了文件也删不掉,只能等保留期结束。
异地备份的成本构成
异地备份的成本不只是存储费,主要包含:
| 成本项 | 说明 | 常见误区 |
|---|---|---|
| 存储容量 | 按保留份数与保留期累积 | 只算一份,忽略 WAL 归档长期累积 |
| 传输/出网 | 上传流量,以及恢复时的下载流量 | 忽略“恢复时下回”的出网费用 |
| 请求次数 | 对象存储按 API 调用计费 | 高频小文件同步导致请求费超预期 |
| 运维人力 | 同步脚本维护、演练 | 完全不计 |
| 恢复耗时 | 慢速链路回传可能要数天 | 只关心能不能恢复,不关心多久 |
其中最容易低估的是恢复时的下载时间与费用。备份上传时可以慢慢传,恢复时却是争分夺秒。
异地备份的 RPO 由什么决定
异地副本的数据新鲜度(RPO)取决于同步机制:
| 同步方式 | 典型 RPO | 说明 |
|---|---|---|
| WAL 归档实时推送 | 秒级到分钟级 | 受 archive_timeout 与网络影响 |
| WAL 流式接收(pg_receivewal) | 秒级 | 持续连接,断链时需要重连 |
| 定期同步基础备份 | 小时级到天级 | 只有基础备份,无连续 WAL |
| 定期物理搬运 | 天级到周级 | 受搬运周期限制 |
注意:只用基础备份同步到异地,RPO 等于同步周期,因为两次同步之间的数据完全不在异地。要降低 RPO,就必须把 WAL 也同步过去。
异地备份与备份验证的关系
送到异地的文件,如果没有验证过“能不能恢复”,那只是“一堆看起来像备份的文件”。异地备份的验证至少包含两层:
- 文件级验证:用
pg_verifybackup校验清单与校验和,确认文件在传输中没有损坏。 - 恢复级验证:真正用异地副本恢复一次,确认恢复链路完整可用。
第 1 层可以在每次同步后自动做;第 2 层只能定期做,但必须做。详见恢复演练。
语法与用法
把 WAL 归档直接推到异地
如果异地位于可挂载的共享存储或通过 SSH 可达,最常见的方式是在 postgresql.conf 中让归档命令直接写到目标:
# postgresql.conf
archive_mode = on
archive_command = 'test ! -f /mnt/offsite/wal/%f && cp %p /mnt/offsite/wal/%f'其中 %p 是被归档 WAL 文件相对于数据目录的路径,%f 是文件名。test ! -f 的用法在官方文档中作为示例出现,作用是避免重复归档时覆盖既有文件。
如果目标是另一台主机,可以用 rsync 或 scp:
# 通过 rsync 推到远端主机(需提前配置免密 SSH)
archive_command = 'rsync -a --ignore-existing %p walstore@offsite-host:/srv/pg_wal/%f'用 pg_receivewal 流式接收 WAL
pg_receivewal 是一个客户端工具,主动连接实例并把 WAL 流式写入本地目录。把它运行在异地主机上,就实现了 WAL 的持续异地同步:
pg_receivewal -h primary-host -p 5432 -U replicator \
-D /srv/offsite_wal --synchronous常用选项:
| 选项 | 含义 |
|---|---|
-D, --directory | WAL 写入的目标目录 |
--synchronous | 每次写入后立即刷盘,降低丢失风险 |
-S, --slot | 使用的复制槽名称 |
-n, --no-loop | 出错后不循环重试,直接退出 |
-E, --endpos | 接收到指定 LSN 后停止 |
--create-slot | 启动前创建所需复制槽 |
--drop-slot | 退出时删除复制槽 |
-P, --progress | 显示进度 |
-v, --verbose | 输出详细信息 |
-Z, --compress | 压缩输出(具体压缩算法支持情况取决于版本) |
--synchronous 的代价是吞吐下降,但保证了已接收的 WAL 已经落盘。对异地备份而言,这个取舍通常是值得的。
把基础备份写到异地
pg_basebackup 的输出目录可以是任意路径,包括挂载到异地的存储或通过网络文件系统写入的位置:
pg_basebackup -h primary-host -p 5432 -U replicator \
-D /srv/offsite_backup/base_20260920 \
-F plain -X stream -c fast -P如果不想挂载,可以先在本地生成再同步:
# 本地生成
pg_basebackup -h 127.0.0.1 -D /var/lib/pgsql/backup/base_20260920 -F plain -X stream -c fast
# 同步到异地
rsync -a --delete /var/lib/pgsql/backup/base_20260920/ \
offsite-host:/srv/offsite_backup/base_20260920/从对象存储上传基础备份
用对象存储时,通常先用命令行工具本地生成,再上传:
# 生成本地基础备份(tar 格式便于打包上传)
pg_basebackup -h 127.0.0.1 -D /var/lib/pgsql/backup/base_20260920 -F tar -X stream -c fast
# 上传到对象存储
aws s3 cp /var/lib/pgsql/backup/base_20260920/ \
s3://pg-offsite-backup/base/20260920/ --recursive上传完成后应当校验远端对象数量与本地文件数量一致,避免“传了一半”。
校验异地副本
对异地的 plain 格式基础备份,可以用 pg_verifybackup 校验:
pg_verifybackup /srv/offsite_backup/base_20260920如果只想快速检查文件列表而不解析 WAL:
pg_verifybackup -n /srv/offsite_backup/base_20260920这一层校验能发现传输过程中的文件缺失与校验和错误,是异地同步后的必要步骤。
计算需要同步的带宽
同步窗口内的带宽需求可以用一个粗略公式估算:
所需带宽 ≈ 备份大小 ÷ 允许同步时间例如一份 200 GB 的基础备份,要求在 4 小时内完成同步:
200 GB ÷ 4 h = 50 GB/h ≈ 114 Mbps(理论下限,实际需留出协议开销与波动余量)实际规划时应至少留出 2~3 倍余量,否则一旦网络波动或备份变大,同步就会拖到下一个周期。
检查异地副本的完整性(以文件数为代理)
在无法执行 pg_verifybackup 的场景下,可以用文件数、总大小作为粗略代理:
# 本地文件数
find /var/lib/pgsql/backup/base_20260920 -type f | wc -l
# 异地文件数
ssh offsite-host 'find /srv/offsite_backup/base_20260920 -type f | wc -l'两者不一致说明同步不完整。这只是粗筛,不能替代 pg_verifybackup。
恢复时把数据取回
异地恢复的第一步是“把数据取回可用位置”。如果异地在对象存储:
aws s3 cp s3://pg-offsite-backup/base/20260920/ /var/lib/pgsql/restore/base_20260920/ --recursive如果异地在远端主机:
rsync -a offsite-host:/srv/offsite_backup/base_20260920/ /var/lib/pgsql/restore/base_20260920/取回后先校验,再启动恢复。不要跳过校验直接启动,一个损坏的备份启动失败会浪费大量时间。
详细说明
为什么只有本地备份等于没有备份
把这一条放在最前面,是因为它最容易被“我们有备份”这句话掩盖。本地备份能抵御的故障类型,和它不能抵御的故障类型,边界其实很清楚:
| 故障类型 | 本地备份能救吗 | 异地备份能救吗 |
|---|---|---|
| 单块磁盘坏 | 能 | 能(但没必要动用) |
| 误删表 / 误更新 | 能 | 能 |
| 实例目录被误删 | 能 | 能 |
| 机房电力故障 | 多数能(恢复后重放) | 能 |
| 机房火灾 / 水淹 | 不能 | 能 |
| 存储阵列整体损坏 | 不能 | 能 |
| 勒索软件加密备份盘 | 不能 | 能(若异地为不可变/离线) |
| 人为恶意删除(有权限) | 不能 | 视隔离程度而定 |
可以看到,本地备份在“故障范围不超过本机”时够用,一旦故障范围与备份范围重叠,就失效了。异地备份买的正是这份“不重叠”。
故障域的划分方式
划分故障域不能只看地图距离,还要看共享的依赖:
- 同一栋楼不同机房:共享消防、供电、网络出口 → 同一故障域。
- 同城不同机房:共享城市电网、城域网 → 大部分情况同一故障域。
- 同一云厂商不同可用区:共享区域级控制面 → 高度相关。
- 同一云厂商不同区域:通常独立 → 可以互为异地。
因此,“异地”的判定要问:这个位置和主站点共享哪些基础设施? 如果答案是“共享关键依赖”,那它就不是真正意义上的异地。
网络同步的失败模式
网络同步看起来优雅,但有几个典型失败模式必须提前设计应对:
- 网络中断导致归档积压:
archive_command失败时 WAL 会留在pg_wal中不删,持续失败会撑爆磁盘,最终导致写满 PANIC。必须对pg_wal空间设置告警。 - 远端存储满:远端写入失败同样会阻塞归档,和网络中断的结果一样。
- 同步脚本卡住而非失败:某些网络挂载在链路异常时会“挂起”,导致归档命令长时间不返回。这类问题不如直接失败好排查。
- 半截文件:网络传输中途断开,远端留下不完整文件。因此传输应当写到临时名再原子重命名,避免被误认为已完成。
应对原则是:归档失败要能快速暴露,且不能无限累积。这需要监控配合,而不是只靠配置。
原子写入与半截文件
归档命令必须保证“远端要么有完整文件,要么什么都没有”。直接在目标位置写入是不安全的:
# 不推荐:直接写目标路径,中断会留下半截文件
archive_command = 'rsync -a %p /mnt/offsite/wal/%f'更稳妥的做法是先写临时文件,成功后重命名:
# 推荐:先写临时文件,再原子重命名
archive_command = 'cp %p /mnt/offsite/wal/%f.tmp && mv /mnt/offsite/wal/%f.tmp /mnt/offsite/wal/%f'注意:归档命令返回非零表示失败,PostgreSQL 会重试;只有返回 0 才认为归档成功。因此重命名的最后一步必须反映真实结果。
传输加密的选择
不同传输方式的加密能力不同:
| 传输方式 | 默认加密 | 建议 |
|---|---|---|
scp / rsync over ssh | 是(SSH) | 直接使用即可 |
| 对象存储 API | 是(HTTPS) | 确认使用 HTTPS 而非 HTTP |
| 网络文件系统(NFS) | 否 | 需要额外加密或网络隔离 |
| 物理搬运磁盘 | 否 | 必须使用整盘加密 |
对于物理搬运,磁盘加密尤其重要:一块装满生产数据的硬盘丢失,等于一次数据泄露事件。
静态加密的选择
备份文件的静态加密有两种层次:
- 存储层加密:由存储设备或对象存储服务提供,对使用者透明。优点是简单,缺点是依赖存储方正确配置,且密钥通常由存储方管理。
- 应用层加密:在写入备份前先加密(例如用
gpg、age、或对象存储的客户端加密)。优点是不依赖存储方,密钥完全自控;缺点是恢复时多一步解密,且密钥丢失即备份报废。
选择取决于合规要求与信任模型。但无论哪种,密钥都不能与备份存放在同一位置,这一点是硬要求。
传输压缩的取舍
备份数据量通常很大,压缩能显著降低带宽与存储成本,但会增加 CPU 开销:
| 方案 | CPU 开销 | 压缩率 | 适用 |
|---|---|---|---|
| 不压缩 | 无 | 1:1 | 内网高带宽、CPU 紧张 |
| 通用压缩(gzip) | 中 | 高 | 平衡选择 |
| 快速压缩(zstd / lz4) | 低 | 中到高 | 高吞吐场景 |
需要权衡的是“压缩耗时”与“传输耗时”哪个更长。如果带宽充足而 CPU 紧张,压缩反而拖慢整体;反之则压缩收益明显。
异地副本的保留策略
异地副本的保留策略通常与本地不同,因为它服务的是极端场景,不需要像本地那样保留大量细粒度恢复点:
| 副本类型 | 本地保留 | 异地保留 | 理由 |
|---|---|---|---|
| 每日基础备份 | 7~14 份 | 7~30 份 | 异地多留几份,防止本地全部损坏 |
| 每周基础备份 | 4~8 份 | 8~26 份 | 覆盖月度回溯 |
| 每月基础备份 | 12 份以上 | 12 份以上(建议不动) | 合规与长期回溯 |
| WAL 归档 | 覆盖最长恢复链 | 覆盖异地最长恢复链 | 必须能重放到某个基础备份之后 |
关键约束:WAL 归档的保留期必须覆盖最老的那份基础备份。否则那份基础备份虽然还在,却因为缺少后续 WAL 而无法恢复到目标时间点。
异地副本的清理与联动
异地 WAL 的清理不能单独做,必须与基础备份的保留策略联动。典型规则是:只有当某个 WAL 段不再被任何保留的基础备份需要时,才能删除。
这可以用 pg_archivecleanup 辅助判断某个基础备份之后的起点:
# 查看某个基础备份对应的起始 WAL 文件名
cat /srv/offsite_backup/base_20260920/backup_label | head -n 5也可以借助 pg_verifybackup 使用的清单中记录的信息来确认恢复链范围,避免手工删错。
异地恢复的两种位置选择
恢复时把数据放到哪里,有两种做法:
- 原地恢复:直接在原主机的数据目录上恢复。速度快,但会覆盖现场,丢失排查线索;且如果原主机硬件有问题,恢复等于白做。
- 异机恢复:在备用主机上恢复,验证通过后再切换流量。保留了原现场,但需要一台可用的备用主机与足够的存储。
灾难场景下通常推荐异机恢复。但前提是平时就准备好备用主机或者有快速获取主机的渠道(例如云上按需开一台)。
恢复时的数据回传瓶颈
异地备份最大的隐性成本在恢复。假设异地有一份 500 GB 的基础备份,回传带宽 100 Mbps:
500 GB ÷ 100 Mbps ≈ 500 × 8 ÷ 100 = 40,000 s ≈ 11.1 小时再叠加 WAL 重放时间,整体 RTO 会明显拉长。因此:
- 恢复目标应尽量选离恢复站点近的副本(先试同城,同城不可用才动异地)。
- 大备份回传应当支持并行 / 断点续传,避免中断后从头再来。
- 应当事先估算回传时长,并写进 RTO 目标,而不是灾难当天才知道要等一天。
异地副本的定期扫描与修复
异步同步难免出现不一致:某个 WAL 段没传成功、某个文件被误删、对象存储的某个 PUT 静默失败。因此需要有周期性的一致性扫描:
# 对比本地与异地的文件清单差异
diff <(cd /var/lib/pgsql/backup/base_20260920 && find . -type f | sort) \
<(ssh offsite-host 'cd /srv/offsite_backup/base_20260920 && find . -type f | sort')发现缺失就补齐,而不是等到恢复时才发现。这个扫描最好自动化并纳入定期任务。
异地备份与复制槽的配合
如果使用 pg_receivewal --slot 接收 WAL,就必须理解复制槽的行为:槽会记录接收端已确认的位置,主库会因此保留对应的 WAL 段。好处是不会因为网络中断而丢失需要的 WAL;风险是接收端长时间断开会导致主库 pg_wal 持续增长。
因此异地接收端要么保证长期在线,要么配置 max_slot_wal_keep_size 给一个上限,让超限的槽失效而不是撑爆主库。这个取舍详见复制槽。
异地备份与时间线
一旦发生过一次基于时间点的恢复,或者做过一次主备切换,时间线(timeline)就会变化。异地副本里可能同时存在多条时间线的 WAL:
- 恢复时必须指定正确的
recovery_target_timeline。 - 归档目录中不同时间线的同名 WAL 文件名可能因为时间线前缀不同而不冲突,但仍需注意目录组织。
- 恢复完成后要明确“当前使用的是哪条时间线”,避免后续接错。
这块在多灾多难的实例上特别容易出错,建议在备份元数据中显式记录时间线。
备份元数据应当记录什么
异地备份如果没有元数据,时间一长就没人知道“这份备份能不能用、恢复到哪”。建议每份备份旁存一份元数据:
备份时间: 2026-09-20 02:00:00+08
来源主机: primary-a
PostgreSQL 版本: 18.x
备份工具: pg_basebackup
备份格式: plain
时间线: 3
起始 WAL: 0000000300000014000000A5
清单文件: backup_manifest(校验和算法 SHA256)
数据目录大小: 187 GB
存放位置: s3://pg-offsite-backup/base/20260920/
校验结果: pg_verifybackup 通过(2026-09-20 05:12)有了这些信息,恢复时就不用靠猜。
异地备份的访问控制
异地备份的访问权限应当比生产库更严格,因为它是“最后一道防线”:
- 上传凭据只允许写入,不允许删除(写入与删除分离)。
- 删除操作应当有额外的审批或延时机制(例如对象存储的保留期)。
- 备份账号不应与生产库账号共用凭据。
- 恢复用的读取权限与上传权限分开管理,减少凭据泄露的影响面。
核心思想是:让负责写入的人无法删除,让负责删除的人无法写入。
供应商锁定与可移植性
如果异地备份完全依赖某个云厂商的专有格式或服务,将来迁移或该服务故障时会很被动。降低风险的做法:
- 尽量使用标准格式(plain 目录、tar、WAL 文件本身就是标准的)。
- 避免在上传前做不可逆的专有转换。
- 保留在通用环境(普通 Linux + PostgreSQL)下恢复的能力。
简单说:备份应当是“能被一台裸机恢复”的,而不只是“能被当前这套云环境恢复”。
异地备份的费用控制
费用控制不是省钱,而是把成本花在最需要的地方:
- 高频变化的 WAL 走网络同步(数据量小);
- 体积巨大的基础备份走低频同步 + 压缩 + 冷存储层;
- 极老的历史备份迁到最廉价的归档层,但要算上解冻时间;
- 定期清理超出保留期且不再被恢复链需要的副本。
要特别注意冷存储的解冻时间:如果一份备份被放进需要数小时解冻的层级,它就不适合作为“当天就要恢复”的副本。
多云与单云的选择
异地也可以跨云,而不只是同一云内的跨区域:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 单云跨区域 | 配置简单、传输快、成本可控 | 受限于单一厂商 |
| 跨云 | 天然规避厂商级故障 | 传输成本高、配置复杂、格式需中性 |
对绝大多数业务,单云跨区域已经能覆盖“机房/城市级故障”;跨云主要应对“厂商级灾难”,属于更高等级的要求。是否值得,取决于业务对厂商故障的容忍度。
异地备份与合规
某些行业规范对备份有硬性要求,常见的有:
- 备份必须存放在与生产不同的地理位置;
- 备份必须加密;
- 备份保留期至少 N 年;
- 需要定期恢复演练记录;
- 需要可审计的访问日志。
这些要求应当在设计阶段就纳入,而不是事后补。审计日志尤其容易遗漏——很多团队能证明“备份成功”,却无法证明“谁在什么时候读过备份”。
异地备份的“最后一份”思路
设计时可以问一个极端问题:如果本地机房里的一切都没了,我手里还剩什么? 答案就是异地备份策略的核心。
如果答案是“什么都没有”,那当前只有本地备份;如果答案是“一份上次同步的基础备份但没有后续 WAL”,那 RPO 等于同步周期;如果答案是“基础备份 + 直到灾难前的 WAL”,那才算一条完整链。
把这个问题的答案写清楚,比任何指标表格都更能说明现状。
实战示例
示例一:把 WAL 归档直接写入异地挂载点
# postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /mnt/offsite/wal/%f && cp %p /mnt/offsite/wal/%f'test ! -f 保证目标不存在时才复制;若已存在则返回 0(视为成功),避免重复归档报错。
示例二:通过 SSH 把 WAL 推到远端主机
# 需要 postgres 用户对远端主机免密登录
archive_command = 'rsync -a --ignore-existing %p walstore@offsite-host:/srv/pg_wal/%f'--ignore-existing 的作用与 test ! -f 类似:已存在就不覆盖。
示例三:用 pg_receivewal 在异地持续接收 WAL
# 在异地主机上运行,配合复制槽使用
pg_receivewal -h primary-host -p 5432 -U replicator \
-D /srv/offsite_wal --synchronous -S offsite_wal -PWaiting for the WAL files to be available on the server...-S offsite_wal 让接收端使用名为 offsite_wal 的复制槽,从而让主库保留尚未接收的 WAL。
示例四:先在本地生成基础备份再同步
# 本地生成
pg_basebackup -h 127.0.0.1 -U replicator \
-D /var/lib/pgsql/backup/base_20260920 \
-F plain -X stream -c fast -P
# 同步到异地
rsync -a /var/lib/pgsql/backup/base_20260920/ \
offsite-host:/srv/offsite_backup/base_20260920/示例五:打包上传到对象存储
# tar 格式便于整体上传
pg_basebackup -h 127.0.0.1 -U replicator \
-D /var/lib/pgsql/backup/base_20260920_tar -F tar -X stream -c fast
aws s3 cp /var/lib/pgsql/backup/base_20260920_tar/ \
s3://pg-offsite-backup/base/20260920/ --recursive示例六:校验异地副本
# 对异地的 plain 备份做完整校验(含 WAL 可解析性)
pg_verifybackup /srv/offsite_backup/base_20260920backup successfully verified看到这一行,才说明这份异地副本在文件层面是可用的。
示例七:估算回传时间
备份大小: 500 GB
回传带宽: 100 Mbps
回传时间 ≈ 500 × 8 ÷ 100 = 40,000 秒 ≈ 11.1 小时把这个数字写进恢复手册,让决策者知道“切换到异地副本意味着多久不能写”。
示例八:对比本地与异地的文件清单
diff <(cd /var/lib/pgsql/backup/base_20260920 && find . -type f | sort) \
<(ssh offsite-host 'cd /srv/offsite_backup/base_20260920 && find . -type f | sort' | sed 's#^\./#./#')输出为空说明文件列表一致;出现 < 行表示本地有而异地没有,需要补齐。
示例九:从对象存储取回并恢复
# 取回
aws s3 cp s3://pg-offsite-backup/base/20260920/ \
/var/lib/pgsql/restore/base_20260920/ --recursive
# 校验
pg_verifybackup -n /var/lib/pgsql/restore/base_20260920
# 配置恢复并启动
echo "restore_command = 'aws s3 cp s3://pg-offsite-backup/wal/%f /var/lib/pgsql/restore/wal/%f'" \
>> /var/lib/pgsql/restore/base_20260920/postgresql.conf
touch /var/lib/pgsql/restore/base_20260920/recovery.signal示例十:用复制槽保证 WAL 不被提前回收
-- 在主库上创建一个物理复制槽供异地接收端使用
SELECT * FROM pg_create_physical_replication_slot('offsite_wal');
-- 查看槽的当前状态
SELECT slot_name, slot_type, active, restart_lsn, wal_status
FROM pg_replication_slots
WHERE slot_name = 'offsite_wal'; slot_name | slot_type | active | restart_lsn | wal_status
-------------+-----------+--------+-------------+------------
offsite_wal | physical | t | 0/1A000000 | reservedwal_status 为 reserved 表示该槽所需的 WAL 仍然保留着;如果变成 lost,说明主库已经无法再为该槽保留数据,异地副本的恢复链出现了断点。
示例十一:压缩后上传以节省带宽
# 本地生成 tar 备份后压缩再上传
pg_basebackup -h 127.0.0.1 -D /var/lib/pgsql/backup/base_20260920_tar -F tar -X stream -c fast
tar -C /var/lib/pgsql/backup/base_20260920_tar -czf \
/var/lib/pgsql/backup/base_20260920.tar.gz .
aws s3 cp /var/lib/pgsql/backup/base_20260920.tar.gz \
s3://pg-offsite-backup/base/20260920.tar.gz压缩比通常在 2~4 倍之间(取决于数据本身的重复度与是否已压缩),能显著降低存储与传输成本。
示例十二:记录异地备份的元数据
cat > /srv/offsite_backup/base_20260920/META.txt <<'EOF'
备份时间: 2026-09-20 02:00:00+08
来源主机: primary-a
PostgreSQL 版本: 18
备份工具: pg_basebackup
备份格式: plain
时间线: 3
数据目录大小: 187 GB
存放位置: s3://pg-offsite-backup/base/20260920/
EOF同一份元数据也应当同步到异地,否则灾难后拿不到来源信息。
注意事项
- 异地备份必须真的在另一个故障域,地理位置不同但共享供电/网络的不算。
- 只有基础备份没有 WAL 时,RPO 等于同步周期,不要误以为“有异地备份就等于低 RPO”。
- 归档失败会导致
pg_wal持续增长,必须监控空间,防止写满 PANIC。 - 写入应当先写临时文件再原子重命名,避免网络中断留下半截文件被误认为完成。
- 加密备份的密钥不能与备份同放一处,否则勒索软件会同时拿到两者。
- 不可变/对象锁的保留期到期前无法删除,误传的文件同样删不掉,要接受这个约束。
- 冷存储层有解冻延迟,不适合作为“当天必须恢复”的副本。
- 恢复路径要与传输路径成对设计并验证,能上传不等于能取回。
pg_receivewal --slot会拽住主库 WAL,接收端长期断开需要max_slot_wal_keep_size兜底。- 发生过恢复或切换后时间线会变化,恢复时要确认
recovery_target_timeline。 - 文件级校验不等于恢复级验证,
pg_verifybackup通过只能证明文件没坏。 - 成本要算上恢复时的下载流量与时间,只算上传会低估整体投入。
最佳实践
- 明确划分故障域,确保异地副本与生产不共享关键基础设施依赖。
- 按 3-2-1 原则组织副本:至少 3 份数据、2 种介质、1 份异地。
- WAL 走网络近实时同步,基础备份走低频同步或物理搬运,两者职责分离。
- 每份异地备份随附元数据文件,记录时间、来源、版本、时间线、起始 WAL、校验结果。
- 每次同步后自动执行
pg_verifybackup,把“传输成功”升级为“文件可用”。 - 归档命令采用临时文件 + 原子重命名的写法,杜绝半截文件。
- 对
pg_wal空间、异地归档目录空间、复制槽wal_status分别设置告警。 - 传输使用加密通道,静默存储使用静态加密,密钥单独保管。
- 生产数据采用不可变(WORM / 对象锁)或离线介质保存,防勒索优先于灵活性。
- 异地保留策略与本地联动,WAL 保留期必须覆盖最老的那份基础备份。
- 事先估算并文档化回传时长与费用,将其纳入 RTO 目标。
- 定期做一致性扫描,发现缺失文件主动补齐,而不是等恢复时才发现。
- 保持备份格式标准可移植,避免被单一云厂商的专有格式锁定。
- 将异地备份的恢复路径纳入恢复演练,按真实链路验证一次。
下一节
继续学习:恢复演练
参考链接
- PostgreSQL 官方文档:Backup and Restore
- PostgreSQL 官方文档:Continuous Archiving and Point-in-Time Recovery (PITR)
- PostgreSQL 官方文档:pg_basebackup
- PostgreSQL 官方文档:pg_receivewal
- PostgreSQL 官方文档:pg_verifybackup
- PostgreSQL 官方文档:Backup Manifest Format
- PostgreSQL 官方文档:Write Ahead Log (WAL) Configuration
- PostgreSQL 官方文档:Replication Configuration
- PostgreSQL 官方文档:pg_archivecleanup