Files
mnote/rust/design/core/03-storage-event-indexing-v0.md
T

14 KiB
Raw Blame History

03. Storage / Event / Indexing v0

更新时间:2026-04-11 适用范围:/mnt/Data1T/mnote-rust


1. 目标

本文件定义 mnote-rust 在当前路线下的持久化、事件日志、索引体系。

它要回答:

  • 当前系统事实层应落在哪里
  • Rust 内核怎样接入既有事实层而不重复造库
  • 写操作如何进入事件链路
  • 搜索、引用、RAG、回放、审计依赖什么数据面
  • 如何既保证性能,又保证 AI 可观测、可维护、可回放

这部分的前提必须先讲清:

对当前 mnote / mnote-next 主线来说,Convex 仍是主事实层

因此,这份文档不再讨论“用 SQLite 取代 Convex 做主库”,而是讨论:

  • 如何在 Convex 主事实层 之上建立 Rust 内核
  • 如何把索引、事件、导出、离线缓存、本地处理组织清楚
  • 如何避免再次引入第二套事实真相

如果这层设计不好,系统最终仍会退化成:

  • 页面组件自己维护真相
  • 各类能力各写各的缓存
  • AI 看不到完整上下文
  • Rust 内核与现有系统各管一套数据
  • 回滚、审计、重建索引都变得困难

2. 总原则

2.1 主事实层、事件日志、索引必须分层

三者职责不同:

  • 主事实层:保存系统当前真相
  • 事件日志:保存“如何变成现在”的过程
  • 索引层:为检索、聚合、推荐、RAG 提供加速读模型

禁止让任一层越权:

  • 不能拿索引当真相
  • 不能只靠事件流而没有可直接读取的当前状态
  • 不能让页面缓存成为事实层
  • 不能让本地缓存演化成第二主库

2.2 当前主事实层应继续落在 Convex

基于 mnotemnote-next 已有主线,当前阶段应坚持:

  • Convex 是唯一主事实层
  • Rust 内核通过协议与 bridge 接入 Convex,而不是绕开它再建一套主库
  • 旧仓与新仓共享的核心对象,应优先复用既有 Convex schema / deployment / caller 体系

这不是保守,而是避免推翻已经跑通的主链路。

2.3 本地文件系统与本地嵌入式存储仍然重要

虽然主事实层继续用 Convex,但本地层仍然需要:

  • 文件系统:资产原文件、导出文件、缓存、派生产物、临时工作目录
  • 可选本地嵌入式存储:离线缓存、索引快照、dry-run、调试态数据、临时队列

但这些都不能升级为新的主事实层。

2.4 写入必须原子化到“主事实层 + 事件落账”

一次命令成功后,至少要保证:

  • 主事实层写入完成
  • 事件写入成功
  • 命令日志有记录

索引允许异步追平,但必须可检测 lag。

2.5 索引必须可重建

搜索索引、向量索引、聚合视图都必须满足:

  • 可从主事实层 + 事件重新构建
  • 可做全量重建
  • 可做增量追平

否则后期会不可维护。


3. 三层数据面

[主事实层]
Convex + FileSystem

[事件与日志层]
CommandLog + DomainEvent + JobLog + AuditTrail

[索引与派生层]
FTS / Reference Graph / Backlink View / Outline View / Vector Index

3.1 主事实层负责

  • Workspace / Page / Block / Asset / Reference / Task / AgentSession 当前状态
  • 事务性写入
  • 读取当前真相
  • 提供稳定 schema
  • 承担权限与主对象约束

3.2 事件与日志层负责

  • 记录命令与事件
  • 记录 actor、reason、scope、结果
  • 支持回放、审计、问题追踪
  • 作为索引增量更新输入

3.3 索引与派生层负责

  • 全文搜索
  • 页面大纲
  • 反链
  • 资产片段搜索
  • RAG 检索读模型
  • 推荐 / 关系聚合

4. 主事实层选型

4.1 当前阶段的明确结论

推荐:

  • Convex:结构化主事实层
  • File System:存原始资产与大对象
  • Rust bridge / protocol layer:作为统一命令、查询、工具接入面

原因:

  • mnote 中“Convex 已完全替换 Supabase”的现实一致
  • mnote-next 中“默认复用现有 Convex deployment / project”的路线一致
  • 复用现有 deployment、schema、自建与 AI 开发经验,避免重建第二套数据库主线
  • 更符合“复用成熟能力,重构边界,不做无意义重建”的迁移原则

4.2 本地嵌入式存储的正确定位

可以保留本地嵌入式存储,但只限于:

  • CLI / Agent 的离线缓存
  • 全文索引引擎的本地数据文件
  • dry-run / scaffold / 调试态数据
  • 单机导入处理中的临时工作库

不能把它写成:

  • 第二套主事实层
  • 与 Convex 并列的正式写入真相
  • 长期双写的核心业务库

4.3 不建议的中心方案

  • 用 SQLite 取代 Convex 成为主真相
  • OnlyOffice / 第三方编辑器内部状态作为主真相
  • 仅事件溯源、无当前态表
  • 单纯 Markdown 文件散落 + 大量 sidecar 作为唯一结构源
  • 长期维护 Convex + SQLite 双事实源

这些都会让系统再次陷入边界混乱。


5. 主事实对象结构建议

这里讨论的是领域对象结构,不是要求新建第二套数据库。

5.1 结构原则

  • 主对象必须能映射到现有 Convex schema
  • 单对象一组稳定字段或有限关联表
  • 避免过度 EAV
  • JSON 仅用于局部扩展字段,不替代主 schema

5.2 建议主对象

至少包含:

  • workspaces
  • pages
  • page_versions(可选)
  • blocks
  • block_relations(可选,若不全放在 block 表中)
  • assets
  • asset_versions
  • references
  • tasks
  • agent_sessions
  • command_logs
  • domain_events
  • jobs
  • job_logs

这些对象应优先映射或扩展到现有 Convex 主线,而不是在新仓先落一套平行本地表。

5.3 blocks 对象建议

关键字段:

  • block_id
  • workspace_id
  • page_id
  • parent_block_id
  • sort_key
  • block_type
  • content_json
  • props_json
  • annotations_json
  • revision
  • deleted_at

说明:

  • sort_key 建议支持稀疏排序,避免频繁全量重排
  • 软删除优于直接硬删,方便审计与恢复
  • revision 用于乐观并发控制

5.4 assets 对象建议

  • asset_id
  • workspace_id
  • asset_kind
  • mime_type
  • current_version_id
  • storage_strategy
  • status
  • metadata_json

5.5 references 对象建议

  • reference_id
  • source_object_type
  • source_object_id
  • target_object_type
  • target_object_id
  • anchor_json
  • ref_kind
  • status

6. 文件系统布局建议

资产不要全塞数据库 blob。

建议:

workspace-data/
├─ assets/
│  ├─ asset_xxx/
│  │  ├─ v1/original.docx
│  │  ├─ v2/original.docx
│  │  ├─ extracted/outline.json
│  │  ├─ extracted/text.md
│  │  └─ derived/preview.png
├─ indexes/
│  ├─ fts/
│  └─ vector/
├─ runtime/
│  ├─ jobs/
│  ├─ sessions/
│  └─ temp/
└─ export/

原则:

  • 结构化真相仍以 Convex 为主
  • 原始大文件入文件系统
  • 派生产物有明确目录归属
  • 临时文件与正式资产分离

7. Command Log

CommandLog 是所有写命令的正式记录。

7.1 最低字段

  • command_log_id
  • command_name
  • actor_type
  • actor_id
  • source
  • workspace_id
  • target_objects
  • payload_summary
  • refs_json
  • idempotency_key
  • status
  • created_at
  • finished_at

7.2 设计要求

  • 能关联到一次真实主写入
  • 能关联后续 domain events
  • 能关联 tool call / job / rollback
  • 能为 AI 回放与人类审计提供最小闭包

7.3 与 Convex 的关系

命令日志可以:

  • 直接进入 Convex 主线对象
  • 或通过 bridge 落到与主对象同一事实层

但不要单独把命令日志只记在本地、主对象却记在远端;那会破坏统一审计链路。


8. Domain Event

DomainEvent 不是为了炫技,而是为了:

  • 给索引层提供标准增量输入
  • 给回放和审计提供结构化事件
  • 给异步任务提供稳定订阅源

8.1 事件最低字段

  • event_id
  • workspace_id
  • aggregate_type
  • aggregate_id
  • event_type
  • event_version
  • payload_json
  • command_log_id
  • actor_type
  • created_at

8.2 事件边界

建议记录领域事件,而不是 UI 手势。

例如:

  • page_created
  • page_renamed
  • block_inserted
  • block_moved
  • block_content_replaced
  • asset_version_added
  • reference_created
  • task_status_changed

不建议记录:

  • 弹窗打开
  • 光标移动
  • hover 展示
  • 面板折叠

9. Job 与异步链路

不是所有事情都应进主事务。

应把这些放入 Job

  • OCR
  • OnlyOffice 提取/转换
  • 向量切片与嵌入
  • 全量重建索引
  • 大文件导入
  • 外部同步

9.1 Job 最低字段

  • job_id
  • job_type
  • workspace_id
  • target_objects
  • input_json
  • status
  • progress
  • error
  • created_at
  • started_at
  • finished_at

9.2 不能放进主事务的内容

  • 大模型推理
  • OCR
  • 大文件转换
  • 向量重建
  • 远程同步

这些必须走 Job。


10. 索引体系

最低必做:

  • 页面标题全文检索
  • 块内容全文检索
  • 资产提取文本全文检索
  • 引用 snippet 检索

建议:

  • 初期直接用本地全文索引引擎(如 SQLite FTS5、Tantivy 或兼容方案)
  • 索引文档单位统一为“可定位对象”

例如:

  • page
  • block
  • asset_fragment
  • reference_snippet

10.2 为什么块级索引重要

因为你的产品最终不是“整篇文档搜索”,而是:

  • 找到某一段
  • 跳回块位置
  • 让 AI 只编辑局部
  • 给引用与上下文最小闭包

10.3 Reference Graph

必须维护引用图读模型:

  • page -> page
  • block -> block
  • block -> asset fragment
  • task -> source object

这对:

  • 反链
  • 影响面分析
  • AI 上下文组装
  • 知识导航

都很关键。

10.4 Outline View

页面大纲、Office 文档提取大纲、PDF 目录、Mindmap 树都应是派生读模型。

优点:

  • 不污染主事实层
  • 允许多种提取策略
  • 容易重建

10.5 Vector Index

向量索引建议延后,但结构上预留。

原则:

  • 向量索引只是检索加速层
  • 向量 chunk 必须能回指 page_id / block_id / asset_id / anchor
  • 向量索引失效可重建,不影响系统主真相

11. 索引更新策略

11.1 起步建议:异步增量 + 可全量重建

每次事件提交后:

  • 生成索引任务
  • 按对象粒度增量更新
  • 记录 lag 与最后处理 event id

11.2 索引一致性要求

  • 主事实层一致性 > 索引实时性
  • 查询可返回“索引处理中”状态
  • 对必须强一致的局部场景,可同步刷新小范围索引

11.3 失败恢复

索引 worker 挂了也不能影响主写入。

必须支持:

  • last_processed_event_id 继续追平
  • 指定对象重建
  • 指定 workspace 全量重建

11.4 回放与索引重建的正式入口

从 task-034 开始,回放和重建不再只是“库里有个纯函数”,而要成为统一命令面的一部分。

最小要求:

  • event_replay:从统一事件列表读取,输出 replay 后的 cursor 与受影响 event 摘要
  • index_rebuild:基于 last_processed_event_id / last_processed_at 重建索引批次,并返回新的 cursor
  • 两者都复用同一套 request_idtrace_idworkspace_idcommand_id 语义,不允许临时脚本私有解释

这意味着后续无论是 CLI、AI 还是 Web 排障,都应先调用同一条 Rust 恢复命令面,而不是各自写一套索引补数脚本。


12. AI 可观测性

AI 全流程介入后,系统必须能回答:

  • 某次回答引用了哪些对象
  • 某次编辑基于哪些上下文
  • 某个工具为什么失败
  • 当前搜索索引是否滞后
  • 哪些对象正在被长任务处理

因此建议额外维护:

12.1 AgentSession 关联表

  • agent_session_objects
  • agent_session_tool_calls
  • agent_session_outputs

12.2 Tool Call Log

记录:

  • tool name
  • input
  • output summary
  • duration
  • error
  • command_log_id / query trace id

这样后期 AI 排障、回放、评估都会容易很多。


13. 性能关键点

13.1 不要整页全文重写

编辑块时,只更新相关块与局部派生视图。

13.2 不要同步重建全索引

任何全库重建都必须后台化。

13.3 资产处理走异步

OCR、Office 转换、预览图生成、向量切片都不能阻塞主编辑事务。

13.4 读写模型分离

高频 UI 读需求可通过派生视图优化,不要污染主事实 schema。


14. OnlyOffice 在存储层的正确位置

OnlyOffice 相关对象建议这样落地:

  • 原始 docx/xlsx/pptx/pdf:作为 Asset + AssetVersion
  • 文档编辑结果:生成新 AssetVersion
  • 提取结构(目录、批注、书签、文本片段):作为派生读模型或 Reference
  • 当前选区、当前页码:作为短生命周期 SelectionAnchor

不要把:

  • OnlyOffice 内部文档状态
  • 插件瞬时 UI 状态
  • callback 的临时 payload

直接当成主事实层。


15. 迁移建议

先做:

  1. 盘点现有 Convex schema 与 mnote-rust 领域对象的映射
  2. CommandLog / DomainEvent 的主事实层方案
  3. Page / Block / Asset / Reference 的 bridge / repo 接口
  4. 建立全文索引最小实现
  5. 建立索引 worker 骨架

再做:

  1. AgentSession / ToolCallLog
  2. Outline / Backlink 读模型
  3. 向量索引占位
  4. Office / OCR / Mindmap 派生索引
  5. 必要的离线缓存 / dry-run 本地层

16. 一句话结论

mnote-rust 当前应采用 Convex 主事实层 + FileSystem 资产存储 + CommandLog/DomainEvent 事件链路 + 可重建全文/引用/向量索引 的分层结构; Rust 内核负责把命令、查询、工具、索引、异步任务与外部能力重新组织清楚,而不是再造一套与现有主线并列的数据库事实源。