外观
同步撤销与异步清理,怎样保证边界?
项目版本说明
这些问答整合历史设计与实现资料;当前 main 分支 README 以工作包描述建设状态。具体能力、实验数据与设计目标的边界见项目状态与证据。
标准回答
我把撤销拆成两件事:先让内容不再被允许使用,再清理索引、缓存和下游残留。前者是正确性边界,后者是可能耗时的后台工作。
EachOf 的方案是同步更新有效状态,再异步进行下游隔离和清理。这样不需要等整条依赖链全部清完才确认撤销。比如旧向量暂时还在索引里,查询即使召回了它,也必须根据记忆和来源的有效状态决定是否可以返回,不能因为向量相似就直接使用。
这个方案成立的关键,是所有相关路径遵守同一规则。读侧要检查有效性,缓存要复核来源,写侧也要防止基于旧来源生成的候选在撤销之后重新写回来。否则表面上删了一条记忆,异步任务又把它“复活”,撤销就没有真正闭环。
同时,我会明确当前视图和历史视图的区别:撤销后退出当前使用,不一定意味着历史记录也立即物理删除。历史记录仍然受权限控制;涉及彻底删除时,还要单独考虑备份和日志,不能把业务撤销当成所有介质都已擦除。
关键机制
- 定义撤销完成的语义:从哪个确认点之后,新查询不得使用旧依据;正在执行的请求如何处理也要明确。
- 异步清理承担长链传播与资源回收;读侧不能仅因索引仍有相似结果就直接放行。
- 当前视图排除失效内容;历史视图按权限保留解释能力。业务撤销不等于完成法定删除或备份擦除。
通俗例子
旧向量还没从索引移除,召回后仍必须验证记忆及其依据的状态,才能避免清理窗口中的旧结论泄漏。
追问与回答
为什么不全部同步清理完?
因为依赖链可能很长,索引和缓存也可能分布在不同处理环节。全部同步完成会拉长确认时间,并把后台清理故障传递到前台。先建立有效状态边界、再清理,可以缩短关键路径。但这不是放松一致性要求:只要还有读接口绕过状态检查,就可能返回旧内容。因此收益来自拆分工作,而正确性依赖读写两侧共同遵守撤销规则。
撤销时另一个 Worker 正在写怎么办?
Worker 生成候选时看到的状态可能已经过期,因此提交时还要核对来源版本和有效状态。如果来源已撤销或版本不匹配,就不能作为当前有效记忆提交。更细的并发问题是“检查通过后,写入前又发生撤销”,这需要把检查和提交放进合适的事务或条件更新边界。我能明确这个约束,但具体采用哪种并发控制,必须与代码中的实际实现一致。
易混淆点
只设置一个数据库标记还不足以证明即时撤销;遗漏缓存、历史接口或正在提交的任务,都可能破坏边界。
参考资料
内容来源
- Android 技术札记 · 原条目
eachof-revoke