I3.04.3last-write-wins data loss设计

最后写入胜出会造成静默数据丢失

别名: LWW · 最后写入获胜 · silent clobber · 时间戳覆盖

概念解释

最后写入胜出(last-write-wins, LWW)用「谁的时间戳更晚」决定留下哪一份,较早的那份被覆盖。覆盖发生在同步管道里,界面通常继续显示一份干净数据,所以这是静默丢失:被覆盖的作者没有任何失败事件。时间晚不等于意图新,更不等于意图更好。时钟不准、离线后一次性上传、重试,都会让「最后」变成碰巧。

这条是对 LWW 作为默认同步策略的警告。它不展开字段级合并怎么做,也不讨论界面如何画差异——只要产品用 LWW 当唯一裁决,丢失就已经在策略里写好了。

机制

分布式里需要一个全序。LWW 用墙上时钟(或逻辑时钟的粗糙代理)冒充全序:较晚的事件吞掉较早的事件。全序让副本收敛,收敛的代价是非交换的销毁——A 然后 B 与 B 然后 A 留下不同的世界,且其中一次世界里 A 的劳动为零。对用户,销毁没有对应的「保存失败」,因为从赢的那一侧看,保存成功了。输的一侧往往稍后才打开,看见的是别人的句子,自己的句子像从未存在。

时钟使「最后」不稳定。设备时间被用户拨过、NTP 未同步、两台设备几乎同时保存,都会让胜出方换人。离线队列把一小时前的编辑在一分钟前上传,时间戳若取上传时刻,离线劳动会赢过刚才线上的一句小改;若取编辑时刻,晚上传的长文会被刚触碰过空格的人覆盖。无论取哪一端,LWW 都在用一个标量替人做取舍。

边界

计数器、开关、「是否已读」这类可接受丢失、且语义就是「最新为准」的标志,LWW 的伤害可能低于冲突对话框的骚扰。单人、单设备、从不同线上,不存在并发写入,LWW 不会被触发。追加型日志(只 insert 不改历史)没有「覆盖」,LWW 派不上用场。数据库内部用 LWW 做缓存失效,只要失效的不是用户以为已保存的正文,就不在这条的丢失范围。真正危险的是被用户当成文档的字段:姓名、金额、段落、收件人。那些字段上的 LWW 等于把静默丢失做成默认。

怎么落地

  • 对用户可感知的内容字段,不要用 LWW 作为唯一冲突策略。至少在覆盖发生时留下被覆盖副本,并让覆盖变成可看见的事件。
  • 若底层历史原因仍是 LWW,产品层要把它翻译成冲突,而不是把管道的收敛结果直接渲染成「当前文档」。
  • 时间戳只用来排序展示,不用来销毁。
  • 验证:设备 A 改一段落后离线。设备 B 在线改同一段落并保存。A 恢复后上传。打开文档:若只剩 B 的句子、A 的段落无处可找、也没有任何冲突或「被覆盖」提示,就是 LWW 静默丢失。把 A 的时钟拨快一小时再试——若胜出方对调而同样无提示,说明裁决跟着时钟走,而不是跟着作者走。

延伸

  • 同组I3.04.1 冲突需暴露给用户而非自动取一 · I3.04.2 冲突解决需保留双方内容
  • 相邻I3.11 同步冲突与合并 · I3.10 离线状态与本地优先
  • 站内检索last-write-wins · silent data loss · LWW

同组卡片

快捷操作

分享

分享当前页面

ios_share

https://hci.top/zh/handbook/I3.04.3