这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

时间点恢复 —— 数据库的时间机器(PITR)

高可用解决"机器坏了",时间点恢复解决"数据错了"。Pigsty 基于 pgBackRest 提供开箱即用的 PITR 能力,让您可以将集群回滚至恢复窗口内的任意时刻,为人为失误与软件缺陷兜底。

当您不小心删除了数据、表、甚至整个数据库时,时间点恢复(Point-in-Time Recovery,PITR)让您可以回到过去。

—— 这个曾经只有资深 DBA 才能施展的『魔法』,在 Pigsty 的标准配置中零配置开箱即用。


复制不是备份

高可用 可以在硬件故障时自动切换主库,让服务免于中断。但它有一个天然的盲区:复制不是备份

流复制会以毫秒级的延迟,把主库上发生的一切忠实地同步到所有从库 —— 包括那条忘了加 WHEREDELETE, 和那句敲错了目标库的 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(恢复耗时):从 ∞(永久丢失)降至几十分钟到几小时,取决于备份大小与磁盘/网络带宽。
单实例配置策略事件RTORPO
什么也不做主机与本地数据同时丢失永久丢失全部丢失
基础备份主机与本地数据同时丢失取决于备份大小与带宽(几小时)丢失上次备份后的数据(几小时到几天)
基础备份 + 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: onarchive_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 infopgbackrest 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汇总恢复计划:源集群、目标类型、还原命令;只打印,不会暂停等待确认
pausepatronictl 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 pitrpgsql-pitr.yml


原地恢复与克隆恢复

同一套恢复机制,有两种截然不同的用法:

维度原地恢复克隆恢复
做法把生产集群整体回滚到过去用备份把 另一套集群 恢复到过去
停机需要(恢复期间服务不可用)不需要(生产集群不受影响)
影响目标点之后的 所有 写入都被抹去不影响源集群;目标集群会被覆盖,可反复尝试不同时间点
适用整库损毁、灾难恢复、可接受回滚误删找回、审计取证、恢复演练

克隆恢复的关键是 pg_pitrcluster 字段 —— 它指定 从谁的备份 恢复。 下面的命令把 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 出来、导回生产,是处理误删除的标准姿势 —— 全库回滚是最后手段,而不是第一反应。克隆恢复的完整流程与善后事项,请参阅 克隆数据库集群


恢复之后

恢复完成不等于事情结束。有三件事应当纳入收尾清单:

  1. 新时间线,新备份:提升后集群运行在新时间线上。尽快执行一次全量备份(pg-backup full), 让恢复窗口在新时间线上重新建立。
  2. 归档状态:探索性恢复后,按 恢复后处理 恢复归档。
  3. 克隆善后:克隆出的新集群与源集群的备份身份(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)

没加 WHEREDELETE、写错条件的 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 TABLEDROP 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 和其他外部依赖。配置清单可以纳入私有版本控制, 秘密与私钥则应加密保存并与备份分离 —— 这才是独立故障域真正的意义。


把事故排练成肌肉记忆

以上每个场景的第一次实战,都不应该发生在生产事故中。

克隆恢复给了您一个不触碰生产的演练场:可以把可访问的集群备份恢复成一套新集群,验证、计时、销毁;目标集群会被覆盖。 建议把恢复演练作为例行运维的一部分 —— 每季度(或每次重大架构变更后)完整走一遍克隆恢复流程,回答三个问题:

  1. 备份可用吗? 端到端还原成功,数据完整。
  2. RTO 是多少? 实测还原耗时,而不是估算 —— 数据库在增长,去年的答案今年未必成立。
  3. 人熟练吗? 值班工程师能否不翻文档完成恢复。

没有演练过的备份只是一种心理安慰。演练过的备份,才是真正的时间机器。

具体操作步骤请参阅 恢复操作克隆数据库集群