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

返回本页常规视图.

备份恢复

时间点恢复(PITR)备份与恢复

Pigsty 使用 pgBackRest 管理 PostgreSQL 备份,这可能是生态系统中最强大的开源备份工具。 它支持增量/并行备份与恢复、加密、MinIO/S3 等众多特性。Pigsty 默认为每个 PGSQL 集群预配置了备份功能。

章节内容
机制备份脚本、定时任务、pgbackrest、仓库与管理
策略备份策略、磁盘规划、恢复窗口权衡
仓库配置备份仓库:本地、MinIO、S3
管理常用备份管理命令
恢复使用剧本恢复到特定时间点
示例沙箱示例:手工执行恢复操作

快速上手

  1. 备份策略:使用 Crontab 调度基础备份
  2. WAL 归档:持续记录写入活动
  3. 恢复与还原:从备份和 WAL 归档中恢复
node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ]
./pgsql-pitr.yml -e '{"pg_pitr": { "time": "2025-07-13 10:00:00+00" }}'

1 - 备份策略

根据您的需求设计备份策略
  • 何时:备份策略
  • 何处:备份仓库
  • 如何:备份方法

何时备份

第一个问题是何时备份您的数据库——这是备份频率和恢复时间之间的权衡。 由于您需要从上一次备份开始重放 WAL 日志到恢复目标点,备份越频繁,需要重放的 WAL 日志就越少,恢复速度就越快。

每日全量备份

对于生产数据库,建议从最简单的每日全量备份策略开始。 这也是 Pigsty 的默认备份策略,通过 crontab 实现。

node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ]
pgbackrest_method: local          # 选择备份仓库方法:`local`、`minio` 或其他自定义仓库
pgbackrest_repo:                  # pgbackrest 仓库配置: https://pgbackrest.org/configuration.html#section-repository
  local:                          # 使用本地 POSIX 文件系统的默认 pgbackrest 仓库
    path: /pg/backup              # 本地备份目录,默认为 `/pg/backup`
    retention_full_type: count    # 按数量保留全量备份
    retention_full: 2             # 使用本地文件系统仓库时,保留2个,最多3个全量备份

配合默认的 local 本地文件系统备份仓库使用时,可提供 24~48 小时的恢复窗口。

pitr-scope

假设您的数据库大小为 100GB,每天写入 10GB 数据,则备份大小如下:

pitr-space

这将消耗数据库大小的 2~3 倍空间,再加上 2 天的 WAL 日志。 因此在实践中,您可能需要准备至少 3~5 倍数据库大小的备份磁盘才能使用默认备份策略。

全量 + 增量备份

您可以通过调整这些参数来优化备份空间使用。

如果使用 MinIO / S3 作为集中式备份仓库,您可以使用超出本地磁盘限制的存储空间。 此时可以考虑使用全量 + 增量备份配合 2 周保留策略:

node_crontab:  # 周一凌晨1点全量备份,工作日增量备份
  - '00 01 * * 1 postgres /pg/bin/pg-backup full'
  - '00 01 * * 2,3,4,5,6,7 postgres /pg/bin/pg-backup'
pgbackrest_method: minio
pgbackrest_repo:                  # pgbackrest 仓库配置: https://pgbackrest.org/configuration.html#section-repository
  minio:                          # 可选的 minio 仓库
    type: s3                      # minio 兼容 S3 协议
    s3_endpoint: sss.pigsty       # minio 端点域名,默认为 `sss.pigsty`
    s3_region: us-east-1          # minio 区域,默认 us-east-1,对 minio 无实际意义
    s3_bucket: pgsql              # minio 桶名,默认为 `pgsql`
    s3_key: pgbackrest            # pgbackrest 的 minio 用户访问密钥
    s3_key_secret: S3User.Backup  # pgbackrest 的 minio 用户密钥
    s3_uri_style: path            # minio 使用路径风格 URI 而非主机风格
    path: /pgbackrest             # minio 备份路径,默认为 `/pgbackrest`
    storage_port: 9000            # minio 端口,默认 9000
    storage_ca_file: /etc/pki/ca.crt  # minio CA 证书路径,默认 `/etc/pki/ca.crt`
    block: y                      # 启用块级增量备份
    bundle: y                     # 将小文件打包成单个文件
    bundle_limit: 20MiB           # 文件包大小限制,对象存储建议 20MiB
    bundle_size: 128MiB           # 文件包目标大小,对象存储建议 128MiB
    cipher_type: aes-256-cbc      # 为远程备份仓库启用 AES 加密
    cipher_pass: pgBackRest       # AES 加密密码,默认为 'pgBackRest'
    retention_full_type: time     # 按时间保留全量备份
    retention_full: 14            # 保留最近 14 天的全量备份

配合内置的 minio 备份仓库使用时,可提供保证 1 周的 PITR 恢复窗口。

pitr-scope2

假设您的数据库大小为 100GB,每天写入 10GB 数据,则备份大小如下:

pitr-space2


备份位置

默认情况下,Pigsty 提供两个默认备份仓库定义:localminio 备份仓库。

  • local默认选项,使用本地 /pg/backup 目录(软链接指向 pg_fs_backup/data/backups
  • minio:使用 SNSD 单节点 MinIO 集群(Pigsty 支持,但默认不启用)
pgbackrest_method: local          # 选择备份仓库方法:`local`、`minio` 或其他自定义仓库
pgbackrest_repo:                  # pgbackrest 仓库配置: https://pgbackrest.org/configuration.html#section-repository
  local:                          # 使用本地 POSIX 文件系统的默认 pgbackrest 仓库
    path: /pg/backup              # 本地备份目录,默认为 `/pg/backup`
    retention_full_type: count    # 按数量保留全量备份
    retention_full: 2             # 使用本地文件系统仓库时,保留2个,最多3个全量备份
  minio:                          # 可选的 minio 仓库
    type: s3                      # minio 兼容 S3 协议
    s3_endpoint: sss.pigsty       # minio 端点域名,默认为 `sss.pigsty`
    s3_region: us-east-1          # minio 区域,默认 us-east-1,对 minio 无实际意义
    s3_bucket: pgsql              # minio 桶名,默认为 `pgsql`
    s3_key: pgbackrest            # pgbackrest 的 minio 用户访问密钥
    s3_key_secret: S3User.Backup  # pgbackrest 的 minio 用户密钥
    s3_uri_style: path            # minio 使用路径风格 URI 而非主机风格
    path: /pgbackrest             # minio 备份路径,默认为 `/pgbackrest`
    storage_port: 9000            # minio 端口,默认 9000
    storage_ca_file: /etc/pki/ca.crt  # minio CA 证书路径,默认 `/etc/pki/ca.crt`
    block: y                      # 启用块级增量备份
    bundle: y                     # 将小文件打包成单个文件
    bundle_limit: 20MiB           # 文件包大小限制,对象存储建议 20MiB
    bundle_size: 128MiB           # 文件包目标大小,对象存储建议 128MiB
    cipher_type: aes-256-cbc      # 为远程备份仓库启用 AES 加密
    cipher_pass: pgBackRest       # AES 加密密码,默认为 'pgBackRest'
    retention_full_type: time     # 按时间保留全量备份
    retention_full: 14            # 保留最近 14 天的全量备份

2 - 备份机制

备份脚本、定时任务、备份仓库与基础设施

备份可以通过内置脚本调用,使用节点 crontab 定时执行, 由 pgbackrest 管理,存储在备份仓库中, 仓库可以是本地磁盘文件系统或 MinIO / S3,并支持不同的保留策略。


脚本

您可以使用 pg_dbsu 用户(默认为 postgres)执行 pgbackrest 命令创建备份:

pgbackrest --stanza=pg-meta --type=full backup   # 为集群 pg-meta 创建全量备份
$ pgbackrest --stanza=pg-meta --type=full backup
2025-07-15 01:36:57.007 P00   INFO: backup command begin 2.54.2: --annotation=pg_cluster=pg-meta ...
2025-07-15 01:36:57.030 P00   INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
2025-07-15 01:36:57.105 P00   INFO: backup start archive = 000000010000000000000006, lsn = 0/6000028
2025-07-15 01:36:58.540 P00   INFO: new backup label = 20250715-013657F
2025-07-15 01:36:58.588 P00   INFO: full backup size = 44.5MB, file total = 1437
2025-07-15 01:36:58.589 P00   INFO: backup command end: completed successfully (1584ms)
$ pgbackrest --stanza=pg-meta --type=diff backup
2025-07-15 01:37:24.952 P00   INFO: backup command begin 2.54.2: ...
2025-07-15 01:37:24.985 P00   INFO: last backup label = 20250715-013657F, version = 2.54.2
2025-07-15 01:37:26.337 P00   INFO: new backup label = 20250715-013657F_20250715-013724D
2025-07-15 01:37:26.381 P00   INFO: diff backup size = 424.3KB, file total = 1437
2025-07-15 01:37:26.381 P00   INFO: backup command end: completed successfully (1431ms)
$ pgbackrest --stanza=pg-meta --type=incr backup
2025-07-15 01:37:30.305 P00   INFO: backup command begin 2.54.2: ...
2025-07-15 01:37:30.337 P00   INFO: last backup label = 20250715-013657F_20250715-013724D, version = 2.54.2
2025-07-15 01:37:31.356 P00   INFO: new backup label = 20250715-013657F_20250715-013730I
2025-07-15 01:37:31.403 P00   INFO: incr backup size = 8.3KB, file total = 1437
2025-07-15 01:37:31.403 P00   INFO: backup command end: completed successfully (1099ms)
$ pgbackrest --stanza=pg-meta info
stanza: pg-meta
    status: ok
    cipher: aes-256-cbc

    db (current)
        wal archive min/max (17): 000000010000000000000001/00000001000000000000000A

        full backup: 20250715-013657F
            timestamp start/stop: 2025-07-15 01:36:57+00 / 2025-07-15 01:36:58+00
            wal start/stop: 000000010000000000000006 / 000000010000000000000006
            database size: 44.5MB, database backup size: 44.5MB
            repo1: backup size: 8.7MB

        diff backup: 20250715-013657F_20250715-013724D
            timestamp start/stop: 2025-07-15 01:37:24+00 / 2025-07-15 01:37:26+00
            database size: 44.5MB, database backup size: 424.3KB
            repo1: backup size: 94KB
            backup reference total: 1 full

        incr backup: 20250715-013657F_20250715-013730I
            timestamp start/stop: 2025-07-15 01:37:30+00 / 2025-07-15 01:37:31+00
            database size: 44.5MB, database backup size: 8.3KB
            repo1: backup size: 504B
            backup reference total: 1 full, 1 diff

这里的 stanza 是数据库集群名称:pg_cluster,在默认配置中为 pg-meta

Pigsty 提供了 pb 别名和 pg-backup 包装脚本,会自动填充当前集群名称作为 stanza:

function pb() {
    local stanza=$(grep -o '\[[^][]*]' /etc/pgbackrest/pgbackrest.conf | head -n1 | sed 's/.*\[\([^]]*\)].*/\1/')
    pgbackrest --stanza=$stanza $@
}
pb ...    # pgbackrest --stanza=pg-meta ...
pb info   # pgbackrest --stanza=pg-meta info
pb backup # pgbackrest --stanza=pg-meta backup
pg-backup full   # 执行全量备份         = pgbackrest --stanza=pg-meta --type=full backup
pg-backup incr   # 执行增量备份         = pgbackrest --stanza=pg-meta --type=incr backup
pg-backup diff   # 执行差异备份         = pgbackrest --stanza=pg-meta --type=diff backup

定时备份

Pigsty 利用 Linux crontab 来调度备份任务。您可以用它定义备份策略。

例如,大多数单节点配置模板都有以下用于备份的 node_crontab

node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ]

您可以使用 crontab 和 pg-backup 脚本设计更复杂的备份策略,例如:

node_crontab:  # 周一凌晨1点全量备份,工作日增量备份
  - '00 01 * * 1 postgres /pg/bin/pg-backup full'
  - '00 01 * * 2,3,4,5,6,7 postgres /pg/bin/pg-backup'

要应用 crontab 变更,使用 node.yml 更新所有节点的 crontab:

./node.yml -t node_crontab -l pg-meta    # 将 crontab 变更应用到 pg-meta 组

pgbackrest

以下是 Pigsty 对 pgbackrest 的配置细节:

文件层次结构

  • bin:/usr/bin/pgbackrest,来自 PGDG 的 pgbackrest 包,在组别名 pgsql-common 中。
  • conf:/etc/pgbackrest,主配置文件是 /etc/pgbackrest/pgbackrest.conf
  • logs:/pg/log/pgbackrest/*,由 pgbackrest_log_dir 控制
  • tmp:/pg/spool 用作 pgbackrest 的临时 spool 目录
  • data:/pg/backup 用于存储数据(当选择默认的 local 文件系统备份仓库时)

此外,在 PITR 恢复过程中,Pigsty 会创建临时的 /pg/conf/pitr.conf pgbackrest 配置文件, 并将 postgres 恢复日志写入 /pg/tmp/recovery.log 文件。

监控

有一个 pgbackrest_exporter 服务运行在 pgbackrest_exporter_port9854)端口上,用于导出 pgbackrest 指标。 您可以通过 pgbackrest_exporter_options 自定义它, 或将 pgbackrest_exporter_enabled 设置为 false 来禁用它。

初始备份

当创建 postgres 集群时,Pigsty 会自动创建初始备份。 由于新集群几乎为空,这是一个很小的备份。 它会留下一个 /etc/pgbackrest/initial.done 标记文件,以避免重复创建初始备份。 如果不需要初始备份,请将 pgbackrest_init_backup 设置为 false


管理

启用备份

如果数据库集群创建时 pgbackrest_enabled 设置为 true,备份将自动启用。

如果创建时该值为 false,您可以使用以下命令启用 pgbackrest 组件:

./pgsql.yml -t pg_backup    # 运行 pgbackrest 子任务

删除备份

当移除主实例(pg_role = primary)时,Pigsty 会删除 pgbackrest 备份 stanza。

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # 保留备份
./pgsql-rm.yml -t pg_backup            # 仅删除备份

使用 pg_backup 子任务仅删除备份,使用 pg_rm_backup 参数(设为 false)保留备份。

如果您的备份仓库被锁定(例如 S3 / MinIO 有锁定选项),此操作将失败。

列出备份

此命令将列出 pgbackrest 仓库中的所有备份(所有集群共享)

pgbackrest info

手动备份

Pigsty 提供了内置脚本 /pg/bin/pg-backup,封装了 pgbackrest 备份命令。

pg-backup        # 执行增量备份
pg-backup full   # 执行全量备份
pg-backup incr   # 执行增量备份
pg-backup diff   # 执行差异备份

基础备份

Pigsty 提供了一个替代备份脚本 /pg/bin/pg-basebackup,它不依赖 pgbackrest,直接提供数据库集群的物理副本。 默认备份目录为 /pg/backup

NAME
  pg-basebackup  -- make base backup from PostgreSQL instance

SYNOPSIS
  pg-basebackup -sdfeukr
  pg-basebackup --src postgres:/// --dst . --file backup.tar.lz4

DESCRIPTION
-s, --src, --url     备份源 URL,可选,默认为 "postgres:///",如需密码应在 url、ENV 或 .pgpass 中提供
-d, --dst, --dir     备份文件存放位置,默认为 "/pg/backup"
-f, --file           覆盖默认备份文件名,"backup_${tag}_${date}.tar.lz4"
-r, --remove         删除 n 分钟前的 .lz4 文件,默认 1200(20小时)
-t, --tag            备份文件标签,未设置时使用目标集群名或本地 IP 地址,也用于默认文件名
-k, --key            指定 --encrypt 时的加密密钥,默认密钥为 ${tag}
-u, --upload         上传备份文件到云存储(需自行实现)
-e, --encryption     使用 OpenSSL RC4 加密,未指定密钥时使用 tag 作为密钥
-h, --help           打印此帮助信息
postgres@pg-meta-1:~$ pg-basebackup
[2025-07-13 06:16:05][INFO] ================================================================
[2025-07-13 06:16:05][INFO] [INIT] pg-basebackup begin, checking parameters
[2025-07-13 06:16:05][DEBUG] [INIT] filename  (-f)    :   backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][DEBUG] [INIT] src       (-s)    :   postgres:///
[2025-07-13 06:16:05][DEBUG] [INIT] dst       (-d)    :   /pg/backup
[2025-07-13 06:16:05][INFO] [LOCK] lock acquired success on /tmp/backup.lock, pid=107417
[2025-07-13 06:16:05][INFO] [BKUP] backup begin, from postgres:/// to /pg/backup/backup_pg-meta_20250713.tar.lz4
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/7000028 on timeline 1
pg_basebackup: write-ahead log end point: 0/7000FD8
pg_basebackup: syncing data to disk ...
pg_basebackup: base backup completed
[2025-07-13 06:16:06][INFO] [BKUP] backup complete!
[2025-07-13 06:16:06][INFO] [DONE] backup procedure complete!
[2025-07-13 06:16:06][INFO] ================================================================

备份使用 lz4 压缩。您可以使用以下命令解压并提取 tarball:

mkdir -p /tmp/data   # 将备份提取到此目录
cat /pg/backup/backup_pg-meta_20250713.tar.lz4 | unlz4 -d -c | tar -xC /tmp/data

逻辑备份

您也可以使用 pg_dump 命令执行逻辑备份。

逻辑备份不能用于 PITR(时间点恢复),但对于在不同主版本之间迁移数据或实现灵活的数据导出逻辑非常有用。

从仓库引导

假设您有一个现有集群 pg-meta,想要将其克隆pg-meta2

您需要创建新的 pg-meta2 集群分支,然后在其上运行 pitr

3 - 备份仓库

PostgreSQL 备份存储仓库配置

您可以通过指定 pgbackrest_repo 参数来配置备份存储位置。 您可以在此定义多个仓库,Pigsty 会根据 pgbackrest_method 的值选择使用哪个。

默认仓库

默认情况下,Pigsty 提供两个默认备份仓库定义:localminio 备份仓库。

  • local默认选项,使用本地 /pg/backup 目录(软链接指向 pg_fs_backup/data/backups
  • minio:使用 SNSD 单节点 MinIO 集群(Pigsty 支持,但默认不启用)
pgbackrest_method: local          # 选择备份仓库方法:`local`、`minio` 或其他自定义仓库
pgbackrest_repo:                  # pgbackrest 仓库配置: https://pgbackrest.org/configuration.html#section-repository
  local:                          # 使用本地 POSIX 文件系统的默认 pgbackrest 仓库
    path: /pg/backup              # 本地备份目录,默认为 `/pg/backup`
    retention_full_type: count    # 按数量保留全量备份
    retention_full: 2             # 使用本地文件系统仓库时,保留2个,最多3个全量备份
  minio:                          # 可选的 minio 仓库
    type: s3                      # minio 兼容 S3 协议
    s3_endpoint: sss.pigsty       # minio 端点域名,默认为 `sss.pigsty`
    s3_region: us-east-1          # minio 区域,默认 us-east-1,对 minio 无实际意义
    s3_bucket: pgsql              # minio 桶名,默认为 `pgsql`
    s3_key: pgbackrest            # pgbackrest 的 minio 用户访问密钥
    s3_key_secret: S3User.Backup  # pgbackrest 的 minio 用户密钥
    s3_uri_style: path            # minio 使用路径风格 URI 而非主机风格
    path: /pgbackrest             # minio 备份路径,默认为 `/pgbackrest`
    storage_port: 9000            # minio 端口,默认 9000
    storage_ca_file: /etc/pki/ca.crt  # minio CA 证书路径,默认 `/etc/pki/ca.crt`
    block: y                      # 启用块级增量备份
    bundle: y                     # 将小文件打包成单个文件
    bundle_limit: 20MiB           # 文件包大小限制,对象存储建议 20MiB
    bundle_size: 128MiB           # 文件包目标大小,对象存储建议 128MiB
    cipher_type: aes-256-cbc      # 为远程备份仓库启用 AES 加密
    cipher_pass: pgBackRest       # AES 加密密码,默认为 'pgBackRest'
    retention_full_type: time     # 按时间保留全量备份
    retention_full: 14            # 保留最近 14 天的全量备份

仓库保留策略

如果每天备份但不删除旧备份,备份仓库会不断增长并耗尽磁盘空间。 您需要定义保留策略,只保留有限数量的备份。

默认备份策略定义在 pgbackrest_repo 参数中,可按需调整。

  • local:保留最近 2 个全量备份,备份期间最多允许 3 个
  • minio:保留最近 14 天的所有全量备份

空间规划

对象存储提供几乎无限的存储容量,因此无需担心磁盘空间。 您可以使用混合的全量 + 差异备份策略来优化空间使用。

对于本地磁盘备份仓库,Pigsty 建议使用保留最近 2 个全量备份的策略, 这意味着磁盘上保留两个最新的全量备份(运行新备份时可能存在第三个副本)。

这可保证至少 24 小时的恢复窗口。详情请参阅备份策略


其他仓库选项

您也可以使用其他服务作为备份仓库,详情请参阅 pgbackrest 文档


仓库版本控制

您甚至可以指定 repo target time 来获取对象存储的快照。

您可以通过在 minio_buckets 中添加 versioning 标志来启用 MinIO 版本控制:

minio_buckets:
  - { name: pgsql ,versioning: true }
  - { name: meta  ,versioning: true }
  - { name: data }

仓库锁定

某些对象存储服务(S3、MinIO 等)支持锁定功能,可以防止备份被删除,即使是 DBA 本人也无法删除。

您可以通过在 minio_buckets 中添加 lock 标志来启用 MinIO 锁定功能:

minio_buckets:
  - { name: pgsql , lock: true }
  - { name: meta ,versioning: true  }
  - { name: data }

使用对象存储

对象存储服务提供几乎无限的存储容量,并为您的系统提供远程容灾能力。 如果您没有对象存储服务,Pigsty 内置了 MinIO 支持。

MinIO

您可以通过取消注释以下设置来启用 MinIO 备份仓库。 请注意 pgbackrest 只支持 HTTPS / 域名,因此您必须使用域名和 HTTPS 端点运行 MinIO。

all:
  vars:
    pgbackrest_method: minio      # 使用 minio 作为默认备份仓库
  children:                       # 定义一个单节点 minio SNSD 集群
    minio: { hosts: { 10.10.10.10: { minio_seq: 1 }} ,vars: { minio_cluster: minio }}

S3

如果您只有一个节点,有意义的备份策略可以是使用云厂商的对象存储服务,如 AWS S3、阿里云 OSS 或 Google Cloud 等。 为此,您可以定义一个新仓库:

pgbackrest_method: s3             # 使用 'pgbackrest_repo.s3' 作为备份仓库
pgbackrest_repo:                  # pgbackrest 仓库配置: https://pgbackrest.org/configuration.html#section-repository

  s3:                             # 阿里云 OSS(S3 兼容)对象存储服务
    type: s3                      # oss 兼容 S3 协议
    s3_endpoint: oss-cn-beijing-internal.aliyuncs.com
    s3_region: oss-cn-beijing
    s3_bucket: <your_bucket_name>
    s3_key: <your_access_key>
    s3_key_secret: <your_secret_key>
    s3_uri_style: host
    path: /pgbackrest
    bundle: y                     # 将小文件打包成单个文件
    bundle_limit: 20MiB           # 文件包大小限制,对象存储建议 20MiB
    bundle_size: 128MiB           # 文件包目标大小,对象存储建议 128MiB
    cipher_type: aes-256-cbc      # 为远程备份仓库启用 AES 加密
    cipher_pass: pgBackRest       # AES 加密密码,默认为 'pgBackRest'
    retention_full_type: time     # 按时间保留全量备份
    retention_full: 14            # 保留最近 14 天的全量备份

  local:                          # 使用本地 POSIX 文件系统的默认 pgbackrest 仓库
    path: /pg/backup              # 本地备份目录,默认为 `/pg/backup`
    retention_full_type: count    # 按数量保留全量备份
    retention_full: 2             # 使用本地文件系统仓库时,保留2个,最多3个全量备份

管理备份

启用备份

如果数据库集群创建时 pgbackrest_enabled 设置为 true,备份将自动启用。

如果创建时该值为 false,您可以使用以下命令启用 pgbackrest 组件:

./pgsql.yml -t pg_backup    # 运行 pgbackrest 子任务

删除备份

当移除主实例(pg_role = primary)时,Pigsty 会删除 pgbackrest 备份 stanza。

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # 保留备份
./pgsql-rm.yml -t pg_backup            # 仅删除备份

使用 pg_backup 子任务仅删除备份,使用 pg_rm_backup 参数(设为 false)保留备份。

如果您的备份仓库被锁定(例如 S3 / MinIO 有锁定选项),此操作将失败。

列出备份

此命令将列出 pgbackrest 仓库中的所有备份(所有集群共享)

pgbackrest info

手动备份

Pigsty 提供了内置脚本 /pg/bin/pg-backup,封装了 pgbackrest 备份命令。

pg-backup        # 执行增量备份
pg-backup full   # 执行全量备份
pg-backup incr   # 执行增量备份
pg-backup diff   # 执行差异备份

基础备份

Pigsty 提供了一个替代备份脚本 /pg/bin/pg-basebackup,它不依赖 pgbackrest,直接提供数据库集群的物理副本。 默认备份目录为 /pg/backup

NAME
  pg-basebackup  -- make base backup from PostgreSQL instance

SYNOPSIS
  pg-basebackup -sdfeukr
  pg-basebackup --src postgres:/// --dst . --file backup.tar.lz4

DESCRIPTION
-s, --src, --url     备份源 URL,可选,默认为 "postgres:///",如需密码应在 url、ENV 或 .pgpass 中提供
-d, --dst, --dir     备份文件存放位置,默认为 "/pg/backup"
-f, --file           覆盖默认备份文件名,"backup_${tag}_${date}.tar.lz4"
-r, --remove         删除 n 分钟前的 .lz4 文件,默认 1200(20小时)
-t, --tag            备份文件标签,未设置时使用目标集群名或本地 IP 地址,也用于默认文件名
-k, --key            指定 --encrypt 时的加密密钥,默认密钥为 ${tag}
-u, --upload         上传备份文件到云存储(需自行实现)
-e, --encryption     使用 OpenSSL RC4 加密,未指定密钥时使用 tag 作为密钥
-h, --help           打印此帮助信息
postgres@pg-meta-1:~$ pg-basebackup
[2025-07-13 06:16:05][INFO] ================================================================
[2025-07-13 06:16:05][INFO] [INIT] pg-basebackup begin, checking parameters
[2025-07-13 06:16:05][DEBUG] [INIT] filename  (-f)    :   backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][DEBUG] [INIT] src       (-s)    :   postgres:///
[2025-07-13 06:16:05][DEBUG] [INIT] dst       (-d)    :   /pg/backup
[2025-07-13 06:16:05][INFO] [LOCK] lock acquired success on /tmp/backup.lock, pid=107417
[2025-07-13 06:16:05][INFO] [BKUP] backup begin, from postgres:/// to /pg/backup/backup_pg-meta_20250713.tar.lz4
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/7000028 on timeline 1
pg_basebackup: write-ahead log end point: 0/7000FD8
pg_basebackup: syncing data to disk ...
pg_basebackup: base backup completed
[2025-07-13 06:16:06][INFO] [BKUP] backup complete!
[2025-07-13 06:16:06][INFO] [DONE] backup procedure complete!
[2025-07-13 06:16:06][INFO] ================================================================

备份使用 lz4 压缩。您可以使用以下命令解压并提取 tarball:

mkdir -p /tmp/data   # 将备份提取到此目录
cat /pg/backup/backup_pg-meta_20250713.tar.lz4 | unlz4 -d -c | tar -xC /tmp/data

逻辑备份

您也可以使用 pg_dump 命令执行逻辑备份。

逻辑备份不能用于 PITR(时间点恢复),但对于在不同主版本之间迁移数据或实现灵活的数据导出逻辑非常有用。

从仓库引导

假设您有一个现有集群 pg-meta,想要将其克隆pg-meta2

您需要创建新的 pg-meta2 集群分支,然后在其上运行 pitr

4 - 管理命令

管理备份仓库和备份

启用备份

如果数据库集群创建时 pgbackrest_enabled 设置为 true,备份将自动启用。

如果创建时该值为 false,您可以使用以下命令启用 pgbackrest 组件:

./pgsql.yml -t pg_backup    # 运行 pgbackrest 子任务

删除备份

当移除主实例(pg_role = primary)时,Pigsty 会删除 pgbackrest 备份 stanza。

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # 保留备份
./pgsql-rm.yml -t pg_backup            # 仅删除备份

使用 pg_backup 子任务仅删除备份,使用 pg_rm_backup 参数(设为 false)保留备份。

如果您的备份仓库被锁定(例如 S3 / MinIO 有锁定选项),此操作将失败。


列出备份

此命令将列出 pgbackrest 仓库中的所有备份(所有集群共享)

pgbackrest info

手动备份

Pigsty 提供了内置脚本 /pg/bin/pg-backup,封装了 pgbackrest 备份命令。

pg-backup        # 执行增量备份
pg-backup full   # 执行全量备份
pg-backup incr   # 执行增量备份
pg-backup diff   # 执行差异备份

基础备份

Pigsty 提供了一个替代备份脚本 /pg/bin/pg-basebackup,它不依赖 pgbackrest,直接提供数据库集群的物理副本。 默认备份目录为 /pg/backup

NAME
  pg-basebackup  -- make base backup from PostgreSQL instance

SYNOPSIS
  pg-basebackup -sdfeukr
  pg-basebackup --src postgres:/// --dst . --file backup.tar.lz4

DESCRIPTION
-s, --src, --url     备份源 URL,可选,默认为 "postgres:///",如需密码应在 url、ENV 或 .pgpass 中提供
-d, --dst, --dir     备份文件存放位置,默认为 "/pg/backup"
-f, --file           覆盖默认备份文件名,"backup_${tag}_${date}.tar.lz4"
-r, --remove         删除 n 分钟前的 .lz4 文件,默认 1200(20小时)
-t, --tag            备份文件标签,未设置时使用目标集群名或本地 IP 地址,也用于默认文件名
-k, --key            指定 --encrypt 时的加密密钥,默认密钥为 ${tag}
-u, --upload         上传备份文件到云存储(需自行实现)
-e, --encryption     使用 OpenSSL RC4 加密,未指定密钥时使用 tag 作为密钥
-h, --help           打印此帮助信息
postgres@pg-meta-1:~$ pg-basebackup
[2025-07-13 06:16:05][INFO] ================================================================
[2025-07-13 06:16:05][INFO] [INIT] pg-basebackup begin, checking parameters
[2025-07-13 06:16:05][DEBUG] [INIT] filename  (-f)    :   backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][DEBUG] [INIT] src       (-s)    :   postgres:///
[2025-07-13 06:16:05][DEBUG] [INIT] dst       (-d)    :   /pg/backup
[2025-07-13 06:16:05][INFO] [LOCK] lock acquired success on /tmp/backup.lock, pid=107417
[2025-07-13 06:16:05][INFO] [BKUP] backup begin, from postgres:/// to /pg/backup/backup_pg-meta_20250713.tar.lz4
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/7000028 on timeline 1
pg_basebackup: write-ahead log end point: 0/7000FD8
pg_basebackup: syncing data to disk ...
pg_basebackup: base backup completed
[2025-07-13 06:16:06][INFO] [BKUP] backup complete!
[2025-07-13 06:16:06][INFO] [DONE] backup procedure complete!
[2025-07-13 06:16:06][INFO] ================================================================

备份使用 lz4 压缩。您可以使用以下命令解压并提取 tarball:

mkdir -p /tmp/data   # 将备份提取到此目录
cat /pg/backup/backup_pg-meta_20250713.tar.lz4 | unlz4 -d -c | tar -xC /tmp/data

逻辑备份

您也可以使用 pg_dump 命令执行逻辑备份。

逻辑备份不能用于 PITR(时间点恢复),但对于在不同主版本之间迁移数据或实现灵活的数据导出逻辑非常有用。


从仓库引导

假设您有一个现有集群 pg-meta,想要将其克隆pg-meta2

您需要创建新的 pg-meta2 集群分支,然后在其上运行 pitr

5 - 恢复操作

从备份恢复 PostgreSQL

您可以使用预配置的 pgbackrest 在 Pigsty 中执行时间点恢复(PITR)。


快速上手

如果您想将 pg-meta 集群回滚到之前的时间点,添加 pg_pitr 参数:

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta2
    pg_pitr: { time: '2025-07-13 10:00:00+00' }  # 从最新备份恢复

然后运行 pgsql-pitr.yml 剧本,它将把 pg-meta 集群回滚到指定时间点。

./pgsql-pitr.yml -l pg-meta

恢复后处理

恢复后的集群会禁用 archive_mode,以防止意外的 WAL 写入。 如果恢复后的数据库状态正常,您可以启用 archive_mode 并执行全量备份。

psql -c 'ALTER SYSTEM RESET archive_mode; SELECT pg_reload_conf();'
pg-backup full    # 执行新的全量备份

恢复目标

您可以在 pg_pitr 中指定不同类型的恢复目标,但它们是互斥的:

  • time:恢复到哪个时间点?
  • name:恢复到命名的恢复点(由 pg_create_restore_point 创建)
  • xid:恢复到特定的事务 ID(TXID/XID)
  • lsn:恢复到特定的 LSN(日志序列号)点

如果指定了上述任何参数,恢复类型会相应设置, 否则将设置为 latest(WAL 归档流的末尾)。 特殊的 immediate 类型可用于指示 pgbackrest 通过在第一个一致点停止来最小化恢复时间。

目标类型

pg_pitr: { }  # 恢复到最新状态(WAL 归档流末尾)
pg_pitr: { time: "2025-07-13 10:00:00+00" }
pg_pitr: { lsn: "0/4001C80" }
pg_pitr: { xid: "250000" }
pg_pitr: { name: "some_restore_point" }
pg_pitr: { type: "immediate" }

按时间恢复

最常用的目标是时间点;您可以指定要恢复到的时间点:

./pgsql-pitr.yml -l pg-meta -e '{"pg_pitr": { "time": "2025-12-27 15:50:00+00" }}'

时间应该是有效的 PostgreSQL TIMESTAMP 格式,建议使用 YYYY-MM-DD HH:MM:SS+TZ

按名称恢复

您可以使用 pg_create_restore_point 创建命名恢复点:

SELECT pg_create_restore_point('shit_incoming');

然后在 PITR 中使用该命名恢复点:

./pgsql-pitr.yml -l pg-meta -e '{"pg_pitr": { "name": "shit_incoming" }}'

按 XID 恢复

如果您有一个事务意外删除了某些数据,最好的恢复方式是将数据库恢复到该事务之前的状态。

./pgsql-pitr.yml -e '{"pg_pitr": { "xid": "250000", exclusive: true }}'

您可以从监控仪表盘找到确切的事务 ID,或从 CSVLOG 中的 TXID 字段获取。

按 LSN 恢复

PostgreSQL 使用 LSN(日志序列号)来标识 WAL 记录的位置。 您可以在很多地方找到它,比如 Pigsty 仪表盘的 PG LSN 面板。

./pgsql-pitr.yml -e '{"pg_pitr": { "lsn": "0/4001C80", timeline: "1" }}'

要恢复到 WAL 流中的确切位置,您还可以指定 timeline 参数(默认为 latest


恢复来源

  • cluster:从哪个集群恢复?默认使用当前的 pg_cluster,您可以使用同一 pgbackrest 仓库中的任何其他集群
  • repo:覆盖备份仓库,使用与 pgbackrest_repo 相同的格式
  • set:默认使用 latest 备份集,但您可以通过标签指定特定的 pgbackrest 备份

Pigsty 将从 pgbackrest 备份仓库恢复。如果您使用集中式备份仓库(如 MinIO/S3), 可以指定另一个 “stanza”(另一个集群的备份目录)作为恢复来源。

pg-meta2:
  hosts: { 10.10.10.11: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta2
    pg_pitr: { cluster: pg-meta }  # 从 pg-meta 集群备份恢复

上述配置将标记 PITR 过程使用 pg-meta stanza。 您也可以通过 CLI 参数传递 pg_pitr 参数:

./pgsql-pitr.yml -l pg-meta2 -e '{"pg_pitr": { "cluster": "pg-meta" }}'

从另一个集群 PITR 时也可以使用这些目标:

./pgsql-pitr.yml -l pg-meta2 -e '{"pg_pitr": { "cluster": "pg-meta", "time": "2025-07-14 08:00:00+00" }}'

分步执行

这种方式是半自动的,您将参与 PITR 过程以做出关键决策。

例如,此配置将把 pg-meta 集群本身恢复到指定时间点:

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta2
    pg_pitr: { time: '2025-07-13 10:00:00+00' }  # 从最新备份恢复

让我们逐步执行:

./pgsql-pitr.yml -l pg-meta -t down     # 暂停 patroni 高可用
./pgsql-pitr.yml -l pg-meta -t pitr     # 运行 pitr 过程
./pgsql-pitr.yml -l pg-meta -t up       # 生成 pgbackrest 配置和恢复脚本
# down                 : # 停止高可用并关闭 patroni 和 postgres
#   - pause            : # 暂停 patroni 自动故障切换
#   - stop             : # 停止 patroni 和 postgres 服务
#     - stop_patroni   : # 停止 patroni 服务
#     - stop_postgres  : # 停止 postgres 服务
# pitr                 : # 执行 PITR 过程
#   - config           : # 生成 pgbackrest 配置和恢复脚本
#   - restore          : # 运行 pgbackrest 恢复命令
#   - recovery         : # 启动 postgres 并完成恢复
#   - verify           : # 验证恢复后的集群控制数据
# up:                  : # 启动 postgres / patroni 并恢复高可用
#   - etcd             : # 启动前清理 etcd 元数据
#   - start            : # 启动 patroni 和 postgres 服务
#     - start_postgres : # 启动 postgres 服务
#     - start_patroni  : # 启动 patroni 服务
#   - resume           : # 恢复 patroni 自动故障切换

PITR 参数定义

pg_pitr 参数还有更多可用选项:

pg_pitr:                           # 定义 PITR 任务
    cluster: "some_pg_cls_name"    # 源集群名称
    type: default                   # 恢复目标类型:time, xid, name, lsn, immediate, default
    time: "2025-01-01 10:00:00+00" # 恢复目标:时间,与 xid, name, lsn 互斥
    name: "some_restore_point"     # 恢复目标:命名恢复点,与 time, xid, lsn 互斥
    xid:  "100000"                 # 恢复目标:事务 ID,与 time, name, lsn 互斥
    lsn:  "0/3000000"              # 恢复目标:日志序列号,与 time, name, xid 互斥
    timeline: latest               # 目标时间线,可以是整数,默认为 latest
    exclusive: false               # 是否排除目标点,默认为 false
    action: pause                  # 恢复后操作:pause, promote, shutdown
    archive: false                 # 是否保留归档设置?默认为 false
    db_exclude: [ template0, template1 ]
    db_include: []
    link_map:
      pg_wal: '/data/wal'
      pg_xact: '/data/pg_xact'
    process: 4                     # 并行恢复进程数
    repo: {}                       # 恢复来源仓库
    data: /pg/data                 # 数据恢复位置
    port: 5432                     # 恢复实例的监听端口

6 - 手工恢复

在沙箱环境中按照提示脚本手动执行 PITR

您可以使用 pgsql-pitr.yml 剧本执行 PITR,但在某些情况下,您可能希望手动执行 PITR,直接使用 pgbackrest 原语实现精细的控制。 我们将使用带有 MinIO 备份仓库的 四节点沙箱 集群来演示该过程。

pigsty-sandbox


初始化沙箱

使用 vagrantterraform 准备四节点沙箱环境,然后:

curl https://repo.pigsty.io/get | bash; cd ~/pigsty/
./configure -c full
./install

现在以管理节点上的管理员用户(或 dbsu)身份操作。


检查备份

要检查备份状态,您需要切换到 postgres 用户并使用 pb 命令:

sudo su - postgres    # 切换到 dbsu: postgres 用户
pb info               # 打印 pgbackrest 备份信息

pbpgbackrest 的别名,会自动从 pgbackrest 配置中获取 stanza 名称。

function pb() {
    local stanza=$(grep -o '\[[^][]*]' /etc/pgbackrest/pgbackrest.conf | head -n1 | sed 's/.*\[\([^]]*\)].*/\1/')
    pgbackrest --stanza=$stanza $@
}

您可以看到初始备份信息,这是一个全量备份:

root@pg-meta-1:~# pb info
stanza: pg-meta
    status: ok
    cipher: aes-256-cbc

    db (current)
        wal archive min/max (17): 000000010000000000000001/000000010000000000000007

        full backup: 20250713-022731F
            timestamp start/stop: 2025-07-13 02:27:31+00 / 2025-07-13 02:27:33+00
            wal start/stop: 000000010000000000000004 / 000000010000000000000004
            database size: 44MB, database backup size: 44MB
            repo1: backup size: 8.4MB

备份完成于 2025-07-13 02:27:33+00,这是您可以恢复到的最早时间。 由于 WAL 归档处于活动状态,您可以恢复到备份之后的任何时间点,直到 WAL 结束(即现在)。


生成心跳

您可以生成一些心跳来模拟工作负载。/pg-bin/pg-heartbeat 就是用于此目的的, 它每秒向 monitor.heartbeat 表写入一个心跳时间戳。

make rh     # 运行心跳: ssh 10.10.10.10 'sudo -iu postgres /pg/bin/pg-heartbeat'
ssh 10.10.10.10 'sudo -iu postgres /pg/bin/pg-heartbeat'
   cls   |              ts               |    lsn     |  lsn_int  | txid | status  |       now       |  elapse
---------+-------------------------------+------------+-----------+------+---------+-----------------+----------
 pg-meta | 2025-07-13 03:01:20.318234+00 | 0/115BF5C0 | 291239360 | 4812 | leading | 03:01:20.318234 | 00:00:00

您甚至可以向集群添加更多工作负载,让我们使用 pgbench 生成一些随机写入:

make ri     # 初始化 pgbench
make rw     # 运行 pgbench 读写工作负载
pgbench -is10 postgres://dbuser_meta:[email protected]:5433/meta
while true; do pgbench -nv -P1 -c4 --rate=64 -T10 postgres://dbuser_meta:[email protected]:5433/meta; done
while true; do pgbench -nv -P1 -c4 --rate=64 -T10 postgres://dbuser_meta:[email protected]:5433/meta; done
pgbench (17.5 (Homebrew), server 17.4 (Ubuntu 17.4-1.pgdg24.04+2))
progress: 1.0 s, 60.9 tps, lat 7.295 ms stddev 4.219, 0 failed, lag 1.818 ms
progress: 2.0 s, 69.1 tps, lat 6.296 ms stddev 1.983, 0 failed, lag 1.397 ms
...

PITR 手册

现在让我们选择一个恢复时间点,比如 2025-07-13 03:03:03+00,这是初始备份(和心跳)之后的一个时间点。 要执行手动 PITR,使用 pg-pitr 工具:

$ pg-pitr -t "2025-07-13 03:03:00+00"

它会为您生成执行恢复的指令,通常需要四个步骤:

Perform time PITR on pg-meta
[1. Stop PostgreSQL] ===========================================
   1.1 Pause Patroni (if there are any replicas)
       $ pg pause <cls>  # 暂停 patroni 自动故障切换
   1.2 Shutdown Patroni
       $ pt-stop         # sudo systemctl stop patroni
   1.3 Shutdown Postgres
       $ pg-stop         # pg_ctl -D /pg/data stop -m fast

[2. Perform PITR] ===========================================
   2.1 Restore Backup
       $ pgbackrest --stanza=pg-meta --type=time --target='2025-07-13 03:03:00+00' restore
   2.2 Start PG to Replay WAL
       $ pg-start        # pg_ctl -D /pg/data start
   2.3 Validate and Promote
     - If database content is ok, promote it to finish recovery, otherwise goto 2.1
       $ pg-promote      # pg_ctl -D /pg/data promote
[3. Restore Primary] ===========================================
   3.1 Enable Archive Mode (Restart Required)
       $ psql -c 'ALTER SYSTEM SET archive_mode = on;'
   3.1 Restart Postgres to Apply Changes
       $ pg-restart      # pg_ctl -D /pg/data restart
   3.3 Restart Patroni
       $ pt-restart      # sudo systemctl restart patroni

[4. Restore Cluster] ===========================================
   4.1 Re-Init All [**REPLICAS**] (if any)
       - 4.1.1 option 1: restore replicas with same pgbackrest cmd (require central backup repo)
           $ pgbackrest --stanza=pg-meta --type=time --target='2025-07-13 03:03:00+00' restore
       - 4.1.2 option 2: nuke the replica data dir and restart patroni (may take long time to restore)
           $ rm -rf /pg/data/*; pt-restart
       - 4.1.3 option 3: reinit with patroni, which may fail if primary lsn < replica lsn
           $ pg reinit pg-meta
   4.2 Resume Patroni
       $ pg resume pg-meta
   4.3 Full Backup (optional)
       $ pg-backup full      # 建议在 PITR 后执行新的全量备份

单节点示例

让我们从简单的单节点 pg-meta 集群开始,作为一个更简单的示例。

关闭数据库

pt-stop         # sudo systemctl stop patroni,关闭 patroni(和 postgres)
# 可选,因为如果 patroni 未暂停,postgres 会被 patroni 关闭
$ pg_stop        # pg_ctl -D /pg/data stop -m fast,关闭 postgres

pg_ctl: PID file "/pg/data/postmaster.pid" does not exist
Is server running?

$ pg-ps           # 打印 postgres 相关进程

 UID         PID   PPID  C STIME TTY      STAT   TIME CMD
postgres  31048      1  0 02:27 ?        Ssl    0:19 /usr/sbin/pgbouncer /etc/pgbouncer/pgbouncer.ini
postgres  32026      1  0 02:28 ?        Ssl    0:03 /usr/bin/pg_exporter ...
postgres  35510  35480  0 03:01 pts/2    S+     0:00 /bin/bash /pg/bin/pg-heartbeat

确保本地 postgres 没有运行,然后执行手册中给出的恢复命令:

恢复备份

pgbackrest --stanza=pg-meta --type=time --target='2025-07-13 03:03:00+00' restore
postgres@pg-meta-1:~$ pgbackrest --stanza=pg-meta --type=time --target='2025-07-13 03:03:00+00' restore
2025-07-13 03:17:07.443 P00   INFO: restore command begin 2.54.2: ...
2025-07-13 03:17:07.470 P00   INFO: repo1: restore backup set 20250713-022731F, recovery will start at 2025-07-13 02:27:31
2025-07-13 03:17:07.471 P00   INFO: remove invalid files/links/paths from '/pg/data'
2025-07-13 03:17:08.523 P00   INFO: write updated /pg/data/postgresql.auto.conf
2025-07-13 03:17:08.527 P00   INFO: restore size = 44MB, file total = 1436
2025-07-13 03:17:08.527 P00   INFO: restore command end: completed successfully (1087ms)

验证数据

我们不希望 patroni HA 接管,直到确定数据正确,所以手动启动 postgres:

pg-start
waiting for server to start....2025-07-13 03:19:33.133 UTC [39294] LOG:  redirecting log output to logging collector process
2025-07-13 03:19:33.133 UTC [39294] HINT:  Future log output will appear in directory "/pg/log/postgres".
 done
server started

现在您可以检查数据,看看是否处于您想要的时间点。 您可以通过检查业务表中的最新时间戳来验证,或者在本例中通过心跳表检查。

postgres@pg-meta-1:~$ psql -c 'table monitor.heartbeat'
   id    |              ts               |    lsn    | txid
---------+-------------------------------+-----------+------
 pg-meta | 2025-07-13 03:02:59.214104+00 | 302005504 | 4912

时间戳正好在我们指定的时间点之前!(2025-07-13 03:03:00+00)。 如果这不是您想要的时间点,可以使用不同的时间点重复恢复。 由于恢复是以增量和并行方式执行的,速度非常快。 可以重试直到找到正确的时间点。

提升主库

恢复后的 postgres 集群处于 recovery 模式,因此在提升为主库之前会拒绝任何写操作。 这些恢复参数是由 pgBackRest 在配置文件中生成的。

postgres@pg-meta-1:~$ cat /pg/data/postgresql.auto.conf
# Do not edit this file or use ALTER SYSTEM manually!
# It is managed by Pigsty & Ansible automatically!

# Recovery settings generated by pgBackRest restore on 2025-07-13 03:17:08
archive_mode = 'off'
restore_command = 'pgbackrest --stanza=pg-meta archive-get %f "%p"'
recovery_target_time = '2025-07-13 03:03:00+00'

如果数据正确,您可以提升它为主库,将其标记为新的领导者并准备接受写入。

pg-promote
waiting for server to promote.... done
server promoted
psql -c 'SELECT pg_is_in_recovery()'   # 'f' 表示已提升为主库
 pg_is_in_recovery
-------------------
 f
(1 row)

恢复集群

最后,不仅需要恢复数据,还需要恢复集群状态,例如:

  • patroni 接管
  • 归档模式
  • 备份集
  • 从库

Patroni 接管

您的 postgres 是直接启动的,要恢复 HA 接管,您需要启动 patroni 服务:

pt-start   # sudo systemctl start patroni
pg resume pg-meta      # 恢复 patroni 自动故障切换(如果之前暂停过)

归档模式

archive_mode 在恢复期间被 pgbackrest 禁用。 如果您希望新领导者的写入归档到备份仓库,还需要启用 archive_mode 配置。

psql -c 'show archive_mode'

 archive_mode
--------------
 off
psql -c 'ALTER SYSTEM RESET archive_mode;'
psql -c 'SELECT pg_reload_conf();'
psql -c 'show archive_mode'
# 您也可以直接编辑 postgresql.auto.conf 并使用 pg_ctl 重载
sed -i '/archive_mode/d' /pg/data/postgresql.auto.conf
pg_ctl -D /pg/data reload

备份集

通常建议在 PITR 后执行新的全量备份,但这是可选的。

从库

如果您的 postgres 集群有从库,您也需要在每个从库上执行 PITR。 或者,更简单的方法是删除从库数据目录并重启 patroni,这将从主库重新初始化从库。 我们将在下一个多节点集群示例中介绍这种情况。


多节点示例

现在让我们以三节点 pg-test 集群作为 PITR 示例。

7 - 克隆数据库集群

如何利用 PITR 创建一个新的 PostgreSQL 集群,并恢复到指定时间点?

快速上手

  • 利用 Standby Cluster 创建现有集群的在线副本
  • 利用 PITR 创建现有集群的时间点快照
  • 在 PITR 完成后进行善后,确保新集群的备份流程正常运行

您可以使用 PG PITR 机制克隆整个数据库集群。

重置一个集群的状态

您也可以考虑创建一个全新的空集群,然后利用 PITR,将其重置为 pg-meta 集群的特定状态。

利用这种技术,您可以将现有集群 pg-meta 的任意时间点(备份保留期内)状态克隆到一个新的集群中。

我们依然以 Pigsty 4 节点沙箱环境为例,使用以下命令将 pg-test 集群重置为 pg-meta 集群的最新状态:

./pgsql-pitr.yml -l pg-test -e '{"pg_pitr": { "cluster": "pg-meta" }}'

PITR 善后工作

当你使用 PITR 恢复一个集群后,这个新集群本身的 PITR 功能是被禁用的。 因为如果它也尝试去生成备份,归档 WAL,有可能会写脏数据之前集群的备份仓库。

因此,当你确认这个 PITR 恢复出来的新集群状态符合预期后,你需要执行以下善后工作。

  • 升级备份仓库 Stanza,允许它接纳来自不同集群的新备份(仅当从别的集群恢复时)。
  • 启用 archive_mode,允许新集群归档 WAL 日志(需要重启集群)
  • 执行一个新的全量备份,确保新集群的数据被纳入(可选,也可以等 crontab 定时执行)
pb stanza-upgrade
psql -c 'ALTER SYSTEM RESET archive_mode;'
pg-backup full

通过这些操作,你的新集群将从第一次全量备份开始时,拥有自己的备份历史。 如果你跳过这些步骤,新集群本身的备份将无法进行,WAL 归档也不会生效。 意味着你将无法对新集群执行任何备份或 PITR 操作。

不善后的后果

假设您在 pg-test 集群上执行了 PITR 恢复,使用了另外一个集群 pg-meta 的数据,但没有进行善后工作。

那么在下一次例行备份的时候,你会看到下面的错误:

postgres@pg-test-1:~$ pb backup
2025-12-27 10:20:29.336 P00   INFO: backup command begin 2.57.0: --annotation=pg_cluster=pg-test --compress-type=lz4 --delta --exec-id=21034-171fb30b --expire-auto --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --pg1-path=/pg/data --pg1-port=5432 --repo1-block --repo1-bundle --repo1-bundle-limit=20MiB --repo1-bundle-size=128MiB --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/pgbackrest --repo1-retention-full=14 --repo1-retention-full-type=time --repo1-s3-bucket=pgsql --repo1-s3-endpoint=sss.pigsty --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-ca-file=/etc/pki/ca.crt --repo1-storage-port=9000 --repo1-type=s3 --stanza=pg-test --start-fast
2025-12-27 10:20:29.357 P00  ERROR: [051]: PostgreSQL version 18, system-id 7588470953413201282 do not match stanza version 18, system-id 7588470974940466058
                                    HINT: is this the correct stanza?
2025-12-27 10:20:29.357 P00   INFO: backup command end: aborted with exception [051]
postgres@pg-test-1:~$

WAL 日志归档被 pgBackrest 关闭了,因此也不会有 WAL 归档。

克隆一个新集群

例如,假设您有一个集群 pg-meta,现在你想要从 pg-meta 克隆一个 pg-meta2 的新集群。

您可以考虑使用 备份集群 的方式创建一个新的集群 pg-meta2

pgBackrest 支持增量备份/还原,因此如果您已经通过物理复制拉取了 pg-meta 的数据,通常增量 PITR 还原会非常快。


pb stop --force
pb stanza-delete --force
pb start
pb stanza-create
./pgsql-rm.yml -t pg_backup -l pg-test -e pg_rm_backup=true
./pgsql.yml    -t pg_backup -l pg-test

如果您想要将 pg-test 集群重置为 pg-meta 集群在 2025 年 12 月 26 日 15:30 的状态,可以使用以下命令:

./pgsql-pitr.yml -l pg-test -e '{"pg_pitr": { "cluster": "pg-meta", "time": "2025-12-27 17:50:00+08" ,archive: true }}'

当然,您也可以直接创建一个全新的集群,然后使用 pgsql-pitr.yml 剧本从 pg-meta 恢复数据到新集群 pg-meta2 并顶替新集群的数据目录。

使用这种技术,您不仅可以克隆 pg-meta 集群的最新状态,还可以克隆到任意时间点,例如:

8 - 实例恢复

在同一台机器上克隆实例并执行时间点恢复

Pigsty 提供了两个实用脚本,用于在同一台机器上快速克隆实例并执行时间点恢复:

  • pg-fork:在同一台机器上快速克隆一个新的 PostgreSQL 实例
  • pg-pitr:使用 pgbackrest 手动执行时间点恢复

这两个脚本可以配合使用:先用 pg-fork 克隆实例,再用 pg-pitr 将克隆实例恢复到指定时间点。


pg-fork

pg-fork 可以在同一台机器上快速克隆一个新的 PostgreSQL 实例。

快速上手

使用 postgres 用户(dbsu)执行以下命令,即可创建一个新的实例:

pg-fork 1                         # 从 /pg/data 克隆到 /pg/data1,端口 15432
pg-fork 2 -d /pg/data1            # 从 /pg/data1 克隆到 /pg/data2,端口 25432
pg-fork 3 -D /tmp/test -P 5555    # 克隆到自定义目录和端口

克隆完成后,可以启动并访问新实例:

pg_ctl -D /pg/data1 start         # 启动克隆实例
psql -p 15432                     # 连接克隆实例

命令语法

pg-fork <FORK_ID> [options]

必填参数:

参数说明
<FORK_ID>克隆实例编号(1-9),决定默认端口和数据目录

可选参数:

参数说明默认值
-d, --data <datadir>源实例数据目录/pg/data$PG_DATA
-D, --dst <dst_dir>目标数据目录/pg/data<FORK_ID>
-p, --port <port>源实例端口5432$PG_PORT
-P, --dst-port <port>目标实例端口<FORK_ID>5432
-s, --skip跳过备份 API,使用冷拷贝模式-
-y, --yes跳过确认提示-
-h, --help显示帮助信息-

使用示例

# 从默认实例克隆到 /pg/data1,端口 15432
pg-fork 1

# 从默认实例克隆到 /pg/data2,端口 25432
pg-fork 2
# 从端口 5433 的实例克隆
pg-fork 1 -p 5433

# 使用环境变量指定源端口
PG_PORT=5433 pg-fork 1
# 从 /pg/data1 克隆到 /pg/data2
pg-fork 2 -d /pg/data1

# 从 /pg/data2 克隆到 /pg/data3
pg-fork 3 -d /pg/data2
# 克隆到自定义目录和端口
pg-fork 1 -D /tmp/pgtest -P 5555

# 完全自定义
pg-fork 1 -d /pg/data -D /mnt/backup/pgclone -P 6543
# 源实例已停止时使用冷拷贝
pg-fork 1 -s

# 跳过确认直接执行
pg-fork 1 -s -y

工作原理

pg-fork 支持两种工作模式:

热备份模式(默认,源实例运行中):

  1. 调用 pg_backup_start() 开始备份
  2. 使用 cp --reflink=auto 拷贝数据目录
  3. 调用 pg_backup_stop() 结束备份
  4. 修改配置文件,避免与源实例冲突

冷拷贝模式(使用 -s 参数或源实例未运行):

  1. 直接使用 cp --reflink=auto 拷贝数据目录
  2. 修改配置文件

克隆后配置

pg-fork 会自动修改克隆实例的以下配置:

配置项修改内容
port改为目标端口(避免冲突)
archive_mode设为 off(避免污染 WAL 归档)
log_directory设为 log(使用数据目录下的日志)
primary_conninfo移除(创建独立实例)
standby.signal移除(创建独立实例)
pg_replslot/*清空(避免复制槽冲突)

典型工作流

# 1. 克隆实例用于测试
pg-fork 1 -y

# 2. 启动克隆实例
pg_ctl -D /pg/data1 start

# 3. 在克隆实例上测试(不影响生产)
psql -p 15432 -c "DROP TABLE important_data;"  # 安全测试

# 4. 测试完成后清理
pg_ctl -D /pg/data1 stop
rm -rf /pg/data1

pg-pitr

pg-pitr 是一个用于手动执行时间点恢复的脚本,基于 pgbackrest。

快速上手

pg-pitr -d                                  # 恢复到最新状态
pg-pitr -i                                  # 恢复到备份完成时间
pg-pitr -t "2025-01-01 12:00:00+08"         # 恢复到指定时间点
pg-pitr -n my-savepoint                     # 恢复到命名恢复点
pg-pitr -l "0/7C82CB8"                      # 恢复到指定 LSN
pg-pitr -x 12345678 -X                      # 恢复到事务之前
pg-pitr -b 20251225-120000F                 # 恢复到指定备份集

命令语法

pg-pitr [options] [recovery_target]

恢复目标(选择一个):

参数说明
-d, --default恢复到 WAL 归档流末尾(最新状态)
-i, --immediate恢复到数据库一致性点(最快恢复)
-t, --time <timestamp>恢复到指定时间点
-n, --name <restore_point>恢复到命名恢复点
-l, --lsn <lsn>恢复到指定 LSN
-x, --xid <xid>恢复到指定事务 ID
-b, --backup <label>恢复到指定备份集

可选参数:

参数说明默认值
-D, --data <path>恢复目标数据目录/pg/data
-s, --stanza <name>pgbackrest stanza 名称自动检测
-X, --exclusive排除目标点(恢复到目标之前)-
-P, --promote恢复后自动提升(默认暂停)-
-c, --check干运行模式,仅打印命令-
-y, --yes跳过确认和倒计时-
-h, --help显示帮助信息-

恢复目标类型

# 恢复到 WAL 归档流末尾(最新状态)
pg-pitr -d

# 这是默认行为,会重放所有可用的 WAL
# 恢复到数据库一致性点
pg-pitr -i

# 最快的恢复方式,不重放额外的 WAL
# 适用于快速验证备份是否可用
# 恢复到指定时间点
pg-pitr -t "2025-01-01 12:00:00+08"

# 使用 UTC 时间
pg-pitr -t "2025-01-01 04:00:00+00"

# 时间格式:YYYY-MM-DD HH:MM:SS[.usec][+/-TZ]
# 恢复到命名恢复点
pg-pitr -n my-savepoint

# 恢复点需要事先使用 pg_create_restore_point() 创建
# SELECT pg_create_restore_point('my-savepoint');
# 恢复到指定 LSN
pg-pitr -l "0/7C82CB8"

# LSN 可以从监控面板或 pg_current_wal_lsn() 获取
# 恢复到指定事务 ID
pg-pitr -x 12345678

# 恢复到事务之前(不包含该事务)
pg-pitr -x 12345678 -X
# 恢复到指定备份集
pg-pitr -b 20251225-120000F

# 查看可用备份集
pgbackrest info

使用示例

恢复到指定时间点:

# 1. 停止 PostgreSQL
pg_ctl -D /pg/data stop -m fast

# 2. 执行 PITR
pg-pitr -t "2025-12-27 10:00:00+08"

# 3. 启动并验证
pg_ctl -D /pg/data start
psql -c "SELECT * FROM important_table;"

# 4. 确认无误后提升
pg_ctl -D /pg/data promote

# 5. 启用归档并执行新备份
psql -c "ALTER SYSTEM SET archive_mode = on;"
pg_ctl -D /pg/data restart
pg-backup full

恢复到克隆实例:

# 1. 克隆实例
pg-fork 1 -y

# 2. 在克隆实例上执行 PITR
pg-pitr -D /pg/data1 -t "2025-12-27 10:00:00+08"

# 3. 启动克隆实例验证
pg_ctl -D /pg/data1 start
psql -p 15432

干运行模式:

# 仅打印命令,不执行
pg-pitr -t "2025-12-27 10:00:00+08" -c

# 输出示例:
# Command:
#   pgbackrest --stanza=pg-meta --delta --force --type=time --target="2025-12-27 10:00:00+08" restore

恢复后处理

恢复完成后,实例会处于恢复暂停状态(除非使用 -P 参数)。您需要:

  1. 启动实例pg_ctl -D /pg/data start
  2. 验证数据:检查数据是否符合预期
  3. 提升实例pg_ctl -D /pg/data promote
  4. 启用归档psql -c "ALTER SYSTEM SET archive_mode = on;"
  5. 重启实例pg_ctl -D /pg/data restart
  6. 执行备份pg-backup full

组合使用

pg-forkpg-pitr 可以组合使用,实现安全的 PITR 验证流程:

# 1. 克隆当前实例
pg-fork 1 -y

# 2. 在克隆实例上执行 PITR(不影响生产)
pg-pitr -D /pg/data1 -t "2025-12-27 10:00:00+08"

# 3. 启动克隆实例
pg_ctl -D /pg/data1 start

# 4. 验证恢复结果
psql -p 15432 -c "SELECT count(*) FROM orders WHERE created_at < '2025-12-27 10:00:00';"

# 5. 确认无误后,可以选择:
#    - 方案A:在生产实例上执行相同的 PITR
#    - 方案B:将克隆实例提升为新的生产实例

# 6. 清理测试实例
pg_ctl -D /pg/data1 stop
rm -rf /pg/data1

注意事项

运行要求

  • 必须以 postgres 用户(或 postgres 组成员)执行
  • pg-pitr 执行前必须停止目标实例的 PostgreSQL
  • pg-fork 热备份模式需要源实例正在运行

文件系统

  • 推荐使用 XFS(启用 reflink)或 Btrfs 文件系统
  • CoW 文件系统上克隆几乎瞬间完成,且不占用额外空间
  • 非 CoW 文件系统会执行完整拷贝,耗时较长

端口规划

FORK_ID默认端口默认数据目录
115432/pg/data1
225432/pg/data2
335432/pg/data3
995432/pg/data9

安全建议

  • 克隆实例仅用于测试和验证,不应长期运行
  • 验证完成后及时清理克隆实例
  • 生产环境 PITR 建议使用 pgsql-pitr.yml 剧本
  • 重要操作前先使用 -c 干运行模式确认命令

原理剖析

有时候,您想要用现有的 PostgreSQL 实例在 同一台机器 上创建一个新的实例 (用于测试,PITR 恢复),可以使用 postgres 用户执行下面的命令:

psql <<EOF
CHECKPOINT;
SELECT pg_backup_start('pgfork', true);
\! rm -rf /pg/data2 && cp -r --reflink=auto /pg/data /pg/data2 && ls -alhd /pg/data2
SELECT * FROM pg_backup_stop(false);
EOF

# 修改配置,避免与现有实例冲突:端口,日志,归档等
sed -i 's/^port.*/port = 5431/' /pg/data2/postgresql.conf;
sed -i 's/^log_destination.*/log_destination = stderr/' /pg/data2/postgresql.conf;
sed -i 's/^archive_mode.*/archive_mode = off/' /pg/data2/postgresql.conf;
rm -rf /pg/data2/postmaster.pid /pg/data2/postmaster.opts
pg_ctl -D /pg/data2 start -l /pg/log/pgfork.log
pg_ctl -D /pg/data2 stop
psql -p 5431  # 访问新实例

上面的命令会创建一个新的数据目录 /pg/data2,它是现有数据目录 /pg/data 的一个完整拷贝。 如果您使用的是 XFS (启用了 reflink COW 特性),那么同磁盘拷贝目录会非常快,通常几百毫秒的常数时间内即可完成。

您在原地拉起新实例前,务必 修改 postgresql.conf 里的 port / archive_mode / log_destination 参数,避免影响现有生产实例等运行。 您可以使用一个没有被占用的端口,例如 5431,并将日志输出到 /pg/log/xxxx.log 避免写脏现有实例的日志文件。

我们建议同时修改 shared_buffers Pigsty 默认情况通常分配 25% 的系统内存给 PostgreSQL 实例, 开启新实例时,会与现有实例争夺内存资源。您可以适当调小,以减小对现有生产实例的影响。

9 - 备份数据库

如何在现有 PostgreSQL 集群中,克隆现有数据库,并利用 xfs 瞬间完成

克隆数据库

你可以通过 template 机制复制一个 PostgreSQL 数据库,但在此期间不允许有任何连接到模版数据库的活动连接。

假设你想要克隆 postgres 数据库,那么必须一次性同时执行下面两条语句。 确保清理掉所有连接到 postgres 数据库的连接后执行 Clone

SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'postgres'; 
CREATE DATABASE pgcopy TEMPLATE postgres STRATEGY FILE_COPY;

瞬间克隆

如果你使用的是 PostgreSQL 18 以上的版本,Pigsty 默认为您设置了 file_copy_method。 该参数允许你以 O(1) (约 200ms)的时间复杂度克隆一个数据库,而不需要复制数据文件。

但是您必须显式使用 FILE_COPY 策略来创建数据库。 CREATE DATABASESTRATEGY 参数自 PostgreSQL 15 引入以来的默认值为 WAL_LOG,你需要显式指定 FILE_COPY 来进行瞬间克隆。

SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'meta';
CREATE DATABASE pgcopy TEMPLATE meta STRATEGY FILE_COPY;

例如,克隆一个 30 GB 的数据库,普通克隆(WAL_LOG)用时 18 秒,而瞬间克隆(FILE_COPY)仅需常数时间 200 毫秒。

但是,您仍然需要确保在克隆期间没有任何连接到模版数据库的活动连接,但这个时间可以非常短暂,因此具有生产环境的实用性。 如果您需要一个新的数据库副本用于测试或开发,瞬间克隆是一个非常好的选择。它并不会引入额外的存储开销,因为它使用了文件系统的 CoW(Copy on Write)机制。

Pigsty v4.0 起,您可以在 pg_databases 参数中使用 strategy: FILE_COPY 来实现瞬间克隆数据库。

    pg-meta:
      hosts:
        10.10.10.10: { pg_seq: 1, pg_role: primary }
      vars:
        pg_cluster: pg-meta
        pg_version: 18
        pg_databases:

          - name: meta

          - name: meta_dev
            template: meta
            strategy: FILE_COPY         # <---- PG 15 引入, PG18 瞬间生效 
            #comment: "meta clone"      # <---- 数据库注释
            #pgbouncer: false           # <---- 不加入 连接池?
            #register_datasource: false # <---- 不加入 Grafana 数据源?        

配置完毕后,使用标准数据库创建 SOP 创建该数据库即可:

bin/pgsql-db pg-meta meta_dev

局限性与注意事项

请注意,这个特性仅在支持的文件系统上可用(xfs,brtfs,zfs,apfs),如果文件系统不支持,PostgreSQL 将会报错失败。 默认情况下,主流操作系统发行版的 xfs 都已经默认启用 reflink=1 选项,因此大多数情况下您不需要担心这个问题。 OpenZFS 需要显式配置才能支持 CoW,但因为存在数据损坏的先例,不建议将此特性用于生产。

如果您使用的 PostgreSQL 版本低于 15,指定 strategy 不会有任何效果。

请不要使用 postgres 数据库作为模版数据库进行克隆,因为管理链接通常会连接到 postgres 数据库,这阻止了克隆操作的进行。 如果您确实需要克隆 postgres 数据库,请你手动连接到其他数据库上后,自行执行 SQL 实现。

在极高并发/吞吐的生产环境中使用瞬间克隆需要谨慎,它需要在克隆窗口(200ms)内清理掉所有连接到模版数据库的连接,否则克隆会失败。