这是本节的多页打印视图。
点击此处打印.
返回本页常规视图.
时间点恢复 —— 数据库的时间机器(PITR)
高可用解决"机器坏了",时间点恢复解决"数据错了"。Pigsty 基于 pgBackRest 提供开箱即用的 PITR 能力,让您可以将集群回滚至恢复窗口内的任意时刻,为人为失误与软件缺陷兜底。
当您不小心删除了数据、表、甚至整个数据库时,时间点恢复(Point-in-Time Recovery,PITR)让您可以回到过去。
—— 这个曾经只有资深 DBA 才能施展的『魔法』,在 Pigsty 的标准配置中零配置开箱即用。
复制不是备份
高可用 可以在硬件故障时自动切换主库,让服务免于中断。但它有一个天然的盲区:复制不是备份。
流复制会以毫秒级的延迟,把主库上发生的一切忠实地同步到所有从库 —— 包括那条忘了加 WHERE 的 DELETE,
和那句敲错了目标库的 DROP TABLE。故障切换应对的是"机器坏了";而当"数据错了"的时候,每一个副本上的数据都错得整整齐齐。
数据库的灾难大体可以分为这两类。前者靠冗余解决:多个副本、自动切换,这是高可用的职责范围。
后者的唯一解药是 历史:基础备份与 WAL 归档让数据库回到错误发生之前。这正是时间点恢复所做的事情。
| 威胁 | 高可用 | 延迟集群 | 时间点恢复 |
|---|
| 硬件故障,实例宕机 | ✔ 自动切换 | ✘ | ✔ 但 RTO 较长 |
| 误删数据 / 误删表 / 误删库 | ✘ 错误被复制 | ✔ 延迟窗口内 | ✔ 恢复窗口内任意时刻 |
| 软件缺陷批量污染数据 | ✘ 错误被复制 | ✔ 延迟窗口内 | ✔ 可反复尝试不同时间点 |
| 整个集群 / 机房级灾难 | ✘ | ✘ | ✔ 需使用远程备份仓库 |
三者并非互相替代,而是互相补位:高可用负责秒级止血,延迟集群提供快速反悔窗口,而 PITR 是所有防线失守之后的最终兜底。
时间机器的原理
PITR 并不神秘。数据库本质上是一台状态机:基础备份 是某个时刻状态的完整快照,
WAL(预写式日志)则是此后每一次状态变更的完整历史。拥有一份快照,再加上从快照开始的完整历史,
就可以把数据库重放到这段历史所覆盖的 任意时刻 —— 快照决定了您能回到多早,归档的进度决定了您能回到多近。
基础备份 + WAL 归档 = 时间点恢复
这两样原料的生产在 Pigsty 中都是自动编排的:集群初始化时默认尝试执行首次全量备份,主库持续将 WAL 段文件推送至备份仓库归档;
关于快照、历史、恢复目标与时间线的完整模型,请参阅 工作原理。
开箱即用
在 Pigsty 的标准配置中,PITR 默认启用:每套 PostgreSQL 集群都带有备份仓库、WAL 归档与恢复工具,
由 pgBackRest 驱动。您也可以用几行声明式配置对备份策略进行深度定制:
pg-meta:
hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
vars:
pg_cluster: pg-meta
pgbackrest_method: minio # 备份写入 MinIO 对象存储仓库(默认为 local 本地仓库)
pg_crontab: [ '00 01 * * * /pg/bin/pg-backup full' ] # 每天凌晨一点执行全量备份
默认使用主库本地磁盘作为备份仓库(/pg/backup),保留最近两个全量备份;每日全备时,恢复窗口约为 24~48 小时。
切换到专用 MinIO 集群或 S3 对象存储后,备份获得独立于数据库主机的故障域与 AES-256 加密;
按时间保留十四天并每周全备时,恢复窗口约为 14~21 天。只要存储管够,恢复窗口丰俭由人。
恢复同样是声明式的:指定恢复目标,剧本完成停库、还原、重放与重建高可用,业务数据由人验证。
./pgsql-pitr.yml -l pg-meta -e '{"pg_pitr": { "time": "2026-07-11 10:00:00+08", "action": "promote" }}'
这个设计与 声明式配置 的理念一脉相承:备份策略是集群定义的一部分,
而"回到过去"也不过是一个 参数。
收益与代价
PITR 为数据安全提供的是 完整性 与 可用性 的跃升:
- RPO(最大数据损失):通常降至分钟级,只丢失最后尚未归档的 WAL。
- RTO(恢复耗时):从 ∞(永久丢失)降至几十分钟到几小时,取决于备份大小与磁盘/网络带宽。
| 单实例配置策略 | 事件 | RTO | RPO |
|---|
| 什么也不做 | 主机与本地数据同时丢失 | 永久丢失 | 全部丢失 |
| 基础备份 | 主机与本地数据同时丢失 | 取决于备份大小与带宽(几小时) | 丢失上次备份后的数据(几小时到几天) |
| 基础备份 + WAL 归档 | 主机与本地数据同时丢失 | 取决于备份大小与带宽(几小时) | 丢失最后尚未归档的数据 |
而它的代价,则主要落在另外三处:
- 机密性:备份本身是额外的数据泄露面,需要加密与访问控制的保护(Pigsty 的远程仓库预设启用 AES-256 加密)。
- 资源:备份占用存储空间,归档占用网络带宽 —— 在 zstd 压缩与块级增量备份的加持下,这通常不构成问题。
- 复杂度:备份需要管理、监控,以及最容易被忽略的一环 —— 恢复演练。
也要清醒地认识 PITR 的局限:单靠"单机 + PITR",故障时的 RTO 与 RPO 都显著逊色于高可用集群。
所以在严肃的生产环境中,两者应当组合使用 —— 高可用应对硬件故障,PITR 应对删库跑路。
接下来
- 工作原理:快照与历史、恢复窗口、恢复目标与时间线 —— 建立 PITR 的心智模型
- 实现架构:pgBackRest 引擎、仓库抽象、归档链路,以及"备份跟随主库"的工程细节
- 策略权衡:故障域、空间与窗口、备份频率 —— 如何为您的场景设计备份策略
- 声明式恢复:
pg_pitr 参数、pgsql-pitr.yml 剧本与 pig pitr 命令行工具 - 典型场景:误删数据、发布事故、机房灾难 —— 事故发生时如何决策
具体操作手册请参阅任务层文档 PGSQL 备份恢复。
1 - 时间点恢复的工作原理
快照与历史、恢复窗口、恢复目标与时间线:理解 PITR 的四个核心概念,建立正确的心智模型。
如果把数据库看作一台状态机,那么 WAL(Write-Ahead Log,预写式日志)就是它的完整变更历史 ——
PostgreSQL 的每一次写入,都会先以日志记录的形式落盘,然后才应用到数据文件上。
这个为崩溃恢复而生的机制带来了一个副产品:只要把某个时刻的数据文件快照保存下来,
再持续保留此后产生的 WAL,就可以把数据库 重放 到这段历史所覆盖的任意时间点。
这就是时间点恢复的全部原理。它不是魔法,而是三个朴素概念的组合:快照(基础备份)、历史(WAL 归档)、目标(恢复到哪一刻)。
快照:基础备份
基础备份(Base Backup)是数据库集群在某一时刻的物理快照,它决定了恢复的 起点。
Pigsty 使用 pgBackRest 制作与管理基础备份,支持三种备份类型:
| 类型 | 内容 | 特点 |
|---|
| 全量备份(full) | 复制整个数据库集群 | 独立可用,恢复最快,占用空间最大 |
| 差异备份(diff) | 相对最近一次 全量备份 的变化 | 恢复需要:全量 + 差异 |
| 增量备份(incr) | 相对最近一次 任意备份 的变化 | 空间最省,恢复需要完整备份链 |
备份通过封装脚本 pg-backup [full|diff|incr] 触发,不带参数时默认执行增量备份,
若仓库中尚无全量备份则自动升级为全量。备份任务由 pg_crontab 参数声明,
写入 postgres 用户的 crontab 定时执行。
基础备份的频率决定了恢复的速度:备份越新,恢复时需要重放的 WAL 就越少。
这是 策略权衡 中的关键变量之一。
历史:WAL 归档
快照只能让您回到备份的那一刻,而 WAL 归档 补全了此后的每一步。
Pigsty 默认在集群上开启归档,由 PostgreSQL 在每个 WAL 段文件(16 MB)写满后触发归档命令,交给 pgBackRest 推送至备份仓库:
archive_mode: 'on' # 开启 WAL 归档
archive_command: 'pgbackrest --stanza=pg-meta archive-push %p' # 交由 pgBackRest 推送
archive_timeout: 300 # 低写入时最多五分钟触发切段归档
两个细节值得注意:
archive_timeout: 300 给恢复窗口的右边界上了一道保险:只要期间产生过 WAL,即使段文件迟迟写不满,
五分钟后也会触发切段归档,通常把归档延迟控制在分钟级。- 异步归档(
archive-async=y):pgBackRest 使用本地假脱机目录(/pg/spool)异步批量推送 WAL,
避免归档吞吐成为主库写入的瓶颈。Pigsty 将归档队列上限设为 4 GiB —— 队列指主库上尚未归档的 WAL 积压;
仓库长期不可用导致积压超限时,pgBackRest 会丢弃这些 WAL 以保护主库磁盘,代价是归档断链 ——
需要执行新的全量备份才能重新建立 PITR 能力。
归档的清理是自动的:pgBackRest 在过期备份被清除时,一并清理不再被任何备份需要的 WAL 归档。
恢复窗口
快照与历史合在一起,构成了 恢复窗口(Recovery Window)—— 您能够回到的时间范围:
- 左边界:仓库中最早的那个基础备份的完成时刻 —— 再往前的历史已被保留策略清除。
- 右边界:最新已归档的 WAL 位置 —— 通常距当前时刻不超过几分钟。
恢复窗口是滑动的:新备份不断产生,旧备份按保留策略过期,窗口随时间整体前移。
在 Pigsty 的仓库预设中,本地仓库保留最近两个全量备份(每日全备时窗口约一至两天),
MinIO / S3 仓库按时间至少保留十四天。每周全备时,稳态窗口约为十四至二十一天。
窗口的长短本质上是空间与需求的权衡,详见 策略权衡。
目标:恢复到哪一刻
恢复窗口内的定位方式不止"时间"一种。PostgreSQL 提供了六类恢复目标,Pigsty 通过
pg_pitr 参数统一封装:
| 目标类型 | 说明 | 典型场景 |
|---|
default | 重放全部 WAL,恢复到归档流末尾 | 整库丢失后的灾难恢复 |
time | 恢复到指定时间戳 | 误删数据 —— 最常用 |
xid | 恢复到指定事务 ID | 精确回退某个错误事务 |
lsn | 恢复到指定 WAL 位点 | 按日志位置精确定位 |
name | 恢复到命名还原点 | 事先用 pg_create_restore_point() 打点 |
immediate | 到达一致状态即停止 | 最快可用,验证备份 |
边界语义
恢复目标默认是 包含(inclusive)的:目标点上的那个事务会被保留。
若要停在目标 之前(例如 xid 正是那个误删事务),使用 exclusive: true,
对应 PostgreSQL 的 recovery_target_inclusive = false。
事务是恢复的原子单位:重放停止后,目标点前已提交的事务全部保留,未提交的事务全部回滚 ——
数据库最终呈现的一定是某个一致的瞬间,而不会出现"半个事务"。
时间线
恢复到过去并继续写入,历史就产生了 分叉。PostgreSQL 用 时间线(Timeline)来区分这些平行历史:
每次 PITR 恢复完成并提升后,都会创建一条新时间线,此后产生的 WAL 归属于新时间线,不会覆盖旧历史。
gitGraph
commit id: "全量备份"
commit id: "正常写入"
commit id: "误删数据 ✗"
commit id: "继续写入"
branch Timeline-2
checkout Timeline-2
commit id: "PITR 恢复至误删前"
commit id: "新的写入"时间线的意义在于 可以反悔:旧时间线的 WAL 仍在仓库中,如果发现恢复的时间点选早了,
可以再次恢复到旧时间线上更晚的位置 —— 甚至"回到未来"。除 PITR 外,从库提升(Promote)与故障切换(Failover)同样会产生新时间线。
恢复时可以用 timeline 参数指定目标时间线,Pigsty 默认使用 latest。
想知道这套机制在 Pigsty 中如何落地为具体的组件与配置?请继续阅读 实现架构。
2 - 时间点恢复的实现架构
Pigsty 以 pgBackRest 为引擎实现 PITR:仓库抽象、归档链路、调度机制,以及"备份跟随主库"的工程设计。
原理 一页就能讲完,工程却没有那么简单:
归档不能拖垮主库的写入性能,备份放到对象存储上要加密,主从切换之后备份不能中断,
多套集群共用一个仓库时要相互隔离,海量小文件会拖垮备份吞吐……
Pigsty 选择 pgBackRest 作为备份引擎,并把这些工程问题的答案预置在了出厂配置中。
本文说明这套架构的组成:引擎、仓库、链路、调度,以及一个关键设计 —— 备份跟随主库。
备份引擎:pgBackRest
pgBackRest 是 PostgreSQL 生态中事实上的标准备份工具,Pigsty 用它承担三项职责:
执行基础备份(backup)、接收 WAL 归档(archive-push)、执行恢复(restore / archive-get)。
选择它的理由,恰好对应上面那些工程问题:
- 并行:备份、归档、恢复都支持多进程并行,吞吐可以随核数扩展。
- 增量:支持差异/增量备份与 块级增量(block incremental),只传输文件内部变化的块。
- 压缩与加密:内置 zstd 压缩与 AES-256-CBC 加密,密文落盘,仓库泄露不等于数据泄露。
- 多种仓库:本地磁盘、S3 兼容对象存储(MinIO、云厂商 OSS)、Azure、GCS、SFTP 皆可作为后端。
- 打包:
bundle 特性将海量小文件合并为大对象存储,避免对象存储的小文件惩罚。
在仓库内部,pgBackRest 使用 stanza(节)隔离不同集群的备份。Pigsty 将 stanza 直接映射为集群名
pg_cluster,因此多套集群可以安全地共享同一个备份仓库:
备份仓库
├── backup/
│ ├── pg-meta/ # pg-meta 集群的基础备份
│ └── pg-test/ # pg-test 集群的基础备份
└── archive/
├── pg-meta/ # pg-meta 集群的 WAL 归档
└── pg-test/ # pg-test 集群的 WAL 归档
仓库抽象
备份放在哪里,是备份策略中最重要的决定。Pigsty 把这个决定抽象为两个参数:
pgbackrest_method 选择使用哪个仓库,
pgbackrest_repo 定义所有候选仓库。默认提供两个开箱即用的选项:
pgbackrest_method: local # 使用哪个仓库:local,minio,或自定义仓库名
pgbackrest_repo: # 仓库定义:https://pgbackrest.org/configuration.html#section-repository
local: # 默认仓库:主库本地文件系统
path: /pg/backup # 备份目录,默认挂在主数据盘上
retention_full_type: count # 按份数保留全量备份
retention_full: 2 # 保留最近 2 个全量备份(清理前最多 3 个)
minio: # 可选仓库:MinIO / S3 兼容对象存储
type: s3 # MinIO 走 S3 兼容协议
s3_endpoint: sss.pigsty # MinIO 服务端点(负载均衡域名)
s3_bucket: pgsql # 桶名称
s3_key: pgbackrest # 访问密钥
s3_key_secret: S3User.Backup # MinIO 用户密钥
storage_ca_file: /etc/pki/ca.crt # 使用 Pigsty 自签名 CA 验证 HTTPS
block: y # 启用块级增量备份
bundle: y # 小文件打包存储
cipher_type: aes-256-cbc # 仓库加密:AES-256-CBC
cipher_pass: pgBackRest # 仓库加密密码
retention_full_type: time # 按时间保留全量备份
retention_full: 14 # 按时间保留 14 天
注意两套预置仓库的策略差异:本地仓库 追求简单直接 —— 不加密、不打包、按份数保留;
MinIO 仓库 面向生产 —— 加密、打包、块级增量、按时间保留两周。
这不是随意的默认值,而是对两种使用场景的判断:本地仓库与数据同生共死,重点是快;
远程仓库承担容灾职责,重点是安全与可追溯。
仓库定义到 pgBackRest 配置的转换是机械的:键名中的下划线替换为连字符,加上 repo1- 前缀,
渲染进 /etc/pgbackrest/pgbackrest.conf。所以 pgBackRest 支持的任何仓库选项都可以直接写进
pgbackrest_repo —— 例如添加一个云上 S3 仓库用于异地冷备:
s3: # 自定义仓库 ------> /etc/pgbackrest/pgbackrest.conf
type: s3 # ----> repo1-type=s3
s3_endpoint: oss-cn-beijing-internal.aliyuncs.com
s3_region: oss-cn-beijing # ----> repo1-s3-region=oss-cn-beijing
s3_bucket: <your_bucket> # ----> repo1-s3-bucket=<your_bucket>
s3_key: <your_access_key> # ----> repo1-s3-key=<your_access_key>
s3_key_secret: <your_secret> # ----> repo1-s3-key-secret=<your_secret>
s3_uri_style: host # ----> repo1-s3-uri-style=host
path: /pgbackrest # ----> repo1-path=/pgbackrest
cipher_type: aes-256-cbc # ----> repo1-cipher-type=aes-256-cbc
cipher_pass: <your_password> # ----> repo1-cipher-pass=<your_password>
retention_full_type: time # ----> repo1-retention-full-type=time
retention_full: 90 # ----> repo1-retention-full=90
各类仓库的完整配置方法(MinIO、阿里云 OSS、AWS S3、版本控制与对象锁定)请参阅 备份仓库。
归档与调度
WAL 归档链路在集群初始化时自动接通:只要 pgbackrest_enabled
为真(默认),Patroni 配置模板就会为集群设置 archive_mode: on 与
archive_command: pgbackrest --stanza=<集群名> archive-push %p,WAL 段从此源源不断地流入备份仓库。
基础备份的生产则有两个入口:
- 初始备份:集群初始化完成后,Pigsty 默认在主库上尝试执行一次全量备份(留下
/etc/pgbackrest/initial.done 标记,避免重复)。
可通过 pgbackrest_init_backup 关闭。 - 定时备份:
pg_crontab 参数声明备份计划,写入 postgres 用户的 crontab。
Pigsty 随附的标准集群配置声明每天凌晨一点的全量备份;角色参数本身的默认值为空列表:
pg_crontab: [ '00 01 * * * /pg/bin/pg-backup full' ]
pg-backup 是 pgBackRest 的薄封装:自动解析 stanza,执行 pgbackrest backup,并做一件重要的事 —— 角色检查。
备份跟随主库
pgBackRest 安装在集群的 所有 节点上,但任何时刻只有 当前主库 实际执行备份与归档:
pg-backup 在运行前检查节点角色,从库上直接退出。这个看似简单的设计带来一个重要性质 —— 备份链路与高可用拓扑解耦:
- 所有节点的备份配置完全相同,crontab 也完全相同;
- 故障切换 后,新主库自动接续后续备份与 WAL 归档,无需人工干预;
- 备份仓库只有一份由当前主库写入的权威数据流,不存在双写冲突。
仓库在另一个方向上也参与高可用:当使用远程仓库时,Pigsty 将 pgBackRest 注册为 Patroni 的备用副本创建方式
(create_replica_methods)。默认仍先尝试 basebackup,失败后才使用 pgbackrest --delta restore 从仓库拉取数据;
走到这一后备路径时,造从库的流量压力会从主库转移到备份仓库。
性能取舍
出厂配置中还有几处针对性能的预置判断,体现同一个原则:备份为生产让路,恢复全力以赴。
| 配置 | 默认值 | 考量 |
|---|
| 压缩算法 | zstd | 高压缩比与高吞吐的平衡点,备份体积通常远小于原库 |
| 备份/归档并行度 | 约 1/4 核数(2~4 进程) | 备份不与生产负载争抢 CPU |
| 恢复并行度 | 核数(至多 8 进程) | 恢复时争分夺秒,资源全开 |
| 异步归档 | archive-async=y | 经 /pg/spool 假脱机批量推送,归档不阻塞写入 |
| 归档队列上限 | 4 GiB | 未归档 WAL 积压超限时丢弃归档,保护主库磁盘不被写满 |
| 快速启动 | start-fast=y | 备份开始时立即执行检查点,不等常规检查点周期 |
| 增量恢复 | delta=y | 恢复时复用数据目录中未变化的文件,大幅缩短 RTO |
归档队列上限的具体故障行为见 工作原理。
可观测性
备份不被观测,就等于没有备份。每个 PostgreSQL 节点默认运行 pgbackrest_exporter(端口 9854),
将仓库中的备份状态导出为监控指标:最近一次备份的时刻、类型、大小、持续时间、错误状态 ——
Grafana 监控面板与告警规则开箱即用。此外还有几个便捷入口:
| 入口 | 说明 |
|---|
pb info | pgbackrest info 的别名封装,查看仓库中的备份列表 |
/pg/log/pgbackrest/ | 备份、归档、恢复的详细日志 |
pg-backup | 手动触发备份:full / diff / incr |
关于备份的日常管理命令,请参阅 管理命令;
理解了架构之后,下一个问题是如何为您的场景选择策略 —— 请继续阅读 策略权衡。
3 - 时间点恢复的策略权衡
备份是一份保险:备在哪里决定容灾等级,保留多久决定恢复窗口,多久备一次决定恢复速度 —— 三个问题定义一份备份策略。
备份本质上是一份保险:保费 是存储空间、网络带宽与管理成本,保额 是灾难来临时能挽回多少数据、多快恢复服务。
和所有保险一样,这里没有免费的午餐 —— 更长的恢复窗口意味着更多的空间,更快的恢复意味着更频繁的备份。
设计备份策略,就是回答三个问题:备在哪里?保留多久?多久备一次?
备在哪里:故障域决定容灾等级
备份仓库的位置是第一个、也是最重要的决定,因为它直接划定了备份能扛住哪个级别的灾难。
本地仓库(pgbackrest_method: local)把备份放在主库本地磁盘上。它简单、快速、没有外部依赖,
恢复时走本地 I/O 速度最快 —— 但备份与数据共享同一个故障域:磁盘损毁、主机报废、机器被勒索加密时,
备份大概率与数据一同陪葬。它能对抗的是 逻辑错误(误删、缺陷),而不是 物理灾难。
远程仓库(pgbackrest_method: minio 或云上 S3)把备份放进独立的故障域。
数据库主机全灭,备份依然健在;配合 AES-256 加密与 MinIO 多节点纠删码,
还能对抗仓库侧的磁盘故障,并降低备份介质泄露造成的明文暴露风险。
代价是恢复速度受网络带宽制约,以及多维护一个组件。
| 场景 | 推荐仓库 | 理由 |
|---|
| 开发、测试、演示 | local | 零依赖,坏了重建,无须容灾 |
| 生产环境 | minio(专用集群) | 独立故障域,加密,多节点纠删码 |
| 云上部署 | S3 / OSS 等对象存储 | 免维护,天然异地,成本低廉 |
| 高合规要求 | MinIO 版本控制 + 已配置保留期的对象锁定 | 防篡改、防勒索:锁定版本在保留期内不可被永久删除 |
一个经常被忽略的角度:备份仓库同时是 安全资产。防勒索的关键不是"有备份",
而是"攻击者拿到数据库主机的最高权限后,依然无法销毁备份" ——
这正是对象存储的版本控制与对象锁定(WORM)的价值所在,详见 备份仓库。
保留多久:空间与窗口
恢复窗口的长度由保留策略决定,而保留策略的成本是刚性的:窗口越长,空间越大,没有配置技巧可以绕开。
以一个 100 GB、每日变更 10 GB 的数据库为例(未计压缩):
- 每日全量,保留两份(本地仓库默认思路):约 200 GB 备份 + 两天 WAL 归档 ≈ 2~3 倍 数据库大小,
换来一至两天的恢复窗口。
- 每周全量 + 每日增量,按时间保留十四天(MinIO 仓库默认思路):稳态低点约三份全量 + 十二份增量 +
十四天 WAL,下一次全量前增至十八份增量与二十一天 WAL;恢复窗口约 十四至二十一天。
实际占用通常显著低于这个粗估:zstd 压缩往往能将备份压缩数倍,块级增量(block: y)
使增量备份只存储文件内部真正变化的数据块。但数量级的规律不变 —— 为备份仓库规划 数倍于数据库 的空间是基本前提。
窗口应该多长?一个实用的标尺是:窗口必须覆盖"错误从发生到被发现"的延迟。
误删表通常几分钟内就会被发现,一天的窗口绰绰有余;而缓慢污染数据的软件缺陷、
要到月底对账才暴露的错误,则需要以周计的窗口。“本地一两天、远程至少两周"正是对这两类需求的回应。
多久备一次:频率与恢复速度
恢复耗时(RTO)由两段组成:还原基础备份 的时间加上 重放 WAL 的时间。
备份大小决定前者,备份 频率 决定后者 —— 距离恢复目标最近的那个基础备份越新,需要重放的 WAL 就越少。
WAL 重放是单进程的,而且重放高峰期写入的 WAL 可能比还原备份本身还慢。
对写入繁忙的库,如果恢复目标恰好落在下一次备份之前,“每周全量"可能需要重放接近一周的 WAL —— 这正是增量备份的价值:
以极小的空间代价(块级增量下通常只有全量的百分之几),把"需要重放的历史"每天清零一次。
经验法则:备份窗口允许的前提下,宁可提高备份频率,不要拉长重放距离。
两种预设策略
Pigsty 把上述权衡沉淀为两套开箱即用的预设,多数场景可以直接采用或微调:
标准策略:本地仓库 + 每日全量。配置简单,恢复走本地磁盘速度最快,适合开发测试与容灾要求有限的场景:
pgbackrest_method: local # 备份到主库本地 /pg/backup(默认值,可省略)
pg_crontab: [ '00 01 * * * /pg/bin/pg-backup full' ] # 每日凌晨一点全量备份
# 保留最近两个全量备份,恢复窗口约一至两天
生产策略:MinIO 仓库 + 周全量日增量。独立故障域,AES-256 加密,十四至二十一天窗口,适合严肃生产环境:
pgbackrest_method: minio # 备份到专用 MinIO 集群(或云上 S3)
pg_crontab: # 周一全量,其余每日增量
- '00 01 * * 1 /pg/bin/pg-backup full'
- '00 01 * * 2,3,4,5,6,7 /pg/bin/pg-backup'
# 按时间至少保留 14 天;周全备时恢复窗口约 14~21 天
空间估算与保留策略的可视化推演,请参阅任务层文档 备份策略。
没有演练过的备份,不算备份
最后一个权衡维度不在配置里,而在流程里。备份系统最危险的状态,是"看起来一直在正常运行”:
监控绿灯长明,仓库稳步增长,而没有人知道这些备份 能不能恢复、恢复要多久。
把恢复演练纳入例行运维:定期用 克隆恢复 把备份还原成一套新集群 ——
这既是对备份完整性的端到端验证,也是对 RTO 的实测校准,而且不触碰生产集群;演练目标集群仍会被覆盖。
恢复的具体机制与工具,请继续阅读 声明式恢复。
4 - 声明式恢复
恢复不该是深夜里的十几步手工操作:用 pg_pitr 参数声明想回到的时刻,由 pgsql-pitr.yml 剧本或 pig 命令行工具编排执行。
备份系统的全部价值,都在恢复的那一刻兑现。而恢复几乎总是发生在最糟糕的时刻 ——
生产事故、深夜告警、每一分钟都在损失。传统的 PITR 手工流程在这种时刻是残酷的:
停 HA、停库、写恢复配置、执行还原、盯日志、验证位点、重建元数据、拉起集群……十几个步骤环环相扣,任何一步出错都可能雪上加霜。
Pigsty 的答案与 声明式配置 一脉相承:恢复也是声明式的。
您描述想回到的时刻,编排工具负责停库、还原、重放与重新接管。
声明恢复目标
恢复目标用 pg_pitr 参数描述,交给 pgsql-pitr.yml 剧本执行。
最常用的形式只有一行 —— 把集群恢复到指定时间点:
./pgsql-pitr.yml -l pg-meta -e '{"pg_pitr": { "time": "2026-07-11 10:00:00+08", "action": "promote" }}'
六类恢复目标 与恢复行为的方方面面,都是这个参数的字段:
pg_pitr: # 恢复目标定义(按需声明,字段均可省略)
cluster: pg-meta # 从哪个集群的备份恢复(源 stanza),默认为本集群
type: time # 目标类型:default | time | xid | lsn | name | immediate
time: '2026-07-11 10:00:00+08' # 恢复到的时间点(与 xid / lsn / name 互斥)
exclusive: false # 停在目标之前(排除目标点),默认包含
action: promote # 显式提升;指定目标时未声明 action,实际默认为 pause
timeline: latest # 目标时间线,默认 latest
set: latest # 从哪个备份集开始还原,默认自动选择
repo: { ... } # 临时指定备份仓库(不使用本机配置时)
backup: false # 恢复前是否把原数据目录搬到 /pg/data-backup 留作后悔药
archive: true # 保留原有归档配置;探索性恢复可设为 false
db_include: [ ... ] # 只恢复指定数据库(选择性恢复)
data: /pg/data # 恢复到哪个数据目录
完整的字段说明与用法示例请参阅 恢复操作。
剧本如何执行
pgsql-pitr.yml 把手工恢复的十几个步骤编排为六个阶段,并支持用 tags 分段执行:
| 阶段 | 动作 |
|---|
| print | 汇总恢复计划:源集群、目标类型、还原命令;只打印,不会暂停等待确认 |
| pause | patronictl pause:让 Patroni 进入维护模式,暂停高可用自动干预 |
| stop | 依次停止从库与主库的 Patroni 及 PostgreSQL 进程 |
| pitr | 渲染恢复配置,执行 pgbackrest restore(增量还原),启动进程重放 WAL,等待进入一致状态并打印控制信息 |
| etcd | 清除 etcd 中的旧集群元数据,避免新旧时间线的状态混淆 |
| start | 重新拉起 Patroni,恢复高可用自动驾驶,从库重新克隆 |
几处设计值得注意:
- 增量还原:还原使用 pgBackRest 的
delta 模式,只重写数据目录中与备份不一致的文件。
对大库而言,这往往把"还原全库"缩短为"还原变化的部分",显著压缩 RTO。 - 验证而非假设:剧本用
pg_controldata 打印检查点 LSN、时间线与 NextXID;最终仍由人检查业务数据是否正确。 - 后悔药:声明
backup: true 时,恢复前会把原数据目录完整搬到 /pg/data-backup——
如果恢复目标选错了,原现场还在。再次以 backup: true 运行会先删除已有的 /pg/data-backup,不要把它当成可反复覆盖的快照。 - 分阶段执行:谨慎起见,可以用 tags 把恢复拆为三步走:
-t down(停集群)、-t pitr(执行还原)、-t up(拉起集群),
每步之间人工检查。pitr 阶段返回只表示数据库已进入一致恢复状态;指定了时间、XID、LSN 或恢复点时,还要确认 WAL 已重放到目标。
恢复到达目标后的行为由 action 决定:promote(提升并开启新时间线)、
pause(暂停在目标点,可检查数据后再决定;指定目标时的实际默认值)、shutdown(停机待命)。
若要保留 pause / shutdown 的人工门,应分阶段执行并在确认后再运行 up;一步式执行应显式选择 promote。
剧本不会替您做"数据对不对"的判断 —— 这是工程师保留的最终决定权。
命令行工具:pig
除了 Ansible 剧本,pig 命令行工具提供了单实例粒度的 PITR 编排 ——
适合在数据库节点上直接操作,无需管理节点与剧本环境:
pig pitr -t "2026-07-11 10:00:00+08" # 恢复到指定时间点
pig pitr --xid 250000 -X # 恢复到事务 250000 之前(排除该事务)
pig pitr -d # 重放到 WAL 归档末尾(灾难恢复)
pig pitr -I --no-restart # 只还原并准备 immediate 恢复,PostgreSQL 保持停止
pig pitr 执行单节点恢复编排:预检(校验目标、stanza、备份存在性)、
停止 Patroni 与 PostgreSQL、执行还原、按参数决定是否启动 PostgreSQL、给出恢复后指引。对于 Patroni 托管的数据目录,
恢复后 Patroni 会保持停止,验证数据后再用 pig pt start 恢复 HA 管理;它不会清理 etcd、重建副本或自动重入集群。
默认拒绝任何破坏性的强制停库动作,除非显式指定 --force-stop。
更底层的 pig pb 系列命令封装了 pgBackRest 本身:pb info 查看备份、
pb backup 触发备份、pb restore 执行裸还原。这里有一道有意设置的硬边界:
当实例仍由 Patroni 托管时,pig pb restore 会直接拒绝执行 ——
因为 Patroni 会立刻把恢复到一半的库重新拉起,酿成事故。托管实例的恢复,请始终使用 pig pitr 或 pgsql-pitr.yml。
原地恢复与克隆恢复
同一套恢复机制,有两种截然不同的用法:
| 维度 | 原地恢复 | 克隆恢复 |
|---|
| 做法 | 把生产集群整体回滚到过去 | 用备份把 另一套集群 恢复到过去 |
| 停机 | 需要(恢复期间服务不可用) | 不需要(生产集群不受影响) |
| 影响 | 目标点之后的 所有 写入都被抹去 | 不影响源集群;目标集群会被覆盖,可反复尝试不同时间点 |
| 适用 | 整库损毁、灾难恢复、可接受回滚 | 误删找回、审计取证、恢复演练 |
克隆恢复的关键是 pg_pitr 的 cluster 字段 —— 它指定 从谁的备份 恢复。
下面的命令把 pg-meta 的历史状态恢复到 pg-test 集群上,生产库全程无感:
./pgsql-pitr.yml -l pg-test -e '{"pg_pitr": { "cluster": "pg-meta", "time": "2026-07-11 10:00:00+08", "archive": false, "action": "promote" }}'
从克隆集群中把误删的表 pg_dump 出来、导回生产,是处理误删除的标准姿势 ——
全库回滚是最后手段,而不是第一反应。克隆恢复的完整流程与善后事项,请参阅 克隆数据库集群。
恢复之后
恢复完成不等于事情结束。有三件事应当纳入收尾清单:
- 新时间线,新备份:提升后集群运行在新时间线上。尽快执行一次全量备份(
pg-backup full),
让恢复窗口在新时间线上重新建立。 - 归档状态:探索性恢复后,按 恢复后处理 恢复归档。
- 克隆善后:克隆出的新集群与源集群的备份身份(stanza)不一致,需要重建 stanza 后再启用自身的备份,
详见 克隆数据库集群。
工具完成机械步骤,剩下的是判断:恢复到哪一刻、用原地还是克隆、数据对不对。
这些决策的框架,请继续阅读 典型场景。
5 - 时间点恢复的典型场景
误删数据、发布事故、审计取证、机房灾难 —— 事故发生时如何选择恢复目标与恢复方式,以及为什么要把事故排练成例行演练。
事故发生时,最贵的不是恢复本身,而是 决策时间。
恢复的机械步骤已经被 工具编排 好了,真正需要人来回答的只有三个问题:
恢复到哪一刻?原地恢复还是克隆恢复?如何验证数据是对的?
本文为最常见的几类事故给出决策框架 —— 最好在事故发生之前读完它。
判断框架
| 场景 | 典型问题 | 推荐方式 | 恢复目标 |
|---|
| 误删 / 误更新数据(DML) | DELETE / UPDATE 忘加 WHERE | 克隆恢复,导回数据 | time / xid |
| 误删表 / 库 / Schema(DDL) | DROP TABLE / 错误迁移脚本 | 克隆恢复,导回对象 | time / name |
| 发布事故 / 批量污染 | 缺陷代码批量写坏数据 | 克隆恢复,比对后决策 | time / xid |
| 审计 / 取证 / 复盘 | 需要查看历史某刻的数据 | 克隆恢复(只读) | time / lsn |
| 整库损毁 / 机房灾难 | 硬件全灭、勒索加密 | 原地恢复或异地重建 | default / time |
贯穿所有场景的两条原则:
- 先止损,再恢复。第一动作永远是阻止错误继续扩散:暂停问题应用、吊销问题账号的写权限。
恢复窗口在流逝,但慌乱中启动错误的恢复造成的二次伤害更大。
- 克隆恢复是默认选项。它不触碰生产集群、可以反复尝试不同时间点、可以先验证再动手;代价是目标集群会被覆盖。
只有当集群已经整体不可用 —— 也就是"没有什么可失去"的时候,原地恢复才是首选。
flowchart TD
A["发现数据错误"] --> B["止损:暂停错误来源"]
B --> C{"生产集群还能服务吗?"}
C -->|能| D["克隆恢复:另起集群回到错误前<br/>验证后导回数据"]
C -->|不能| E["原地恢复:整体回滚<br/>或在新硬件上异地重建"]
D --> F["善后:重建备份,复盘"]
E --> F
误删数据(DML)
没加 WHERE 的 DELETE、写错条件的 UPDATE、逻辑出错的批处理脚本 —— 这是 PITR 最高频的用武之地。
关键动作是 定位错误时刻:从应用日志、PostgreSQL 日志或监控曲线中找到错误发生的时间
(若启用了 审计日志,定位会更加精确)。
如果能定位到确切的事务号,xid 目标配合 exclusive 可以精确地停在错误事务 之前,一条数据都不多丢:
# 已知误删发生在 10:15 左右:克隆恢复到 10:14
./pgsql-pitr.yml -l pg-test -e '{"pg_pitr": { "cluster": "pg-meta", "time": "2026-07-11 10:14:00+08", "archive": false, "action": "promote" }}'
# 已知误删事务号为 250000:精确停在该事务之前
./pgsql-pitr.yml -l pg-test -e '{"pg_pitr": { "cluster": "pg-meta", "xid": "250000", "exclusive": true, "archive": false, "action": "promote" }}'
数据在克隆集群中验证无误后,用 pg_dump / COPY 把受影响的行导回生产库。
如果集群配置了 延迟集群,且误删仍在延迟窗口之内,
直接从延迟从库读取数据更快 —— 这是 PITR 之外的第二条时间通道。
误删对象(DDL)
DROP TABLE、DROP DATABASE、跑错环境的迁移脚本。与 DML 场景同理,但有一个更强的约束:
DDL 误删几乎不应该原地恢复 —— 为了找回一张表就把整个库回滚到过去,等于把误删之后所有正常业务写入一并抹掉。
标准流程是克隆恢复:另起集群恢复到误删之前,校验对象完整性,pg_dump 导出误删的表 / 库,导回生产。
如果变更前用 pg_create_restore_point() 打过还原点,name 目标可以让"恢复到变更之前"变得毫无歧义 ——
在高危变更前打点,是成本几乎为零的好习惯。
发布事故与批量污染
某次发布带着缺陷上线,几个小时里持续写入错误数据 —— 这类场景的难点不是恢复,而是 影响范围不清楚。
克隆恢复在这里的价值是提供一个 干净的对照组:把克隆集群恢复到发布之前,与生产库做数据比对,
量化污染范围,再决定是修复数据(把正确值从克隆库导回)还是整体回滚(切换到克隆集群)。
因为克隆恢复可以反复执行,您可以多次尝试不同时间点,逐步逼近"最后一个干净时刻"。
审计与取证
“上个月月底这个账户的余额是多少?"—— 有些问题只有历史数据能回答。
克隆恢复到指定时刻、以只读方式查询、用后即焚,是回答这类问题的标准做法:
不影响生产、不修改历史、可审计可复现。指定时间、LSN、XID 或命名恢复点并配合 action: pause,
集群可以停在目标点上供检查,而不推进到新时间线;immediate 只表示尽快恢复到首个一致点,不用于选择历史时刻。
机房级灾难
主从全灭、磁盘阵列损毁、勒索软件加密了所有主机 —— 高可用在这类灾难面前无能为力,PITR 是最后一道防线。
其中最关键的前提是:备份仓库在灾难的故障域之外。使用 远程备份仓库 时,
数据库主机全部丢失也不影响恢复;使用本地仓库时,这道防线并不存在。
恢复流程是在新硬件上重建:准备新节点,恢复配置清单(它本身应在 git 中,见 声明式配置),
将集群指向远程仓库,恢复到归档流末尾:
./pgsql-pitr.yml -l pg-meta -e '{"pg_pitr": {"action": "promote"}}' # 未指定恢复目标:重放到归档流末尾并显式提升
配置清单与远程备份仓库是重建的两块核心拼图,但不是全部:还需要 Pigsty 安装介质或软件仓库、
备份访问凭据与加密口令、PKI/CA、自定义文件,以及 DNS 和其他外部依赖。配置清单可以纳入私有版本控制,
秘密与私钥则应加密保存并与备份分离 —— 这才是独立故障域真正的意义。
把事故排练成肌肉记忆
以上每个场景的第一次实战,都不应该发生在生产事故中。
克隆恢复给了您一个不触碰生产的演练场:可以把可访问的集群备份恢复成一套新集群,验证、计时、销毁;目标集群会被覆盖。
建议把恢复演练作为例行运维的一部分 —— 每季度(或每次重大架构变更后)完整走一遍克隆恢复流程,回答三个问题:
- 备份可用吗? 端到端还原成功,数据完整。
- RTO 是多少? 实测还原耗时,而不是估算 —— 数据库在增长,去年的答案今年未必成立。
- 人熟练吗? 值班工程师能否不翻文档完成恢复。
没有演练过的备份只是一种心理安慰。演练过的备份,才是真正的时间机器。
具体操作步骤请参阅 恢复操作 与 克隆数据库集群。