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
3. 配置
| pgBackRest 支持多备份仓库,详情参考:https://pgbackrest.org/user-guide.html#multi-repo |
| 本文演示中,PG数据库和备份仓库均在同一台主机上,备份仓库为本地文件。 |
需要在两两处对进行配置:
| 文件名 | 说明 |
|---|---|
postgresql.conf |
配置 |
pgbackrest.conf |
对 |
在 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;";