---
url: /docs/agentbuff-stack/backup-restore.md
---
# 数据库备份与恢复

## 当前能力与范围

I15 新增完整数据库归档、校验清单与独立空库恢复，已在本地实际 PostgreSQL 和系统 `pg_dump` / `pg_restore` 验收。恢复核对账号、文件元数据、任务、账本、购买与迁移记录，文件对象检查点、独立空桶恢复和私有结果下载也已在受控的本地 R2 绑定验证；**没有接入真实云端服务或自动为任意桶执行备份**。正常预览仍为已授权的 0012，验收使用一次性数据库，不升级或覆盖主库。

原 `bun db:export` 默认只导出结构，适合查看 SQL，不能当作完整备份。本教程使用新增的 `db:backup` / `db:restore`。需要先安装 PostgreSQL 客户端工具并加入 PATH；备份客户端不能比源数据库更旧，具体版本兼容参见[官方备份说明](https://www.postgresql.org/docs/17/app-pgdump.html)。核对日期：2026-10-11。

## 1. 选择来源并生成归档

开发来源沿用原数据库配置，测试与正式环境只从对应 `.env.<环境>.local` 读取 `DATABASE_URL`。环境文件必须存在并定义来源连接，不回退到开发或 shell 中旧地址。建议使用有完整读取权限的非池化直连；Hyperdrive 不是备份连接。

```sh
bun db:backup
bun db:backup:staging
bun db:backup:production
```

选择一种命令对应自己的环境，不依次备份所有环境。`--output` 可指定本机私有目录，默认是 `db/backups`。当前执行这些命令仍是人工动作；持续集成、发布脚本和定时维护不会自动备份或恢复，也没有新增云端上传。

成功后输出目录 `snapshot-<唯一编号>`，内含：

| 文件 | 内容与边界 |
| --- | --- |
| `database.dump` | 自定义格式完整数据库归档，包含数据、表结构、约束、序列及迁移记录；不包含 R2、队列消息、Worker Secrets 或集群角色 |
| `manifest.json` | 格式版本、来源指纹、环境、时间、数据库版本、归档大小 / SHA-256，以及每张表的行数 / 数据摘要；不保存来源连接、密码、客户正文或对象路径 |

归档目录权限为 700，两个文件为 600。归档本身含用户数据和认证记录，保持私有存储；新站生成器排除备份目录、归档文件、已完成快照与临时目录。文件校验用于发现损坏，不证明归档来源可信；恢复自己的已确认归档。

备份在只读可重复读事务中导出快照，数据摘要与 `pg_dump` 使用同一快照，避免备份期间的积分 / 账本更新落入不同时间点。普通表、分区表与物化视图逐行排序、流式计算摘要；这需要额外扫描和排序，规模较大时应选择维护窗口并评估磁盘、执行时间和数据库负载。

没有按表筛选、增量或自动保留策略；人工清理命令见下文。失败归档不会发布为完成目录，并清理本次临时目录；进程被强制结束后可能留下 `.partial-*`，恢复命令不接受缺少完整清单的目录。单数据库快照不等于数据库、文件对象与外部支付共同事务。[官方说明](https://www.postgresql.org/docs/17/app-pgdump.html)也区分数据库归档与集群级角色等对象。

## 2. 准备独立空目标

先准备独立恢复数据库及权限。不要把来源或正在提供服务的数据库作为目标；已有表、函数、类型、非默认扩展、对象或自定义模式都会拒绝恢复。命令不删除、清空或创建目标数据库。

根目录创建 `.env.restore.local`，只写目标连接：

```dotenv
RESTORE_DATABASE_URL=postgresql://restore_role:自己的密码@目标主机:5432/独立空库
```

文件不提交。恢复命令只读取这个文件，忽略 shell 中的 `RESTORE_DATABASE_URL`、来源 `DATABASE_URL` 和开发环境回退。不要将完整连接写入命令参数；PostgreSQL 客户端接收连接环境字段，密码不进入参数列表，原始数据库错误也不输出。

## 3. 恢复并核对

```sh
bun db:restore --from backups/snapshot-自己的编号
```

命令从数据库工作区解析相对路径，也可直接使用本机归档的绝对路径。按照备份命令实际输出的目录选择，避免猜测路径。

恢复顺序为归档 / 清单校验 → 来源与目标区分 → 空库检查 → 单事务恢复 → 表清单、行数与数据摘要核对。不会使用 `--clean` 或 `--create`。单事务执行依据[官方恢复说明](https://www.postgresql.org/docs/17/app-pgrestore.html)；恢复完成后的数据核对另行执行，若核对失败，目标仍需隔离，不标为验收完成。

恢复不复制原对象所有者及授权，避免把原角色安排直接带入新环境。之后需按自己的角色策略重新授予应用所需权限，由实际恢复 / 迁移所有者执行 `db/scripts/grant-app-role.sql`，见[数据库角色与凭据](./database-roles)；不能把恢复成功当作业务角色已具备权限。

## 4. 接入服务前检查

先保持目标隔离，不直接启动任务消费者、邮件发送或支付维护。

1. 使用归档对应的模板版本核对迁移记录；新版本的增量迁移需另行检查，恢复命令不会自动升级。
2. 按文件元数据核对私有对象是否存在、长度与内容摘要是否对应；同步准备对象备份 / 恢复。数据库里的 `ready` 只说明历史状态，不证明目标桶现在有文件。
3. 核对新环境的 Hyperdrive、R2、队列、域名、邮件地址、供应商模式和 Secrets；这些不在数据库归档中。
4. 对恢复时间点之后的购买、退款、通知、计佣等外部事实另行对账，再决定何时恢复消费与定时维护，避免向用户报告过期结果。
5. 在独立测试环境走注册、上传、处理、预览与下载，核对私有访问、账本、失败返还和恢复后的重复请求。

如需数据库与文件对象共同恢复，应先确定停写 / 停消费安排及共同检查时间，再备份数据库和对象；本阶段未提供原子跨服务快照、自动对象复制或云端故障切换。真实服务、备份保存 / 删除策略、恢复耗时与费用继续按[部署验收](./deployment)记录。

## 文件对象检查点

新增 `scripts/lib/file-backups.ts` 的对象归档 / 恢复函数，当前用于受控 R2 绑定与实际本地验收。它们没有新增公开下载端点、后台任意桶复制入口、R2 云端凭据或自动运行计划；`db:backup` / `db:restore` 仍只处理数据库。

操作顺序是先停止来源写入、任务消费和清理，再完成数据库归档；保持来源暂停，将归档恢复到隔离空库并通过表数据校验，从这份冻结数据中取得全部 `ready` 文件引用。按冻结引用读取源对象，验证长度、SHA-256 和所需元数据，全部通过才发布对象检查点。这不是数据库和 R2 的原子事务，暂停来源仍是操作者的前置安排，函数不会替操作者停服务。

对象目录位于数据库归档的私有快照目录内，使用独立唯一编号：

| 内容 | 用途 |
| --- | --- |
| `file-manifest.json` | 关联数据库归档 SHA-256、冻结文件清单摘要、捕获时间、文件编号、对象键、长度、内容摘要与类型；它含私有对象键，需保持私有 |
| `<文件编号>.blob` | 原始文件内容，文件名从有效编号派生，避免把对象路径当成本机路径 |

归档目录为 700、文件为 600。当前单文件上限与模板一致为 16 MiB，引用最多 100,000 项、清单最多 64 MiB；按文件逐个读取，不将整个文件集合载入内存。数据库归档中的 `externalObjectsIncluded: false` 保持原义，文件内容在独立对象目录中，不改写旧数据库清单。

对象恢复要求文件检查点与指定数据库归档、目标冻结引用全部一致。先检查每个本地文件，缺失、损坏或换错清单时不写目标；再要求独立空桶，条件写入避免覆盖并发出现的对象，逐个读回核对。必要的类型、文件编号和摘要元数据恢复后，原私有文件接口仍按本站账号与所有权读取。

目标已存在对象时拒绝，不清空或覆盖。写入期间失败可能留下本次部分对象，目标需继续隔离；没有自动删除、断点修复或跨服务回退。该归档仅覆盖冻结的 `ready` 文件，不复制孤立对象、未就绪对象或队列消息；历史任务与外部付款状态仍按原维护和对账规则处理。

条件写入与对象元数据依据[Cloudflare R2 官方说明](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/)，核对日期 2026-10-11；实际云端与 S3 传输、对象保留 / 删除策略和运行成本继续待验。

## 本机归档保留与清理

`db:backup:prune` 只维护指定本机目录，不读取数据库环境配置、不连接数据库或云服务，也不删除正在使用的 R2 对象。暂停该目录的备份、恢复、复制与其他清理操作，先查看预览：

```sh
bun db:backup:prune --directory backups --keep 7 --older-than 30
```

路径从数据库工作区解析，可使用绝对路径。参数均需明确指定；`--keep` 至少为 1，`--older-than` 为正整数天数，以运行时间前的连续 24 小时计算。上例是用法，不是适用于所有产品的保留承诺。

规则按来源指纹和环境分别计算：始终保留每组最新的 7 份校验通过归档，以及完成时间在最近 30 天内的所有归档；只有同时超出数量且严格早于年龄界线的归档才列为 `DELETE`，恰好位于界线的仍保留。来源指纹沿用数据库归档的主机 / 端口 / 数据库，不包含密码。`KEEP`、`DELETE`、`SKIP` 分开显示，默认不修改任何文件。

只识别该目录直接下的 `snapshot-<标准唯一编号>`。逐一核对数据库清单、大小及完整 SHA-256，完成时间倒置或位于未来、损坏 / 缺失归档、符号链接、目录中的未知文件或未完成对象目录均跳过，不作为有效保留名额。其他目录与 `.partial-*` 留在原处，不打印这些未知条目的名称。

确认预览和保留安排后，再执行同样参数并追加 `--apply`：

```sh
bun db:backup:prune --directory backups --keep 7 --older-than 30 --apply
```

执行时重新校验保留与待删除的全组归档和文件列表；发现变化则在开始删除前停止。删除以整个快照目录为单位，同目录下配套的私有对象检查点也一起删除，不留下只能恢复数据库的半份本机归档。被保留快照不会被改写；再次运行不重复删除已移除快照。

命令没有跨进程锁，重新校验也不是文件系统事务；其他操作必须保持暂停。删除期间的权限 / 磁盘错误可能已经移除部分快照，命令报告完整移除数后停止，需重新检查；不提供恢复已删除归档或安全擦除介质的承诺。没有定时清理、云端生命周期设置、跨账户副本或远程保留策略。

清理校验不执行 `pg_restore`，也不认证对象检查点内容完整，更不证明支付事实已对账。保留数量不能代替近期实际恢复验收；在决定期限前，先确认至少一份所需版本的完整归档可以在独立目标恢复，并检查配套文件与外部事实。云端保留 / 删除及实际存储费用仍按 I15 继续验收。

## 本地复验

```sh
bun db:validate-backups
```

命令只接受回环 PostgreSQL 来源，创建自己的三个临时数据库，应用完整迁移并保存合成账号、文件、成功任务、积分账本与购买。真实备份期间并发写入，恢复后核对同一快照的全表摘要、积分 / 账本、迁移、序列、约束与外键；验收损坏归档、原库 / 已占用目标拒绝、私有文件权限、环境文件选择以及失败后的临时文件清理。实际恢复命令另在隔离目录执行，不创建正常项目的恢复环境文件。

验收结束删除本次数据库与归档，主预览不变。该命令没有恢复 R2 文件或调用供应商；云端完整恢复与 I16 的两站实际交付仍待验收。

另外运行文件与数据库共同恢复验收：

```sh
bun files:validate-backups
```

命令创建两个自有回环数据库和两个独立本地 Worker / R2 目录，使用实际注册、上传、报价确认、扣积分和队列执行；任务花费 3 积分，成功后从 20 变为 17。归档并恢复数据库及输入 / 结果对象后，验证原用户下载真实结果、匿名和另一用户拒绝，重复原请求 / 定时维护不再次执行或扣费，实际重启仍可下载。受控破坏源对象和归档文件会拒绝完整检查点或在目标写入前停止；实际 R2 条件写入保留已有对象。

文件验收不会修改正常预览配置或数据库，结束删除自己的数据库、对象目录、归档与会话。主预览仍为 0012；这不是任意云桶的备份命令，也不代表真实云端故障切换已验收。

本机保留规则的实际数据库验收：

```sh
bun db:validate-retention
```

创建两个自有回环数据库，源库完整迁移后生成三份真实 PostgreSQL 归档。命令预览不删除，非法策略拒绝；使用模拟的未来策略时钟让归档达到年龄条件，不改写真实捕获时间或归档内容。清理两份旧归档后，实际恢复保留的一份并核对全表摘要、迁移和 22 积分 / 账本；重复清理无新增删除。结束清理本次数据库与目录，正常主库和六项预览保留。这是本地清理验收，不把模拟年龄称为已保存多日的云端备份。
