跳到主要内容

07-24 哪些数据不该放进 Lyquor 状态?

· 阅读需 11 分钟

Lyquor 让状态持久化变得非常方便:网络函数可以修改共享且版本化的网络状态,实例函数可以维护节点本地的实例状态。但这种便利也带来一个设计风险。如果每一笔订单、观测结果、响应、文件和审计记录都写入状态层,两类状态层最终都会承担数据库不断膨胀带来的存储与运维成本。

我们的核心判断是:只有当未来执行、恢复或下一次状态转换的验证必须把一份数据当作“当前事实”读取时,它才应该进入状态层。 不符合这个条件但仍需持久化的数据,需要不同的去处和明确的生命周期;临时数据则可能根本不需要持久化。

数据
|
+-- 执行、恢复或状态转换验证是否需要把它作为当前事实?
|
+-- 是:所有托管节点都必须一致吗?
| +-- 是 -> 网络状态
| +-- 否 -> 实例状态
|
+-- 否:它是权威且有序的证据吗?
+-- 是 -> 事件 / 历史
+-- 否:它是必须保留的不可变原始内容吗?
+-- 是 -> 对象存储
+-- 否:它是可重建的消费端视图吗?
+-- 是 -> 派生查询 / 索引
+-- 否 -> 留在进程内 / 丢弃

紧凑的执行核心与本地工作数据、外部历史存储彼此分离

持久化不等于写入状态层

Lyquor 文档中的状态模型区分了两类持久内存。网络状态(Network State)是共享的、经过排序的,并按 LyquidNumber 版本化实例状态(Instance State)属于单个节点,在本地持久化,而且不同节点的值可以不同。这个区别回答了谁必须对一个值达成一致,却不能单独判断这个值是否应该写入状态层。

以交易应用为例:

  • 当前余额必须参与判断下一笔提现是否有效。
  • 当前风险限额必须参与判断下一笔订单是否可以接受。
  • 节点可能需要依靠重试游标恢复本地工作进程。
  • 原始市场数据响应可能对审计有用,却与下一次确定性状态转换无关。
  • 历史订单正文可能是回放所必需的,但已经不属于当前订单簿。
  • 图表、搜索索引和日报可以根据权威记录重新生成。

这六类数据都可能需要持久化,但它们不需要相同的一致性、访问路径、保留期限和恢复保证。

这个风险在 Lyquor 中尤其容易被忽视,因为直接内存架构(Direct Memory Architecture,DMA)让状态数据看起来就像普通的 Rust 数据结构。Lyquor 白皮书把网络状态和实例状态描述为持久化、可按字节寻址的内存,应用可以通过 Rust 数据结构直接访问。DMA 减少了应用代码中的存储样板,却不会消除系统层面的复制、版本管理、备份、迁移和保留成本。

网络状态数据持续增长,会增加托管节点需要保存和恢复的版本化数据。实例状态数据持续增长,只是转移了问题:每个运营方仍然需要存储、备份、迁移,并可能重建自己的副本。即使一个只增不减的 Vec 声明在 state! 中,它本质上仍然是一个追加式数据库。

数据不只有两个去处

更完整的 Lyquor 数据模型至少需要五种持久化去处:

去处表示什么示例关键保证
网络状态所有托管节点必须一致应用的当前事实余额、权限、作为共享事实的当前订单簿状态、已接受的风险参数确定性、排序、版本化
实例状态单个节点执行或恢复所需的当前事实工作游标、有界工作集、本地协议检查点、数据源健康状态本地持久、隔离、可恢复
事件与历史层对已经发生之事的追加式证据已提交订单、撤单、成交、操作回执、批次承诺与对象引用有序、可查询、可回放、可裁剪
外部对象层不应进入执行状态层的大型原始内容文件、证据包、原始响应、快照、模型产物可寻址、持久、有生命周期
派生查询层面向消费者而非执行过程优化的视图搜索索引、账户历史、图表、分析数据可查询、可重建、独立扩展

三类状态层之外的数据也不能混为一谈。

事件历史按提交顺序保存权威记录,从而支持回放、审计、订阅和下游处理。它需要稳定标识、完整性规则,以及与已提交执行之间的明确关系。

外部对象层保存大型原始内容。由密码学摘要引用的文件不能在摘要保持不变的情况下被静默替换,但文件的完整内容不必放在执行状态层中。

派生查询层则为特定消费者重新组织权威数据。只要底层状态数据、历史和源数据对象仍然可用,搜索索引就可以删除后重建。

这类边界在 Ethereum 中已经很常见。Solidity 事件会生成可搜索的日志,应用可以通过 JSON-RPC 消费这些日志;分析系统则进一步把区块、交易、日志和调用轨迹转换成不同的查询模型。Ethereum 的数据与分析文档展示了第二层的必要性:执行数据与面向应用的便捷查询,本来就是两种不同的产品。

Lyquor 不需要照搬 Ethereum 的事件编码方式或存储经济模型。真正值得借鉴的是数据边界:

状态层回答:   现在什么是真的?
历史回答: 发生过什么,顺序是什么?
对象存储回答: 完整内容在哪里?
索引回答: 消费者如何高效找到它?

判断应用数据去处的五个问题

在向任一 Lyquor 状态层添加字段之前,应用设计者可以先回答五个问题。

1. 这个值会影响未来状态转换是否有效吗?

如果删除这个值会导致下一次确定性调用无法完成验证,那么它很可能应该进入状态层。余额、防重放序号(nonce)、权限、未结负债和当前生效的策略通常属于这一类。

如果一个值只是解释过去的状态转换,它更可能属于历史。例如,当前持仓可能应该保存在状态层,但得到该持仓时用过的每一步中间计算通常不需要进入状态层。

2. 所有托管节点都必须对它达成一致吗?

如果答案是“是”,而且这个值会影响共享执行,它应该写入网络状态。如果只有一个节点需要它来继续本地工作,实例状态可能已经足够。

节点本地不等于可以随意丢失。重试游标、本地协议检查点或待处理工作项也可能需要可靠恢复。但不能只因为丢失它会带来不便,就把它写入网络状态。

缓存也需要同样克制。只有当缓存规模有界,而且持久化能带来明确的恢复收益时,它才适合写入实例状态;否则保留在进程内存中即可。

3. 它能够重新生成吗?

派生视图通常应该放在状态层之外。订单簿深度图、账户活动页面、搜索结果、聚合交易量和监控面板,都可以根据规范状态数据与完整历史重新生成。

一旦选择重建,就需要回答另一组可靠性问题:哪个来源具有权威性,消费者如何发现缺失区间,以及从哪个检查点开始可以避免从创世状态完整回放。

4. 正文很大,但执行只需要它的身份吗?

保存承诺,不一定保存正文。

一次共享状态转换可能只需要保存摘要、根、长度、所有者、状态字段或可用性承诺。对应的批次完整数据、文件、证据包或模型产物可以进入内容寻址的外部存储。这样既能保持执行边界紧凑,也不会让底层数据失去可审计性。

但这会引入一项新义务:应用必须明确谁负责保存正文、保存多久,以及当承诺仍然存在而正文已经不可用时应该如何处理。

5. 这份数据什么时候可以删除?

没有删除规则的状态数据,往往会意外变成历史数据库。每个集合都应该有明确的生命周期:

  • 被更新的当前事实覆盖;
  • 在进入终态后删除;
  • 保留到争议或恢复窗口结束;
  • 压缩进快照;
  • 从热路径迁移到归档;
  • 或者因为应用明确需要而永久保存。

“永久保存”可以是合理选择,但它应该是产品和成本决策,而不是一个容器的默认行为。

这对 Lyquor 应用意味着什么

把上述原则应用到一个 DEX 类 Lyquid,可以先得到下面这组用于讨论的数据划分:

网络状态
当前余额、持仓、预留金额、权限、
作为共享事实的订单簿状态、风险参数、结算检查点

实例状态
有界工作集、本地数据源健康状态、重试游标、
本地待处理工作与协议检查点

事件与历史层
已提交订单、撤单、成交、清算、经认证的决策、
状态转换回执、批次承诺与对象引用

外部对象层
原始市场数据快照、证据包、批次完整数据、状态快照、文件

派生查询层
账户历史索引、分析与监控视图

这套划分的重点是:不同类型的数据需要不同的存储、排序和恢复保证。每个去处的具体接口、原子性、保留策略和故障恢复机制仍需设计与验证。这不表示 Lyquor 已经提供了所有相关存储服务。

实例函数在节点本地运行,有时需要把数据保存到状态层之外。例如,应用可能要写入证据包、维护本地数据库、追加持久数据流,或保存大型产物。直接开放不受限制的文件系统访问并不能解决问题,因为访问权限、数据迁移、复制和清理规则都会变得不明确。更合适的接口应明确访问范围和使用策略,例如按内容摘要寻址的对象存储、按应用隔离的命名空间、追加式数据流,或由运营方配置的外部存储目标。

事件与历史层同样需要明确规则。开发者需要知道:

  • 一条记录是否与它描述的状态转换原子提交;
  • 记录如何排序和标识;
  • 能否查询历史区间并检查完整性;
  • 哪些记录需要所有节点共享,哪些只保存在本地;
  • 订阅断开后如何恢复;
  • 记录保留多久,以及何时裁剪或归档。

如果这些问题没有答案,把数据移出状态层只能缓解状态膨胀,却可能形成另一个不可靠的数据库。

状态边界也是产品边界

这里的选择不只是节点数据库与文件系统之间的选择,也不只是网络状态与实例状态之间的选择,而是不同保证之间的选择。

网络状态承担最广泛的运维负担,因为它代表共享的当前事实。实例状态让本地执行可以保留工作上下文,而不需要强迫所有节点达成一致。事件历史服务于需要最新状态之外信息的用户、审计方和下游系统。外部对象存储让大型原始内容拥有独立的扩展和生命周期策略。派生查询系统则让面向消费者的视图可以独立扩展和重建。

实用原则可以归纳为:

状态层只保留执行、恢复和验证所需的最小事实;有序证据进入历史层,大型不可变正文进入对象存储,可重建视图进入索引。

因此,下一步研究问题可以非常具体:Lyquor 的事件与外部存储接口至少需要提供哪些保证,才能让应用把数据移出状态层,同时不牺牲可审计性、恢复能力和可移植性?

参考资料