# pgBackRest 生产备份方案分析

> 状态：暂缓实施。当前继续使用 `pg_dump -Fc` + OSS manifest 备份方案，待安排维护窗口后再启用 WAL 归档。
>
> 记录日期：2026-08-23

## 结论

PostgreSQL 没有 MySQL binlog 的同名机制，但有等价能力：

```text
基础物理备份 + WAL 归档 = 按时间点恢复(PITR)
```

当前 Compose 是单节点 PostgreSQL。启用 `archive_mode` 和 `wal_level` 通常需要重启 PostgreSQL，因此不能承诺绝对零停机。

预计生产维护窗口：

| 阶段 | 是否停机 | 预计耗时 |
| --- | --- | --- |
| 构建 pgBackRest 镜像 | 否 | 几分钟 |
| 创建 stanza、校验 OSS | 否 | 1～3 分钟 |
| 重启 PostgreSQL 启用 WAL 归档 | 是 | 5～30 秒 |
| backend/web 等待恢复 | 是 | 20～90 秒 |
| 首次全量备份 | 否 | 几分钟到几十分钟 |
| 建议预留维护窗口 | 是 | 2～5 分钟 |

实际时间取决于数据库大小、磁盘恢复速度、容器启动时间和 OSS 网络质量。当前 `pg_dump` 文件约 161 MB，首次物理备份预计不会很大，但仍应按实际 PostgreSQL 数据目录大小估算。

## 当前方案

目前生产使用：

- `pg_dump -Fc` 全库逻辑备份。
- 备份文件上传 OSS。
- manifest 保存大小、SHA-256、迁移记录和创建时间。
- `apps/backend/data` 配置单独打包上传。
- `restore-db.sh --verify` 在临时 PostgreSQL 容器中演练恢复。

该方案可以恢复到某个 dump 的时间点，但不能恢复最近一次 dump 之后的几分钟变更。

pgBackRest 不能替代当前逻辑备份，建议两套并存：

```text
pgBackRest：生产灾难恢复、PITR、分钟级 RPO
pg_dump：逻辑迁移、单表恢复、跨版本恢复、第二套保险
```

## 需要增加的组件

当前 PostgreSQL 镜像没有 pgBackRest，需要构建自定义镜像：

```dockerfile
FROM docker.m.daocloud.io/library/postgres:16-bookworm

RUN apt-get update \
  && apt-get install -y --no-install-recommends pgbackrest ca-certificates \
  && rm -rf /var/lib/apt/lists/*
```

需要增加或挂载：

- `/etc/pgbackrest/pgbackrest.conf`
- `/var/spool/pgbackrest/`
- pgBackRest 日志目录
- OSS 备份凭证
- pgBackRest repository 配置

不能只在正在运行的容器里执行 `apt install pgbackrest`，因为容器重建后会丢失。

## OSS 规划

可以复用当前图片 OSS 桶，但必须使用独立前缀：

```text
backups/postgres/       # 当前 pg_dump
backups/configs/        # 应用配置
backups/pgbackrest/     # 基础备份和 WAL
```

生产更推荐新建独立备份桶，并创建独立 RAM 用户。原因：数据库备份包含订单、用户和后台数据，不能与图片读写权限混用。

建议 OSS 配置：

- 使用 ECS 同地域内网端点，避免公网流量费用。
- 开启版本控制。
- 配置生命周期规则。
- 备份桶保持私有。
- 必要时启用 pgBackRest 仓库加密，并安全保存加密密码。

pgBackRest 使用 OSS S3 兼容接口，最终配置必须通过 `pgbackrest check` 和一次完整备份实测，不能只凭配置文件判断兼容性。

## PostgreSQL 配置

需要启用：

```text
wal_level=replica
archive_mode=on
archive_command=pgbackrest --stanza=order-site archive-push %p
archive_timeout=60
```

说明：

- `wal_level=replica` 通常需要重启。
- `archive_mode=on` 必须重启。
- `archive_command` 可以 reload，但建议统一写入 Compose 配置。
- `archive_timeout=60` 让低写入量时 WAL 最多约 60 秒切换归档一次。
- OSS 不可用时 WAL 会在本地积压，不能手动删除 `pg_wal` 文件。

启用前先检查当前状态：

```bash
docker compose exec postgres psql \
  -U postgres -d order_site \
  -c "SHOW wal_level; SHOW archive_mode; SHOW archive_command;"
```

如果 `archive_mode` 已经是 `on`，后续可能只需要配置 pgBackRest 和 reload；如果是 `off`，需要完整重启 PostgreSQL。

## 计划中的切换步骤

当前暂不执行。正式实施时按以下顺序：

1. 继续保留并执行现有 `bash deploy/backup-db.sh`。
2. 创建独立 OSS 备份桶或规划独立 `backups/pgbackrest/` 前缀。
3. 创建只允许备份前缀的 RAM 用户。
4. 构建包含 pgBackRest 的 PostgreSQL 镜像。
5. 准备 `pgbackrest.conf`，不要把密钥提交到 Git。
6. 在 PostgreSQL 仍在线时执行 `stanza-create`。
7. 选择低峰期停止 backend/web。
8. 重启 PostgreSQL，启用 `archive_mode` 和 `archive_command`。
9. 等待 PostgreSQL 健康检查通过。
10. 执行 `pgbackrest check`。
11. 执行第一次 full backup。
12. 检查 OSS 中是否出现基础备份和 WAL 文件。
13. 在临时 PostgreSQL 或测试服务器执行一次 PITR 恢复。
14. 恢复 backend/web，并抽查后台、订单和图片访问。

不要执行以下命令，避免误删当前数据库：

```bash
docker compose down -v
docker volume rm order_site_postgres_data
```

增加 pgBackRest 只需要替换镜像、挂载配置并重启，不需要删除 `postgres_data` 数据卷。

## 备份策略建议

```text
每周：full 基础备份
每天：diff 差异备份
持续：WAL 归档到 OSS
每天：保留当前 pg_dump 逻辑备份
```

WAL 归档不依赖 cron，由 PostgreSQL 的 `archive_command` 持续触发。需要监控：

- WAL 归档失败次数。
- `/var/lib/postgresql/data/pg_wal` 占用空间。
- OSS 上传延迟和失败率。
- 最近一次 full/diff 备份时间。
- 最近一段 WAL 是否已经出现在 OSS。

## 恢复时间预期

pgBackRest PITR 恢复也不是零停机操作。当前单节点架构下，恢复生产数据库需要停止 backend/web，并替换或恢复 PostgreSQL 数据目录。

恢复耗时主要取决于：

- 基础备份大小。
- WAL 数量。
- 磁盘读写速度。
- WAL 回放量。
- PostgreSQL 启动和健康检查时间。

如果基础备份较小、目标时间接近当前时间，预计几分钟；数据库规模扩大后需要重新压测，不能按当前 161 MB dump 固定估计。

## 后续验收清单

- `pgbackrest stanza-create` 成功。
- `pgbackrest check` 成功。
- 第一次 full backup 成功。
- OSS 中能看到基础备份和 WAL。
- PostgreSQL 重启后 `archive_mode=on`。
- `pg_stat_archiver` 中归档成功数持续增加。
- OSS 暂时不可用时，WAL 会积压但不会静默丢失。
- 临时环境 PITR 到指定时间成功。
- 恢复后的订单、后台用户、迁移记录和图片引用可用。

