pgBackRest

1. 概述

pgBackRest 是一款可靠的 PostgreSQL 备份和恢复解决方案。其核心基于 PostgreSQL 的物理级备份(数据目录的文件)和WAL归档管理机制。特点是项目质量很高:100%测试覆盖、高质量编码、精简依赖。

pgBackRest在 v2.48.0 版本引入了对 pg_control 文件的 CRC 校验,由于 IvorySQL 修改了 pg_control 文件布局,所以需要在 stanza 配置中使用 pg-version-force 选项指定 IvorySQL 对应的 PostgreSQL 版本。
pgBackRest 无需修改即可在 IvorySQL 5.x 下使用,但这仅是一个巧合,实际使用中仍建议增加 pg-version-force 选项。

版本:v2.58.0

开源协议:MIT License

2. 安装

官网推荐从包管理器安装。
源码测试安装环境为 Ubuntu 26.04。

2.1. 依赖

sudo apt update && \
sudo apt-get install python3-setuptools meson gcc \
      libpq-dev libssl-dev libxml2-dev \
      pkg-config liblz4-dev libzstd-dev \
      libbz2-dev libz-dev libyaml-dev \
      libssh2-1-dev

2.2. 源码安装

cd ~
mkdir -p build
wget -q -O - \
       https://github.com/pgbackrest/pgbackrest/archive/release/2.58.0.tar.gz | \
       tar zx -C build
meson setup build/pgbackrest build/pgbackrest-release-2.58.0
ninja -C build/pgbackrest

# 编译完成后,将在 build/src 路径下生成 pgbackrest 可执行文件
./build/src/pgbackrest --version

2.3. 验证安装

pgBackRest本体只有一个可执行文件,使用时通过传入配置文件即可。本文仅作使用说明,无需额外安装步骤。

3. 配置

pgBackRest 支持多备份仓库,详情参考:https://pgbackrest.org/user-guide.html#multi-repo
本文演示中,PG数据库和备份仓库均在同一台主机上,备份仓库为本地文件。

需要在两两处对进行配置:

文件名 说明

postgresql.conf

配置 PostgreSQL ,增加 archive_command ,使能 WAL 备份

pgbackrest.conf

pgBackRest 本身的配置,包括源数据库、备份仓库等的配置

postgresql.conf 末尾附加:

仅为示例,具体操作参考文档后续部分。
port = 6551
ivorysql.port = 6553
listen_addresses = '127.0.0.1'
unix_socket_directories = '/tmp/.pgbr_sock_1000_6551'
archive_mode = on
archive_command = '/path/to/pgbackrest --config=/path/to/pgbackrest.conf --stanza=ivory archive-push %p'
log_min_messages = info

创建 pgbackrest.conf

仅为示例,具体操作参考文档后续部分。
[global]
# 配置了多个repos的话,WAL是全冗余、备份需要手动执行
repo1-path=/tmp/pgbackrest_ivorysql/repo
# 完整备份保留时间
repo1-retention-full=9999
# log文件
log-path=/tmp/pgbackrest_ivorysql/log
# console的loglevel
log-level-console=info
# 写入文件的loglevel
log-level-file=detail
# 不等下一个checkpoint
start-fast=y

[$STANZA]
# 配置多个的pg的情况是primary+replica,编号不代表主备,自动识别
pg1-path=/tmp/pgbackrest_ivorysql/pg1/data
pg1-port=6551
pg1-socket-path='/tmp/.pgbr_sock_1000_6551'
pg-version-force=18

4. 使用

4.1. pgBackRest 命令

# 使用方式
pgbackrest [选项] [命令]

pgBackRest 命令列表:

# 增加或修改备份的注释,注释以JSON形式记录在backup.info文件
annotate

# 备份仓库中获取WAL段,PostgreSQL 通过 restore_command 调用。
# 执行pgbackrest restore 时自动把它写进 postgresql.auto.conf:
# "restore_command = 'pgbackrest --stanza=ivory archive-get %f "%p"'"
archive-get

# 向备份仓库存入WAL段,把 PostgreSQL 刚写完的 WAL 段推送到 pgbackrest 仓库。
# 它和 archive-get 一进一出,构成 PITR 的基础。
# 需要手动写入postgresql.conf: "archive_command = 'pgbackrest --stanza=ivory archive-push %p'"
archive-push

# 备份PG数据库,对整个数据目录做一次一致性备份并写入仓库。
backup

# 检查配置、核对仓库信息、归档链路测试(会执行pg_switch_wal)
check

# 删除超期的备份和不再需要的 WAL 归档
expire

# 显示帮助信息
help

# 获取备份的信息
info

# 用于调试,把仓库里的任意文件取出来写到 stdout 或本地文件。
repo-get

# 通过存储驱动列出仓库里的文件和目录
repo-ls

# 把仓库里的备份还原成可启动的数据目录,并自动配置好后续的 WAL 回放。
restore

# 运行多主机部署时的传输服务(TLS模式):在主机间搬运备份数据。PG和Repo主机都要运行。
server

# pgBackRest server探活命令
server-ping

# 初始化命令:在仓库里为一套集群建立 stanza 的元数据骨架
stanza-create

# 删除stanza
stanza-delete

# 数据库大版本升级后,需要同步升级stanza
stanza-upgrade

# 不启动任何进程/服务,唯一的工作是删掉 stop 命令留下的停止文件,让 pgbackrest 操作恢复运行。
start

# 创建停止文件,backup、archive-push、expire、stanza-create/upgrade 及 remote 请求在启动时检测到该文件即拒绝。
stop

# 检查已存在的备份和 WAL 是否完好
verify

# 获取当前版本
version

pgBackRest 内部命令列表,这些命令不应该在生产场景下使用,可能导致数据损坏:

# 以可读格式转储某次备份的 manifest
manifest

# 往仓库写文件(自动加密),repo-get 的反向
repo-put

# 删仓库文件/目录(--recurse)
repo-rm

4.2. 执行操作

由于操作过程较为复杂,为了便于测试,将命令进行参数化,并构造了辅助函数。下面的命令,请在同一终端回话内执行。

变量准备:

# 根据实际情况修改:IVORY_PG_VERSION_MAJOR、IVORY_BIN、PGBACKREST_BIN,其他可默认
IVORY_PG_VERSION_MAJOR=18
IVORY_BIN="/usr/local/ivorysql/bin"
PGBACKREST_BIN="/home/lct/repos/pgbackrest/build/src/pgbackrest"
TEST_BASE="/tmp/pgbackrest_ivorysql_regress"
PG_PORT="6551"
STANDBY_PORT="6552"
ORA_PORT="6553"
STANDBY_ORA_PORT="6554"
DB_MODE="oracle"
STANZA="ivory"

PG1_DATA="$TEST_BASE/pg1/data"
PG2_DATA="$TEST_BASE/pg2/data"
REPO_PATH="$TEST_BASE/repo"
# unix socket 路径有 107 字节上限,固定放在 /tmp 下的短目录
SOCK_PATH="${SOCK_PATH:-/tmp/.pgbr_sock_$(id -u)_$PG_PORT}"
LOG_PATH="$TEST_BASE/log"
CONF="$TEST_BASE/pgbackrest.conf"
RUN_LOG="$TEST_BASE/regression.log"
TEST_DB="regress"
RESTORE_POINT="pgb_regress_rp"

sql()
{
    local port="$1" db="$2" query="$3"
    "$IVORY_BIN/psql" -h "$SOCK_PATH" -p "$port" -d "$db" -U "$(id -un)" \
        -v ON_ERROR_STOP=1 -Atc "$query" 2>>"$RUN_LOG"
}
switch_wal_and_wait()
{
    local wal i
    # 先产生一条 WAL 记录,避免当前段为空时 pg_switch_wal() 空转(不会触发归档)
    sql "$ORA_PORT" postgres "SELECT pg_create_restore_point('pgb_wal_sync')" >/dev/null || return 1
    wal=$(sql "$ORA_PORT" postgres "SELECT pg_walfile_name(pg_switch_wal())") || return 1
    for i in $(seq 1 60); do
        local archived
        archived=$(sql "$ORA_PORT" postgres "SELECT last_archived_wal FROM pg_stat_archiver")
        [[ -n "$archived" && ! "$archived" < "$wal" ]] && return 0
        sleep 1
    done
    return 1
}

执行前准备:

rm -rf "$SOCK_PATH"
mkdir -p "$PG1_DATA" "$REPO_PATH" "$SOCK_PATH" "$LOG_PATH"
chmod 700 "$PG1_DATA" "$SOCK_PATH"
: >"$RUN_LOG"
export LD_LIBRARY_PATH="$(dirname "$IVORY_BIN")/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

# 确认一下版本
${IVORY_BIN}/pg_ctl --version
${PGBACKREST_BIN} --version

初始化主库及配置:

# 初始化,使用oracle模式
"$IVORY_BIN/initdb" -D "$PG1_DATA" -m "$DB_MODE" -E UTF8 --no-locale -A trust -U "$(id -un)"

# 配置IvorySQL
cat >>"$PG1_DATA/postgresql.conf" <<EOF

# ---- pgbackrest 附加配置 ----
port = $PG_PORT
ivorysql.port = $ORA_PORT
listen_addresses = '127.0.0.1'
unix_socket_directories = '$SOCK_PATH'
archive_mode = on
archive_command = '$PGBACKREST_BIN --config=$CONF --stanza=$STANZA archive-push %p'
log_min_messages = info
EOF

# 配置pgBackRest
cat >"$CONF" <<EOF
[global]
# 配置了多个repos的话,WAL是全冗余、备份需要手动执行
# Path where backups and archive are stored.
repo1-path=$REPO_PATH
# 完整备份保留时间
repo1-retention-full=9999
# log文件
log-path=$LOG_PATH
# console的loglevel
log-level-console=info
# 写入文件的loglevel
log-level-file=detail
# 不等下一个checkpoint
start-fast=y

[$STANZA]
# 配置多个的pg的情况是primary+replica,编号不代表主备,自动识别
pg1-path=$PG1_DATA
pg1-port=$PG_PORT
pg1-socket-path=$SOCK_PATH
pg-version-force=$IVORY_PG_VERSION_MAJOR
EOF
pg-version-force=$IVORY_PG_VERSION_MAJOR 在IvorySQL 5.x 下即使不设置也可以正常使用,但是这是由于一些列巧合导致的,所以仍建议确认这项被设置了。

初始化数据:

# 启动PG
"$IVORY_BIN/pg_ctl" -D "$PG1_DATA" -l "$LOG_PATH/pg1.log" -w start

# 初始化stanza
"$PGBACKREST_BIN" --config="$CONF" --stanza="$STANZA" "stanza-create";

# 检查配置
"$PGBACKREST_BIN" --config="$CONF" --stanza="$STANZA" "check";

# 创建数据
sql "$ORA_PORT" "$TEST_DB" \
  "CREATE TABLE t_ora(id number PRIMARY KEY, name varchar2(100));
  INSERT INTO t_ora SELECT g, 'ora-' || g FROM generate_series(1, 1000) g;";

全量备份:

"$PGBACKREST_BIN" --config="$CONF" --stanza="$STANZA" --type=full backup;

差异备份:

# 增加一千条

sql "$ORA_PORT" "$TEST_DB" \
  "INSERT INTO t_ora SELECT g, 'ora-' || g FROM generate_series(1001, 2000) g;";

# 差异备份
"$PGBACKREST_BIN" --config="$CONF" --stanza="$STANZA" --type=diff backup;

增量备份:

# 增加一千条
sql "$ORA_PORT" "$TEST_DB" \
  "INSERT INTO t_ora SELECT g, 'ora-' || g FROM generate_series(2001, 3000) g;";

# 差异备份
"$PGBACKREST_BIN" --config="$CONF" --stanza="$STANZA" --type=incr backup;

看备份清单:

"$PGBACKREST_BIN" --config="$CONF" --stanza="$STANZA" info;

预期能看到输出:

stanza: ivory
    status: ok
    cipher: none

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

        full backup: 20260707-164512F
            timestamp start/stop: 2026-07-07 16:45:12+08 / 2026-07-07 16:45:13+08
            wal start/stop: 000000010000000000000003 / 000000010000000000000003
            database size: 36.2MB, database backup size: 36.2MB
            repo1: backup set size: 4.8MB, backup size: 4.8MB

        diff backup: 20260707-164512F_20260707-164826D
            timestamp start/stop: 2026-07-07 16:48:26+08 / 2026-07-07 16:48:27+08
            wal start/stop: 000000010000000000000005 / 000000010000000000000005
            database size: 36.3MB, database backup size: 496.3KB
            repo1: backup set size: 4.8MB, backup size: 89.7KB
            backup reference total: 1 full

        incr backup: 20260707-164512F_20260707-165028I
            timestamp start/stop: 2026-07-07 16:50:28+08 / 2026-07-07 16:50:29+08
            wal start/stop: 000000010000000000000007 / 000000010000000000000007
            database size: 36.3MB, database backup size: 272.3KB
            repo1: backup set size: 4.8MB, backup size: 33.9KB
            backup reference total: 1 full, 1 diff

备份仓库完整性校验:

"$PGBACKREST_BIN" --config="$CONF" --stanza="$STANZA" verify;

会看到类似下面的输出:

2026-07-07 16:54:01.819 P00   INFO: verify command begin 2.58.0: --config=/tmp/pgbackrest_ivorysql_regress/pgbackrest.conf --exec-id=3116367-e3fb70bf --log-level-console=info --log-level-file=detail --log-path=/tmp/pgbackrest_ivorysql_regress/log --repo1-path=/tmp/pgbackrest_ivorysql_regress/repo --stanza=ivory
2026-07-07 16:54:02.100 P00   INFO: verify command end: completed successfully (283ms)

全量恢复:

# 保存当前md5,命令输出当前的值,例如:ae3361d0b4e8dadae6f7f7fffbfa54da
sql "$ORA_PORT" "$TEST_DB" \
  "SELECT md5(string_agg(id::text || ':' || name, ',' ORDER BY id)) FROM t_ora;";

# 调用前面的函数,确保WAL归档,这里应该是正确备份时候的操作流程
switch_wal_and_wait

# 停止PG,并清空数据
"$IVORY_BIN/pg_ctl" -D "$PG1_DATA" -w -t 60 -m fast stop
find "$PG1_DATA" -mindepth 1 -delete

# 执行全量恢复
"$PGBACKREST_BIN" --config="$CONF" --stanza="$STANZA" restore;

# 执行恢复后验证,这次的md5输出应该与上面的一致
"$IVORY_BIN/pg_ctl" -D "$PG1_DATA" -l "$LOG_PATH/pg1.log" -w start
sql "$ORA_PORT" "$TEST_DB" \
  "SELECT md5(string_agg(id::text || ':' || name, ',' ORDER BY id)) FROM t_ora;";

delta恢复,由于继续上面操作,md5保持一致:

# 停止PG后模拟损坏情况
"$IVORY_BIN/pg_ctl" -D "$PG1_DATA" -w -t 60 -m fast stop
rm -f "$PG1_DATA/global/pg_control"

# 执行delta恢复
"$PGBACKREST_BIN" --config="$CONF" --stanza="$STANZA" --delta restore;

# 验证,这次的md5输出应该与上面的一致
"$IVORY_BIN/pg_ctl" -D "$PG1_DATA" -l "$LOG_PATH/pg1.log" -w start
sql "$ORA_PORT" "$TEST_DB" \
  "SELECT md5(string_agg(id::text || ':' || name, ',' ORDER BY id)) FROM t_ora;";

恢复点回退,由于继续上面的操作,md5保持一致:

# 创建还原点,输出类似:0/B00EAB8
sql "$ORA_PORT" "$TEST_DB" \
  "SELECT pg_create_restore_point('$RESTORE_POINT');";

# 执行模拟的误操作
sql "$ORA_PORT" "$TEST_DB" \
  "DELETE FROM t_ora WHERE id > 100;";

# 执行恢复
"$IVORY_BIN/pg_ctl" -D "$PG1_DATA" -w -t 60 -m fast stop
"$PGBACKREST_BIN" --config="$CONF" --stanza="$STANZA" \
  --delta --type=name --target="$RESTORE_POINT" \
  --target-action=promote restore;

# 验证,这次的md5输出应该与上面的一致
"$IVORY_BIN/pg_ctl" -D "$PG1_DATA" -l "$LOG_PATH/pg1.log" -w start
sql "$ORA_PORT" "$TEST_DB" \
  "SELECT md5(string_agg(id::text || ':' || name, ',' ORDER BY id)) FROM t_ora;";