跳转到主要内容

这是本节的多页打印视图。 .

返回本页常规视图.

备份

备份和时间点恢复

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

策略
    备份脚本、调度、pgbackrest、仓库和管理
管理
    备份策略、磁盘规划、恢复窗口权衡
恢复
    使用 playbook 恢复到特定时间点
示例
    沙箱示例:手动执行恢复
无质保承诺

Pigsty 尽力提供可靠的 PITR 解决方案,但我们不对 PITR 操作导致的数据丢失承担任何责任,请自行承担风险使用。 如需专业支持,请考虑我们的 专业服务


快速开始

步骤 1

    [备份策略](/zh/docs/pgsql/backup/mechanism):使用 Crontab 调度基础备份

步骤 2

    [WAL 归档](/zh/docs/pgsql/backup/policy):持续记录写入活动

步骤 3

    [恢复和还原](/zh/docs/pgsql/backup/restore):从备份和 WAL 归档中恢复
每天凌晨 1 点全量备份
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 - 备份机制

备份脚本、调度、仓库和基础设施

备份可以通过内置 脚本 调用,使用节点 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 --compress-type=lz4 --delta --exec-id=88380-4b22e767 --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-meta --start-fast --type=full
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:57.105 P00   INFO: check archive for prior segment 000000010000000000000005
2025-07-15 01:36:58.403 P00   INFO: execute non-exclusive backup stop and wait for all WAL segments to archive
2025-07-15 01:36:58.421 P00   INFO: backup stop archive = 000000010000000000000006, lsn = 0/6000120
2025-07-15 01:36:58.424 P00   INFO: check archive for segment(s) 000000010000000000000006:000000010000000000000006
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)
2025-07-15 01:36:58.589 P00   INFO: expire command begin 2.54.2: --exec-id=88380-4b22e767 --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --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-meta
2025-07-15 01:36:58.593 P00   INFO: repo1: time-based archive retention not met - archive logs will not be expired
2025-07-15 01:36:58.593 P00   INFO: expire command end: completed successfully (4ms)
$ pgbackrest --stanza=pg-meta --type=diff backup
2025-07-15 01:37:24.952 P00   INFO: backup command begin 2.54.2: --annotation=pg_cluster=pg-meta --compress-type=lz4 --delta --exec-id=88431-1b8ca3e0 --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-meta --start-fast --type=diff
2025-07-15 01:37:24.985 P00   INFO: last backup label = 20250715-013657F, version = 2.54.2
2025-07-15 01:37:24.985 P00   INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
2025-07-15 01:37:25.045 P00   INFO: backup start archive = 000000010000000000000008, lsn = 0/8000028
2025-07-15 01:37:25.045 P00   INFO: check archive for prior segment 000000010000000000000007
2025-07-15 01:37:26.204 P00   INFO: execute non-exclusive backup stop and wait for all WAL segments to archive
2025-07-15 01:37:26.220 P00   INFO: backup stop archive = 000000010000000000000008, lsn = 0/8000158
2025-07-15 01:37:26.223 P00   INFO: check archive for segment(s) 000000010000000000000008:000000010000000000000008
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)
2025-07-15 01:37:26.381 P00   INFO: expire command begin 2.54.2: --exec-id=88431-1b8ca3e0 --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --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-meta
2025-07-15 01:37:26.386 P00   INFO: repo1: time-based archive retention not met - archive logs will not be expired
2025-07-15 01:37:26.386 P00   INFO: expire command end: completed successfully (5ms)
$ pgbackrest --stanza=pg-meta --type=incr backup
2025-07-15 01:37:30.305 P00   INFO: backup command begin 2.54.2: --annotation=pg_cluster=pg-meta --compress-type=lz4 --delta --exec-id=88449-eba235f7 --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-meta --start-fast --type=incr
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:30.337 P00   INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
2025-07-15 01:37:30.383 P00   INFO: backup start archive = 000000010000000000000009, lsn = 0/9000028
2025-07-15 01:37:30.383 P00   INFO: check archive for segment 000000010000000000000009
2025-07-15 01:37:31.191 P00   INFO: execute non-exclusive backup stop and wait for all WAL segments to archive
2025-07-15 01:37:31.230 P00   INFO: backup stop archive = 00000001000000000000000A, lsn = 0/A000050
2025-07-15 01:37:31.232 P00   INFO: check archive for segment(s) 000000010000000000000009:00000001000000000000000A
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)
2025-07-15 01:37:31.403 P00   INFO: expire command begin 2.54.2: --exec-id=88449-eba235f7 --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --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-meta
2025-07-15 01:37:31.409 P00   INFO: repo1: time-based archive retention not met - archive logs will not be expired
2025-07-15 01:37:31.409 P00   INFO: expire command end: completed successfully (6ms)
$ 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-013441F
            timestamp start/stop: 2025-07-15 01:34:41+00 / 2025-07-15 01:34:43+00
            wal start/stop: 000000010000000000000004 / 000000010000000000000004
            database size: 43.9MB, database backup size: 43.9MB
            repo1: backup size: 8.3MB

        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
            wal start/stop: 000000010000000000000008 / 000000010000000000000008
            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
            wal start/stop: 000000010000000000000009 / 00000001000000000000000A
            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:

alias
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
script
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

每日凌晨1点全量备份
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。

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

pgbackrest

以下是 Pigsty 对 pgbackrest 的设置详情:

文件系统层次结构

  • 二进制文件:/usr/bin/pgbackrest,来自 PGDG 的 pgbackrest 包,在组别名 pgsql-common 中。
  • 配置:/etc/pgbackrest,主配置是 /etc/pgbackrest/pgbackrest.conf
  • 日志:/pg/log/pgbackrest/*,由 pgbackrest_log_dir 控制
  • 临时文件:/pg/spool 用作 pgbackrest 的临时缓冲目录
  • 数据:如果选择默认的 local 文件系统备份仓库,则使用 /pg/backup

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

监控

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

初始备份

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


管理

启用备份

如果您的数据库集群是在 pgbackrest_enable 设置为 true 的情况下创建的,备份将自动启用。

如果是在 false 值下创建的,您可以使用以下命令启用 pgbackrest 组件:

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

移除备份

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

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # 保持备份完整
./pgsql-rm.yml -t pg_backup            # 仅移除备份

使用 pg_backup 子任务仅移除备份,并使用 pg_rm_backup 参数保留备份。

如果您的备份仓库被锁定(例如,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     Backup source URL, optional, "postgres:///" by default, if password is required, it should be given in url, ENV or .pgpass
-d, --dst, --dir     Where to put backup files, "/pg/backup" by default
-f, --file           Overwrite default backup filename, "backup_${tag}_${date}.tar.lz4"
-r, --remove         .lz4 Files mtime before n minutes ago will be removed, default is 1200 (20hour)
-t, --tag            Backup file tag, if not set, target cluster_name or local ip address will be used. Also used as part of DEFAULT filename
-k, --key            Encryption key when --encrypt is specified, default key is ${tag}
-u, --upload         Upload backup files to cloud storage, (need your own implementation)
-e, --encryption     Encrypt with RC4 using OpenSSL, if not key is specified, tag is used as key
-h, --help           Print this message
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] #====== BINARY
[2025-07-13 06:16:05][DEBUG] [INIT] pg_basebackup     :   /usr/pgsql/bin/pg_basebackup
[2025-07-13 06:16:05][DEBUG] [INIT] openssl           :   /usr/bin/openssl
[2025-07-13 06:16:05][DEBUG] [INIT] #====== PARAMETER
[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][DEBUG] [INIT] tag       (-t)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] key       (-k)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] encrypt   (-e)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] upload    (-u)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] remove    (-r)    :   -mmin +1200
[2025-07-13 06:16:05][INFO] [LOCK] acquire lock @ /tmp/backup.lock
[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
[2025-07-13 06:16:05][INFO] [BKUP] backup in normal mode
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] [RMBK] remove local obsolete backup: 1200
[2025-07-13 06:16:06][INFO] [BKUP] find obsolete backups: find /pg/backup/ -maxdepth 1 -type f -mmin +1200 -name 'backup*.lz4'
[2025-07-13 06:16:06][WARN] [BKUP] remove obsolete backups:
[2025-07-13 06:16:06][INFO] [RMBK] remove old backup complete
[2025-07-13 06:16:06][INFO] [LOCK] release lock @ /tmp/backup.lock
[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

2 - 备份仓库

PostgreSQL 的备份存储仓库

您可以通过指定 pgbackrest_repo 参数来配置存储备份的位置。 您可以在那里定义多个仓库,Pigsty 将根据 pgbackrest_method 的值选择它。

默认仓库

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

  • local默认,使用本地 /pg/backup 目录(软链接指向 pg_fs_backup/data/backups
  • minio:使用 SNSD 1 节点 MinIO 集群(由 pigsty 支持,但默认未启用)
pgbackrest_method: local          # 选择备份仓库方法,`local` 或 `minio` 或任何其他用户定义的仓库
pgbackrest_repo:                  # pgbackrest 仓库:https://pgbackrest.org/configuration.html#section-repository
  local:                          # 使用本地 posix fs 的默认 pgbackrest 仓库
    path: /pg/backup              # 本地备份目录,默认为 `/pg/backup`
    retention_full_type: count    # 按数量保留全量备份
    retention_full: 2             # 使用本地 fs 仓库时保留 2 个,最多 3 个全量备份
  minio:                          # pgbackrest 的可选 minio 仓库
    type: s3                      # minio 兼容 s3,所以使用 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     # 在 minio 仓库上按时间保留全量备份
    retention_full: 14            # 保留最后 14 天的全量备份

保留策略

如果您每天备份而不删除它们,备份仓库将越来越大并占满您的磁盘空间。 您需要定义保留策略以仅保留有限数量的备份。

默认备份策略在 pgbackrest_repo 参数中定义,根据需要更改它们。

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

空间规划

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

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

这为您提供至少最后 24 小时的保证恢复窗口。详情请查看备份策略


仓库替代方案

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


仓库版本控制

您甚至可以指定仓库目标时间以获取对象存储的快照。

您可以通过在 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     # 在 minio 仓库上按时间保留全量备份
    retention_full: 14            # 保留最后 14 天的全量备份

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

管理备份

启用备份

如果您的数据库集群是在 pgbackrest_enable 设置为 true 的情况下创建的,备份将自动启用。

如果是在 false 值下创建的,您可以使用以下命令启用 pgbackrest 组件:

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

移除备份

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

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # 保持备份完整
./pgsql-rm.yml -t pg_backup            # 仅移除备份

使用 pg_backup 子任务仅移除备份,并使用 pg_rm_backup 参数保留备份。

如果您的备份仓库被锁定(例如,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     Backup source URL, optional, "postgres:///" by default, if password is required, it should be given in url, ENV or .pgpass
-d, --dst, --dir     Where to put backup files, "/pg/backup" by default
-f, --file           Overwrite default backup filename, "backup_${tag}_${date}.tar.lz4"
-r, --remove         .lz4 Files mtime before n minutes ago will be removed, default is 1200 (20hour)
-t, --tag            Backup file tag, if not set, target cluster_name or local ip address will be used. Also used as part of DEFAULT filename
-k, --key            Encryption key when --encrypt is specified, default key is ${tag}
-u, --upload         Upload backup files to cloud storage, (need your own implementation)
-e, --encryption     Encrypt with RC4 using OpenSSL, if not key is specified, tag is used as key
-h, --help           Print this message
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] #====== BINARY
[2025-07-13 06:16:05][DEBUG] [INIT] pg_basebackup     :   /usr/pgsql/bin/pg_basebackup
[2025-07-13 06:16:05][DEBUG] [INIT] openssl           :   /usr/bin/openssl
[2025-07-13 06:16:05][DEBUG] [INIT] #====== PARAMETER
[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][DEBUG] [INIT] tag       (-t)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] key       (-k)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] encrypt   (-e)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] upload    (-u)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] remove    (-r)    :   -mmin +1200
[2025-07-13 06:16:05][INFO] [LOCK] acquire lock @ /tmp/backup.lock
[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
[2025-07-13 06:16:05][INFO] [BKUP] backup in normal mode
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] [RMBK] remove local obsolete backup: 1200
[2025-07-13 06:16:06][INFO] [BKUP] find obsolete backups: find /pg/backup/ -maxdepth 1 -type f -mmin +1200 -name 'backup*.lz4'
[2025-07-13 06:16:06][WARN] [BKUP] remove obsolete backups:
[2025-07-13 06:16:06][INFO] [RMBK] remove old backup complete
[2025-07-13 06:16:06][INFO] [LOCK] release lock @ /tmp/backup.lock
[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 - 备份策略

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

何时备份

第一个问题是何时备份您的数据库——在备份频率和恢复时间之间做权衡。 由于您需要回放 WAL 日志到从上次备份以来的恢复目标, 备份越频繁,需要回放的 WAL 日志就越少,恢复就越快。

每日全量备份

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

每日凌晨1点全量备份
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 小时的恢复窗口。

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

它将消耗数据库大小的 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:                          # pgbackrest 的可选 minio 仓库
    type: s3                      # minio 兼容 s3,所以使用 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     # 在 minio 仓库上按时间保留全量备份
    retention_full: 14            # 保留最近 14 天的全量备份

当使用内置的 minio 文件系统备份仓库时,它提供有保证的 1 周 PITR 窗口。

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


备份位置

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

  • local默认,使用本地 /pg/backup 目录(软链接指向 pg_fs_backup/data/backups
  • minio:使用 SNSD 1 节点 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:                          # pgbackrest 的可选 minio 仓库
    type: s3                      # minio 兼容 s3,所以使用 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     # 在 minio 仓库上按时间保留全量备份
    retention_full: 14            # 保留最近 14 天的全量备份

4 - 备份管理

管理备份仓库和备份

启用备份

如果您的数据库集群是在 pgbackrest_enable 设置为 true 的情况下创建的,备份将自动启用。

如果是在 false 值下创建的,您可以使用以下命令启用 pgbackrest 组件:

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

移除备份

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

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # 保持备份完整
./pgsql-rm.yml -t pg_backup            # 仅移除备份

使用 pg_backup 子任务仅移除备份,并使用 pg_rm_backup 参数保留备份。

如果您的备份仓库被锁定(例如,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     Backup source URL, optional, "postgres:///" by default, if password is required, it should be given in url, ENV or .pgpass
-d, --dst, --dir     Where to put backup files, "/pg/backup" by default
-f, --file           Overwrite default backup filename, "backup_${tag}_${date}.tar.lz4"
-r, --remove         .lz4 Files mtime before n minutes ago will be removed, default is 1200 (20hour)
-t, --tag            Backup file tag, if not set, target cluster_name or local ip address will be used. Also used as part of DEFAULT filename
-k, --key            Encryption key when --encrypt is specified, default key is ${tag}
-u, --upload         Upload backup files to cloud storage, (need your own implementation)
-e, --encryption     Encrypt with RC4 using OpenSSL, if not key is specified, tag is used as key
-h, --help           Print this message
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] #====== BINARY
[2025-07-13 06:16:05][DEBUG] [INIT] pg_basebackup     :   /usr/pgsql/bin/pg_basebackup
[2025-07-13 06:16:05][DEBUG] [INIT] openssl           :   /usr/bin/openssl
[2025-07-13 06:16:05][DEBUG] [INIT] #====== PARAMETER
[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][DEBUG] [INIT] tag       (-t)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] key       (-k)    :   pg-meta
[2025-07-13 06:16:05][DEBUG] [INIT] encrypt   (-e)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] upload    (-u)    :   false
[2025-07-13 06:16:05][DEBUG] [INIT] remove    (-r)    :   -mmin +1200
[2025-07-13 06:16:05][INFO] [LOCK] acquire lock @ /tmp/backup.lock
[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
[2025-07-13 06:16:05][INFO] [BKUP] backup in normal mode
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] [RMBK] remove local obsolete backup: 1200
[2025-07-13 06:16:06][INFO] [BKUP] find obsolete backups: find /pg/backup/ -maxdepth 1 -type f -mmin +1200 -name 'backup*.lz4'
[2025-07-13 06:16:06][WARN] [BKUP] remove obsolete backups:
[2025-07-13 06:16:06][INFO] [RMBK] remove old backup complete
[2025-07-13 06:16:06][INFO] [LOCK] release lock @ /tmp/backup.lock
[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-pitr 提示脚本进行 PITR,手动操作,更灵活但更复杂。
  • 剧本方式:使用 pgsql-pitr.yml 剧本进行 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

恢复 PITR

恢复的集群上的 archive_mode 将被禁用,以防止不必要的 WAL 写入。 如果恢复的数据库状态正常,您可以启用 archive_mode 并进行全量备份。

postgres @ pg-meta $
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(日志序列号)点

如果指定了上述任何参数,恢复 type 将相应设置, 否则将设置为 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 -e '{"pg_pitr": { "time": "2025-07-13 10:00: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 -e '{"pg_pitr": { "name": "shit_incoming" }}'

按 XID

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

恢复到事务之前
./pgsql-pitr.yml -e '{"pg_pitr": { "xid": "250000", exclusive: true }}'

您可以从监控仪表板找到确切的事务 ID,或从 CSVLOG 的 TXID 中找到它。

包含 vs 排除

目标参数默认是"包含"的,这意味着恢复将包含目标点。 exclusive 标志将排除那个确切的目标,比如 xid 24999 将是最后一个被重放的事务

这仅适用于 timexidlsn 恢复目标,详情请查看 recovery_target_inclusive

按 LSN

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

恢复到 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 参数:

使用 pg-meta 备份恢复 pg-meta2
./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 HA
./pgsql-pitr.yml -l pg-meta -t pitr     # 运行 pitr 过程
./pgsql-pitr.yml -l pg-meta -t up       # 生成 pgbackrest 配置和恢复脚本
# down                 : # 停止 ha 并关闭 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 并恢复 ha
#   - 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: latest                  # 恢复目标类型:time、xid、name、lsn、immediate、latest
    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 剧本执行 PITR,但在某些情况下,您可能希望手动执行 PITR。 我们将使用带有 MinIO 备份仓库的 4 节点沙盒环境 集群来演示该过程。


初始化沙盒

使用 vagrantterraform 准备 4 节点沙盒环境,然后:

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

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

pigsty-sandbox.jpg

检查备份

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

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

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

/etc/profile.d/pg-alias.sh
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 暂停 Patroni(如果有任何副本)
       $ pg pause <cls>  # 暂停 patroni 自动故障转移
   1.2 关闭 Patroni
       $ pt-stop         # sudo systemctl stop patroni
   1.3 关闭 Postgres
       $ pg-stop         # pg_ctl -D /pg/data stop -m fast

[2. Perform PITR] ===========================================
   2.1 恢复备份
       $ pgbackrest --stanza=pg-meta --type=time --target='2025-07-13 03:03:00+00' restore
   2.2 启动 PG 以重放 WAL
       $ pg-start        # pg_ctl -D /pg/data start
   2.3 验证并提升
     - 如果数据库内容正确,提升它以完成恢复,否则转到 2.1
       $ pg-promote      # pg_ctl -D /pg/data promote

[3. Restore Primary] ===========================================
   3.1 启用归档模式(需要重启)
       $ psql -c 'ALTER SYSTEM SET archive_mode = on;'
   3.1 重启 Postgres 以应用更改
       $ pg-restart      # pg_ctl -D /pg/data restart
   3.3 重启 Patroni
       $ pt-restart      # sudo systemctl restart patroni

[4. Restore Cluster] ===========================================
   4.1 重新初始化所有**副本**(如果有)
       - 4.1.1 选项 1:使用相同的 pgbackrest 命令恢复副本(需要中央备份仓库)
           $ pgbackrest --stanza=pg-meta --type=time --target='2025-07-13 03:03:00+00' restore
       - 4.1.2 选项 2:清除副本数据目录并重启 patroni(可能需要很长时间恢复)
           $ rm -rf /pg/data/*; pt-restart
       - 4.1.3 选项 3:使用 patroni 重新初始化,如果主 lsn < 副本 lsn 可能会失败
           $ pg reinit pg-meta
   4.2 恢复 Patroni
       $ pg resume pg-meta
   4.3 全量备份(可选)
       $ pg-backup full      # 建议在 PITR 后进行新的全量备份

单节点示例

让我们以简单的单节点 pg-meta 集群为例开始,这比较简单。

关闭数据库

pt-stop         # sudo systemctl stop patroni,关闭 patroni(和 postgres)
$ 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 --web.listen-address=:9630 --log.level=info
postgres  32252      1  0 02:28 ?        Ssl    0:00 /usr/bin/pg_exporter --web.listen-address=:9631 --log.level=info
postgres  32460      1  0 02:28 ?        Ssl    0:00 /usr/bin/pgbackrest_exporter --log.level=info
postgres  35480  35479  0 03:00 pts/2    S      0:00 -bash
postgres  35510  35480  0 03:01 pts/2    S+     0:00 /bin/bash /pg/bin/pg-heartbeat
postgres  37183  37182  0 03:07 pts/4    S      0:00 -bash
postgres  38627  35510  0 03:14 pts/2    S+     0:00 sleep 1

确保本地 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: --archive-mode=off --delta --exec-id=38997-5c07abb3 --log-level-console=info --log-level-file=info --log-path=/pg/log/pgbackrest --pg1-path=/pg/data --process-max=2 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/pgbackrest --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 --spool-path=/pg/spool --stanza=pg-meta --target="2025-07-13 03:03:00+00" --type=time
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.526 P00   INFO: restore global/pg_control (performed last to ensure aborted restores cannot be started)
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 在配置文件中生成。

/pg/data/postgresql.auto.conf
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,这将从主节点重新初始化副本。 我们将在下一个多节点集群示例中介绍这种情况。