上周我擦除了我的生产数据库。我的工作 [Neotoma](https://neotoma.io) 实例在一个命令中从 6,174 个观察值和 3,862 个实体增加到 84 个观察值和 67 个实体。数月的联系、任务、对话、反馈记录、交易和现行规则：消失了。

我把它拿回来了。最终数据库包含 6,296 个观测值和 3,951 个实体，涵盖五周的活动。恢复过程大约需要一个小时。这篇文章是关于它是如何发生的、为什么恢复是可能的，以及关于构建一个你真正可以信任的记忆系统的经验揭示了什么。

## 发生了什么

我正在研究 Neotoma CLI，测试开发工作流程。我运行了一系列命令来重置数据库状态并重新初始化它。目的是清除开发环境中的测试数据。目标是生产数据库。

这个错误很平常。当“NEOTOMA_ENV”设置为生产时，我运行了“neotoma重置”。我以为我的目标是开发人员。当我注意到时，活动数据库已从重新初始化过程中获得了 84 个新观察结果，除此之外没有其他任何观察结果。

## 查找备份

我做的第一件事就是搜索我机器上的每个 Neotoma SQLite 文件。我发现十个副本分散在备份目录、数据文件夹和带时间戳的恢复工件中，这些都是三月初的一次千钧一发的结果。

|源文件|观察|实体|最新活动 |
|---|---|---|---|
| `neotoma.prod.db.db` | 6,174 | 6,174 3,862 | 3,862 3 月 9 日 |
| `neotoma.prod 2.db` | 4,406 | 4,406 3,073 | 3,073 3 月 10 日 |
|实时目标（擦除后）| 84 | 84 67 | 67 3 月 11 日 |
| `neotoma.prod.db.recovered-*` | 4,381 | 4,381 3,059 | 3,059 3 月 3 日 |
| `数据备份/数据复制/` | 4,158 | 4,158 2,955 | 2,955 3 月 2 日 |
|各种旧版本 | 3,100 至 3,931 | 2,558 至 2,806 | 2 月 17 日至 2 月 27 日 |

备份副本的存在是因为我一直在不定期地手动复制数据库文件。不是一个正式的备份系统，只是当我记得或感到紧张时偶尔发出的“cp”命令。其中一个副本“neotoma.prod.db.db”保存了截至 3 月 9 日的几乎所有数据。第二个副本“neotoma.prod 2.db”包含第一个副本错过的截至 3 月 10 日的数据。

在这两个文件和实时数据库中幸存的 84 个观察结果之间，我有足够的材料来重建完整的时间线。

## 合并是如何进行的

Neotoma 有一个内置的 `merge-db` 命令用于组合 SQLite 数据库。流程：

1. 将涉及的每个文件（源文件和目标文件）备份到带时间戳的目录中。任何恢复尝试都不应危及原件。
2. 停止正在运行的 Neotoma 服务器以防止并发写入。
3. 试运行合并以查看存在哪些冲突。
4. 使用“--mode keep-target”执行合并，这会从源中插入目标缺失的行，并保留两个数据库共享的任何行的目标版本。
5. 对第二个源重复此操作。
6. 验证观测值和实体计数。
7. 重新启动服务器。

主要合并从最大的备份中引入了 6,174 个观测值。第二次合并比 3 月 10 日窗口增加了大约 100 个。最终计数：2 月 9 日至 3 月 11 日期间的活动，6,296 个观测值，3,951 个实体。

重新启动后，我通过 Neotoma MCP 对实体进行了采样，以确认一切都可以访问。联系人、任务、对话、反馈记录：全部存在且结构正确。

## 为什么这种恢复是可能的

由于 Neotoma 架构的三个特性，恢复能够成功。

**观察是事实的来源。** Neotoma 不会在发生变化时通过覆盖行来存储实体。每个事实都作为不可变的观察结果进入系统：“Alice 的电子邮件是 alice@example.com，于 3 月 3 日通过 Gmail 观察到。”实体状态是根据完整的观察集计算的。观察日志是仅附加的。

这意味着数据库备份是系统所见过的每个事实的完整快照，而不仅仅是最新状态。当我将备份合并到实时数据库时，我并没有恢复“每个实体的最后已知状态”。我正在重播完整的历史。

**实体快照是派生的，而不是主要的。** 合并观察后，Neotoma 会根据观察日志重新计算实体快照。每个实体的快照都是确定性的：给定相同的观察结果，您始终会获得相同的实体状态。这就是合并命令包含快照重新计算步骤的原因。一旦观察到位，实体就会正确地自我重建。

**带有冲突检测的主键合并。** `merge-db` 命令遍历每个表，插入源中存在但目标中不存在的行，并按主键处理冲突。在“keep-target”模式下，目标版本在任何碰撞中获胜。试运行模式可以在提交之前准确预览将插入的内容以及将发生冲突的内容。我对这两次合并进行了试运行，并在执行之前审查了冲突报告。

这三个属性共同使数据库能够以传统行级备份所不具备的方式进行自我修复。您无需担心哪个备份具有实体的“正确版本”。您合并观察结果，重新计算，然后就会得出正确的状态。

## 我学到了什么

这次经历强化了一些事情。

**非正式备份比没有备份好。** 我偶尔复制数据库文件的习惯节省了几个月的工作。但偶尔的手动副本并不是一个系统。他们留下间隙。如果我在 3 月 7 日而不是 3 月 11 日擦除数据库，我就会丢失 2 月 28 日到 3 月 7 日的数据，因为没有副本完全覆盖该窗口。我现在正在 Mac 上使用 Time Machine 设置自动每日备份。

** env 标志错误是一个经典错误。** 每个跨开发和生产环境运行的系统都存在这种风险。缓解措施是破坏性操作的确认提示、颜色编码的终端提示或每个环境的单独凭据。在此事件之后，每当检测到生产环境时，我都会对“neotoma重置”添加强制确认。 prod 会忽略“-y”标志。在发生任何事情之前，您会看到“Neotoma 重置（生产）”和警告。

**事件源架构在恢复方面得到了回报。** 如果 Neotoma 通过就地覆盖行来存储实体，则数据库擦除将是一个数据丢失事件，没有干净的恢复路径。由于观察是不可变的并且实体状态是派生的，因此恢复是合并和重新计算操作。观察日志是事实。其他一切都可以从中重建。

**我测试了我正在构建的工具。** 几个月前，我为不同的用例编写了“merge-db”命令：组合来自运行多个 Neotoma 实例的用户的数据。我从来没想过将它用在我自己的生产数据上。但由于该工具存在并处理冲突解决和快照重新计算，因此恢复是机械的而不是有压力的。

## 您的数据应该不会出现错误

这一事件暴露了 Neotoma 应该弥补的差距，这样用户就不必像我手动那样做。

**自动快照。** Neotoma 应按计划在任何破坏性操作之前对数据库进行快照。一组轮换的带有时间戳的副本，保留 30 天。如果您错误地在产品上运行重置，则重置前的快照就在那里。恢复不应该取决于您那周是否记得运行“cp”。

**异常检测。** 从数千个观测值突然下降到接近于零是不正常的。 Neotoma 可以检测到这种模式并在提交之前进行确认。一个简单的启发式，“这个操作将删除超过 90% 的观察结果，确认吗？”会完全阻止我擦拭。

**代理驱动的恢复。** 由于代理是 Neotoma 的主要用户体验，因此恢复也应该通过代理进行。您告诉您的代理“我的数据库看起来有问题，我想我丢失了数据。”该代理检查观察计数、查找可用快照、比较日期范围并引导您通过 MCP 完成合并。无需 CLI 探索。

**远程同步。** 本地备份可以防止意外覆盖，但不能防止磁盘故障。 Neotoma 应支持将观察日志同步到远程位置：云存储桶、第二台机器或自托管服务器。由于观察是仅附加的，因此同步模型很简单。将新的观察结果发送到远程。重建任一端的实体状态。

使这种恢复成为可能的相同架构使这些功能的构建变得简单。仅追加观察、派生状态和确定性重新计算不仅仅是恢复属性。它们是备份、同步和自我修复的基础，是一流的保证。

## 数字

|公制|擦拭前|擦拭后|康复后|
|---|---|---|---|
|观察| 6,174 | 6,174 84 | 84 6,296 |
|实体| 3,862 | 3,862 67 | 67 3,951 | 3,951
|日期范围 | 2 月 9 日至 3 月 9 日 | 3 月 10 日至 3 月 11 日 | 2 月 9 日至 3 月 11 日 |

最终计数高于擦除前计数，因为合并合并了来自所有三个源的观察结果：两个备份文件和幸存的擦除后数据。一些仅存在于 3 月 10 日备份或仅存在于实时数据库中的观察结果并不存在于原始最大备份中。

如果您的内存系统使用可变状态，则擦除是永久性的。如果它使用具有派生实体状态的仅附加观察日志，则擦除是与完全恢复的合并。当数据是您的联系人、您的承诺、您与座席数百次会话的历史记录时，这种差异就很重要。

Neotoma 是 [GitHub 上的开源](https://github.com/neotoma-app/neotoma)。如果您想要一个内存层，让您的数据能够承受最严重的错误，请[尝试](https://neotoma.io/install)。