Mindwtr如何实现多端无损同步?完整拆解它的合并算法、墓碑机制与冲突解决策略
【免费下载链接】MindwtrGet tasks and ideas out of your head. A free GTD to-do app for desktop and mobile. Works offline, no account needed项目地址: https://gitcode.com/gh_mirrors/mi/Mindwtr
Mindwtr是一款免费、开源、本地优先的 GTD 待办事项应用(To-Do App),支持桌面端和移动端离线使用、无需注册账号。它最核心的能力之一,就是多端无损同步:你在手机上改的任务,打开电脑就能看到;而多台设备同时修改同一条任务时,Mindwtr 依靠一套版本感知的合并算法(revision-aware merge)、墓碑机制(Tombstone)和确定性的冲突解决规则,保证所有设备最终收敛到完全一致的结果,且不丢失任何一方的有效修改。
为什么简单的"最后写入 wins"不够用?
很多人的第一反应是:两台设备改了同一条任务,谁的时间戳晚就听谁的。但 Mindwtr 的架构决策记录(ADR)里明确指出,纯时间戳的 last-write-wins 有三个致命问题(见 docs/adr/0003-revision-aware-sync.md):
- 设备时钟会漂移:手机和电脑的时钟可能相差几秒甚至几分钟,"晚"不等于"新";
- 删除不能消失:A 设备删掉了任务,B 设备同时改了它,如果简单比较时间戳,删除可能被"复活",或者修改被静默吞掉;
- 时间戳完全相等时:仍然需要一个确定性的裁决规则,不能靠遍历顺序碰运气。
所以 Mindwtr 选择了版本元数据 + 时间戳 + 墓碑 + 确定性平局裁决的组合方案。
快照同步:没有增量日志的"轻量级"路线
在动手之前,先理解 Mindwtr 同步的整体形态:它做的是全量快照合并,而不是逐条操作日志(delta log)。这一点在 docs/adr/0008-snapshot-sync-without-delta-log.md 中被明确决策并保留了:
- 个人 GTD 应用的实体数量小、设备数量少、数据属于个人而非团队协作;
- 整个数据是一个 JSON 快照文件(本地持久化为 SQLite),全文件写入简单且原子;
- 每条数据上自带的
rev(版本号)和revBy(最后修改者)字段,已经足以防止"丢失更新",不需要额外的操作日志。
一次同步周期的流程非常直白:拉取远端快照 → 归一化 → 逐实体合并 → 写回一份合并结果,核心实现在 packages/core/src/sync.ts 与 packages/core/src/sync-cycle.ts 中。
这也解释了为什么 Mindwtr 支持 iCloud、WebDAV、文件同步甚至自建云等多种后端——它们只是快照的"搬运工",真正的合并逻辑只有一份(见 docs/adr/0010-self-hosted-cloud-sync-server.md),所有通道行为完全一致。
核心关键词①:版本感知的合并算法
Mindwtr 的合并策略按优先级依次比较,每一级都是确定性的:
| 优先级 | 比较信号 | 说明 |
|---|---|---|
| 1 | 实体归一化 | 合并前先把两侧数据清洗成可比较的形式(sync-normalization.ts) |
| 2 | 版本号rev/revBy | 谁的版本更新,谁赢——这是对抗时钟漂移的主力 |
| 3 | 时间戳 | 版本号不可比时,用操作时间排序 |
| 4 | 删除 vs 存活(delete-vs-live)规则 | 删除和修改撞车时的专门裁决,见下文 |
| 5 | 确定性平局裁决 | 内容签名 + 稳定排序,保证每台设备选出同一个赢家 |
第 5 步值得展开:当版本号和时间戳都无法区分时,packages/core/src/sync-signatures.ts 会对任务内容做哈希签名,再用确定性的平局规则(如字典序)选出赢家。确定性是这里的关键——不是"随便选一个",而是"任何设备、任何时刻、重复计算都选出同一个"。这就是"无损同步"的数学基础。
核心关键词②:墓碑(Tombstone)机制
删除是怎么跨设备传播的?答案是"软删除墓碑":删除一条任务时,Mindwtr 不真正抹掉它,而是给它盖上deletedAt时间戳,让它以"墓碑"的身份继续参与同步,把"这里曾经有个东西,被删了"这个事实通知到每台设备。
保留多久?规则在 docs/adr/0005-tombstone-retention-policy.md 中定义:
- 墓碑默认保留90 天(可在 1 天到 3650 天之间调整),默认值见 packages/core/src/sync-tombstones.ts;
- 保留窗口内,墓碑始终留在快照和同步载荷中——太早删掉,长期离线回来的设备可能把"已删除的任务"复活;
- 过期后由显式的清理步骤(
purgeExpiredTombstones)清除,而不是在普通读取时顺手删; - 被彻底清除(purged)的记录永不复活,恢复操作会把它视为不存在。
核心关键词③:冲突解决的演进——"存活数据优先"
删除 vs 修改是最棘手的冲突:A 设备删除了任务,B 设备几乎同一时刻修改了它,听谁的?
Mindwtr 这里走过一次演进,非常值得学习:
- 早期规则(0.8.2 之前):操作时间相等时,墓碑优先——宁可错杀也不让删除消失(ADR 0003);
- 实际流量暴露问题:在时钟漂移的设备上,这条规则太激进,容易把有效的修改连同任务一起删掉;
- 现行规则(ADR 0007):在 docs/adr/0007-live-wins-in-ambiguous-delete-merge.md 中改为存活数据优先:
- 用操作时间(删除取
max(updatedAt, deletedAt))比较两个操作; - 两者相差超过 30 秒:较新的操作赢;
- 落在30 秒模糊窗口内:版本号(
rev)高的一边赢; - 仍无法区分:保留存活的任务,而不是让删除默认获胜。
这个设计体现了务实的工程哲学:在"可能丢一次删除"和"可能丢一次用户修改"之间,选择保住用户的修改——因为前者重新删一次就行,后者是真实的劳动成果。
这套机制对普通用户意味着什么?
- 离线随便用:飞行模式下改了任务,联网后自动同步,合并结果在任何设备上都是同一个;
- 多后端随意切换:iCloud、WebDAV、文件同步、自建云服务器共用同一套合并规则,切换后端不会改变冲突行为;
- 删除可安全传播:删掉的任务会在所有设备上消失,而不会被某台离线设备"救活";
- 数据量友好:快照模型 + 墓碑定期清理,让同步文件保持有界增长。
想更深入阅读,可以从这几份材料入手:
- 同步决策记录总目录:docs/adr/README.md
- 同步编排与周期串行化:docs/adr/0014-sync-orchestration-ports.md、docs/adr/0016-sync-cycle-serialization.md
- 附件的同步模型:docs/adr/0011-attachment-sync-model.md
- 合并设置逻辑:packages/core/src/sync-merge-settings.ts
小结
Mindwtr 的多端无损同步 =全量快照合并(简单、原子)×版本号优先的时间戳比较(抗时钟漂移)×墓碑软删除(删除可传播、可收敛)×确定性的平局裁决(所有设备算出同一个结果)。它没有引入复杂的 CRDT 或增量日志,而是用一套小而完备的规则,把个人待办应用最真实的同步场景做到了"每台设备都一样"。
【免费下载链接】MindwtrGet tasks and ideas out of your head. A free GTD to-do app for desktop and mobile. Works offline, no account needed项目地址: https://gitcode.com/gh_mirrors/mi/Mindwtr
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考