运维

备份与灾难恢复

备份什么、如何恢复,以及如何为故障做规划。

ObjectOS 本身是无状态的 —— 运行时可以从容器镜像重建。**备份针对的是 ObjectOS 所 依赖的数据,而不是 ObjectOS 本身。**请围绕这些数据集规划恢复。本页的所有内容同样 适用于 Community 和 Enterprise。

备份什么

资产归属备份策略
业务数据库客户数据库原生备份(托管服务用 PITR);每季度测试一次恢复
编译后的产物(objectstack.json)应用/发布团队把每个已发布的产物存放在不可变存储中;绝不覆盖
密钥基线 —— 首先是 OS_SECRET_KEY客户的密钥管理器托管备份这些值本身;按策略确定轮换周期
对象/文件存储(启用 storage 能力时)客户存储桶版本控制 + 按需跨区域复制
本地存储上的上传文件(OS_STORAGE_LOCAL_ROOT)客户与数据库相同的计划,这样记录及其附件会恢复到同一个时间点
客户自管的身份提供商IdP 厂商厂商原生方式

**没有 OS_SECRET_KEY 的数据库备份是不完整的备份。**每个存储的密钥 —— 加密的 设置、secret 字段、数据源凭据 —— 都用这一个密钥加密。在没有原始密钥的情况下 恢复的数据库会保留这些值,但它们将永远无法读取。在生成密钥的当天就托管备份它, 并把密钥和数据库视为一个备份单元。

一个设置了 OS_DATABASE_URL 和 OS_SECRET_KEY 的容器,其文件系统本身不需要备份: 它的缓存都可以重建 —— 缓存的产物每次读取时都会依据其完整性固定值重新校验,所以 丢失缓存只会多一次获取,绝不会影响正确性。使用默认值运行的单台主机则不同: 它的主目录保存着 SQLite 数据库、上传的文件和自行生成的加密密钥,所以要把它纳入 你的文件备份。

数据库备份

根据驱动选择策略:

驱动推荐做法
PostgreSQL持续 WAL 归档 + 基础备份;时间点恢复。对于小型数据库,pg_dump --format=custom 是可行的逻辑备份
MySQL二进制日志归档 + 完整转储;时间点恢复
MongoDB副本集快照;用 oplog 实现 PITR
SQLite(单节点 / 桌面)WAL 模式 + 通过 VACUUM INTO 或在线备份 API 做热快照;可选用 Litestream 做持续复制 —— 参见下文的 SQLite 部署

无论使用哪种驱动,都要验证:

  • 备份能在客户的 RPO 内完成;
  • 一个全新的数据库可以用同一个产物启动 ObjectOS 并提供服务;
  • 至少每季度端到端地演练一次恢复测试。

SQLite 部署

SQLite 是默认驱动,对于单节点的 ObjectOS 是正当的生产选择 —— 桌面应用、小团队 的内部工具、边缘 / 本地部署的一体机、评估环境。代价是结构性的:SQLite 只允许单个 写入者,所以它不适合多节点 ObjectOS、高并发写入吞吐量或共享数据库的部署。对于 这些场景,请使用 PostgreSQL。

当你确实在生产环境中运行 SQLite 时,让它安全的配置和备份方案是配套的:

运行时配置

  • 运行时让基于文件的 SQLite 保持 WAL 模式(读者不会阻塞写入者;崩溃安全)。这 意味着数据库旁边的 app.db-wal 可能保存着尚未写入 app.db 的已提交写入 —— 在运行时运行期间,绝不要单独复制 app.db。
  • PRAGMA synchronous=NORMAL 是常见的桌面/单节点默认值 —— 搭配 WAL 是安全的, 而且比 FULL 快得多。
  • 把数据库文件放在本地磁盘上。在运行时运行期间,不要把它放在 NFS、SMB 上, 或者放在由 Dropbox / OneDrive / iCloud 同步的文件夹中 —— SQLite 的锁在这些地方 不可靠,这是 SQLite 部署损坏最常见的原因。

热快照(无停机)

在你用 SQLite 的在线备份 API 或 VACUUM INTO 制作快照时,运行时会继续提供服务:

VACUUM INTO '/var/backups/objectos/db-2026-05-27T13-00Z.sqlite';

按与你的 RPO 相匹配的频率调度它(cron、systemd 定时器或进程内调度器)。给每个 快照标上产生它的产物版本,这样你就可以把数据库和产物一起回滚(参见下文的 产物版本管理)。

异地副本

本地快照无法在磁盘损坏后幸存。把每个快照推送到持久存储:

  • 服务器 / 虚拟机:推送到 S3 或任何兼容 S3 的存储桶(你很可能已经为 storage 能力配置了一个)。
  • 桌面应用:把快照写入用户控制的同步文件夹(OneDrive / iCloud / Google Drive)—— 在这里是可以接受的,因为与活动数据库不同,快照文件是已关闭且不可变的。

持续复制(更低的 RPO)

如果要在不放弃 SQLite 的前提下实现一分钟以内的 RPO,就在 ObjectOS 进程旁边运行 Litestream。它把 WAL 流式传输到兼容 S3 的存储,并支持 时间点恢复。当单节点 SQLite 部署需要接近零的数据丢失时,这是推荐的路径。

恢复

停止运行时,用选定的快照替换数据库文件(或者运行 litestream restore),确保旁边 没有残留过期的 -wal 或 -shm 文件,然后针对产生该快照的同一个产物版本启动运行时。

产物版本管理

把已发布的产物视为不可变的。给每个产物标上编译它所用的发布 id(例如 objectstack-2026-05-24.json)。恢复到一个已知良好的业务状态,通常意味着:

  1. 把数据库恢复到选定的时间点。
  2. 把 ObjectOS 重新指向当时在线的产物版本。
  3. 重启运行时。

如果你原地覆盖产物,即使数据库备份完美无缺,你也会失去干净回滚的能力。

RPO/RTO 规划

客户部署的一个可行的起始目标:

类别RPORTO备注
评估 / 演示尽力而为尽力而为SQLite 快照即可
桌面应用 / 小团队单节点≤ 1 小时(快照)或 ≤ 数秒(Litestream)数分钟SQLite + WAL + 定时 VACUUM INTO + 异地副本
单租户生产≤ 15 分钟≤ 1 小时带 PITR 的托管 PostgreSQL + 预热镜像
多租户 / 受监管≤ 5 分钟≤ 30 分钟高可用数据库 + 多可用区 ObjectOS + 经过测试的运行手册

更严格的目标需要 ObjectOS 容器本身之外的平台变更(高可用数据库、多可用区入口、 预热副本)。

值得演练的故障模式

  • **数据库长时间不可用。**从 ObjectStack 17.0 起,这是有约定的行为,而不是只能 期望的事情:只要某个数据驱动没有响应,/api/v1/ready 就返回 503,所以就绪探针 会把 Pod 标记为未就绪。要演练的是恢复 —— 数据库恢复后副本会重新加入,并且 不需要任何重启就能察觉。对象所绑定的数据源如果在启动时无法连接,现在会让启动 失败,所以要在应用副本滚动之前确认你的恢复已经完成。
  • **产物回退。**回滚产物指针;数据不受影响。
  • **密钥轮换。**轮换 OS_AUTH_SECRET 会使所有会话失效。在维护窗口期间进行,或在 各副本之间错开进行。
  • **区域故障。**如果客户要求区域故障转移,业务数据库、密钥管理器和入口都需要跨 区域。ObjectOS 本身可以在任何能获取到镜像的地方运行。

恢复演练

在上线之前,在一台临时主机上演练这一流程,并在任何存储变更之后再演练一次。 一个你从未恢复过的备份只是一种希望,而不是备份。

  1. 准备一台干净的主机,使用与制作备份时相同的镜像版本。

  2. 恢复数据库 —— 用提供商的快照,或者:

    pg_restore --clean --if-exists -d "$OS_DATABASE_URL" backup-2026-07-14.dump
  3. 提供原始的密钥。OS_SECRET_KEY 和 OS_AUTH_SECRET 必须是托管备份的原始值, 绝不能是新生成的值。如果你使用本地存储,也要恢复上传的文件。

  4. 针对同一个产物版本启动,并验证:

    curl -fsS https://app.example.com/api/v1/health
    curl -fsS https://app.example.com/api/v1/ready

    然后端到端地证明恢复成功:用一个真实账户登录,打开一条在备份之前就存在的 记录,并读取一个加密设置的值 —— 这才会用到 OS_SECRET_KEY。

运行手册中要记录什么

  • 每个数据集的备份计划、保留期和值班联系人。
  • 分步的恢复流程(先数据库,再产物,然后启动 ObjectOS)。
  • 用于确认恢复后的数据库一致的验证查询。
  • 同时覆盖 ObjectOS 镜像和产物版本的回滚计划(参见 升级与回滚)。
  • 面向客户可见事件的沟通模板。

企业版额外提供

  • **把上一个镜像摘要与备份保存在一起。**恢复到某个时间点,就是恢复到当时正在运行 的摘要,而在物理隔离的站点上,上一个摘要可能是唯一的副本 —— 参见 物理隔离。
  • **OS_LICENSE_KEY 属于密钥基线。**恢复后的部署会用交给它的许可证启动,而多组织 姿态没有许可证会拒绝启动。
  • **在副本滚动之前完成恢复。**捆绑的技术栈会在任何副本启动之前运行它的一次性迁移, 所以当技术栈启动时,数据库必须已经完整。