PIGSTY

示例

在沙盒环境中根据提示脚本手动执行 PITR

您可以使用 pgsql-pitr 剧本执行 PITR,但在某些情况下,您可能希望手动执行 PITR。 我们将使用带有 MinIO 备份仓库的 4 节点沙盒环境 集群来演示该过程。


初始化沙盒

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

curl https://repo.pigsty.io/get | bash; 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:DBUser.Meta@10.10.10.10:5433/meta
while true; do pgbench -nv -P1 -c4 --rate=64 -T10 postgres://dbuser_meta:DBUser.Meta@10.10.10.10:5433/meta; done
while true; do pgbench -nv -P1 -c4 --rate=64 -T10 postgres://dbuser_meta:DBUser.Meta@10.10.10.10: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)
可选,因为如果 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 --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,这将从主节点重新初始化副本。 我们将在下一个多节点集群示例中介绍这种情况。