14 KiB
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
基于 mnote 与 mnote-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 建议主对象
至少包含:
workspacespagespage_versions(可选)blocksblock_relations(可选,若不全放在 block 表中)assetsasset_versionsreferencestasksagent_sessionscommand_logsdomain_eventsjobsjob_logs
这些对象应优先映射或扩展到现有 Convex 主线,而不是在新仓先落一套平行本地表。
5.3 blocks 对象建议
关键字段:
block_idworkspace_idpage_idparent_block_idsort_keyblock_typecontent_jsonprops_jsonannotations_jsonrevisiondeleted_at
说明:
sort_key建议支持稀疏排序,避免频繁全量重排- 软删除优于直接硬删,方便审计与恢复
revision用于乐观并发控制
5.4 assets 对象建议
asset_idworkspace_idasset_kindmime_typecurrent_version_idstorage_strategystatusmetadata_json
5.5 references 对象建议
reference_idsource_object_typesource_object_idtarget_object_typetarget_object_idanchor_jsonref_kindstatus
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_idcommand_nameactor_typeactor_idsourceworkspace_idtarget_objectspayload_summaryrefs_jsonidempotency_keystatuscreated_atfinished_at
7.2 设计要求
- 能关联到一次真实主写入
- 能关联后续 domain events
- 能关联 tool call / job / rollback
- 能为 AI 回放与人类审计提供最小闭包
7.3 与 Convex 的关系
命令日志可以:
- 直接进入 Convex 主线对象
- 或通过 bridge 落到与主对象同一事实层
但不要单独把命令日志只记在本地、主对象却记在远端;那会破坏统一审计链路。
8. Domain Event
DomainEvent 不是为了炫技,而是为了:
- 给索引层提供标准增量输入
- 给回放和审计提供结构化事件
- 给异步任务提供稳定订阅源
8.1 事件最低字段
event_idworkspace_idaggregate_typeaggregate_idevent_typeevent_versionpayload_jsoncommand_log_idactor_typecreated_at
8.2 事件边界
建议记录领域事件,而不是 UI 手势。
例如:
page_createdpage_renamedblock_insertedblock_movedblock_content_replacedasset_version_addedreference_createdtask_status_changed
不建议记录:
- 弹窗打开
- 光标移动
- hover 展示
- 面板折叠
9. Job 与异步链路
不是所有事情都应进主事务。
应把这些放入 Job:
- OCR
- OnlyOffice 提取/转换
- 向量切片与嵌入
- 全量重建索引
- 大文件导入
- 外部同步
9.1 Job 最低字段
job_idjob_typeworkspace_idtarget_objectsinput_jsonstatusprogresserrorcreated_atstarted_atfinished_at
9.2 不能放进主事务的内容
- 大模型推理
- OCR
- 大文件转换
- 向量重建
- 远程同步
这些必须走 Job。
10. 索引体系
10.1 Full Text Search
最低必做:
- 页面标题全文检索
- 块内容全文检索
- 资产提取文本全文检索
- 引用 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_id、trace_id、workspace_id、command_id语义,不允许临时脚本私有解释
这意味着后续无论是 CLI、AI 还是 Web 排障,都应先调用同一条 Rust 恢复命令面,而不是各自写一套索引补数脚本。
12. AI 可观测性
AI 全流程介入后,系统必须能回答:
- 某次回答引用了哪些对象
- 某次编辑基于哪些上下文
- 某个工具为什么失败
- 当前搜索索引是否滞后
- 哪些对象正在被长任务处理
因此建议额外维护:
12.1 AgentSession 关联表
agent_session_objectsagent_session_tool_callsagent_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. 迁移建议
先做:
- 盘点现有 Convex schema 与
mnote-rust领域对象的映射 - 落
CommandLog / DomainEvent的主事实层方案 - 落
Page / Block / Asset / Reference的 bridge / repo 接口 - 建立全文索引最小实现
- 建立索引 worker 骨架
再做:
- AgentSession / ToolCallLog
- Outline / Backlink 读模型
- 向量索引占位
- Office / OCR / Mindmap 派生索引
- 必要的离线缓存 / dry-run 本地层
16. 一句话结论
mnote-rust 当前应采用 Convex 主事实层 + FileSystem 资产存储 + CommandLog/DomainEvent 事件链路 + 可重建全文/引用/向量索引 的分层结构;
Rust 内核负责把命令、查询、工具、索引、异步任务与外部能力重新组织清楚,而不是再造一套与现有主线并列的数据库事实源。