删除文档为何需要“_deleted:true”标记而非物理擦除?这对同步有何好处?
解读
国内面试官问这一题,核心想验证两点:
- 你是否真正理解 CouchDB 的“多主复制 + 增量同步”机制;
- 能否把“删除也是数据”这一理念,转化为线上可落地的容灾、溯源、合规方案。
回答时切忌只背概念,要结合国内互联网场景(如移动办公、小程序离线、多活机房)给出“为什么必须保留墓碑”的实战理由。
知识点
- MVCC 版本链:每份文档按 sequence 有序追加,物理擦除会打断版本链,导致其他节点的同一文档无法继续追加。
- 墓碑(tombstone)机制:
_deleted:true本质是一个新版本,携带_id、_rev、_deleted与最小体积,既宣告删除又保留版本号。 - 变更源(_changes feed):同步双方按
last_seq做增量比对;若物理擦除,feed 里将缺失事件,对端无法感知。 - 国内合规要求:等保 2.0、个人信息保护法均要求数据操作可审计;墓碑留痕可直接作为“删除日志”供监管抽查。
- 冲突解决:多主场景下,若 A 节点删除而 B 节点同时更新,墓碑版本号更高,系统可自动识别“删除优先”或“更新优先”策略,避免静默数据回归。
- 压缩与回收:
compaction会物理清理墓碑以外的旧版本,但默认保留最新墓碑,兼顾磁盘回收与同步正确性。
答案
CouchDB 采用多主对等复制,所有变更必须通过 _changes 序列化到对端。若直接物理擦除,被删文档将从变更源中消失,对端因缺失事件而持续保留旧版本,造成数据回潮与静默不一致。
引入 _deleted:true 墓碑后:
- 删除动作被当做一个新版本写入本地 MVCC,序列号递增,确保能出现在
_changes; - 同步时,对端收到墓碑即可安全丢弃本地相同文档,并更新自己的
last_seq,实现增量对齐; - 墓碑体积仅百字节级别,对磁盘与带宽影响极小;
- 国内监管审计需要“谁何时删了哪条数据”,墓碑天然携带
_id、_rev、时间戳,无需额外日志组件即可满足合规; - 在多活机房或移动离线场景,网络分区恢复后,墓碑通过更高版本号自动覆盖冲突更新,保证删除意图最终一致。
因此,_deleted:true既是 CouchDB 复制协议的正确性基石,也是国内业务可溯源、可容灾、可合规的关键设计。
拓展思考
-
墓碑无限增长怎么办?
可设置purge操作永久抹除墓碑,但会重置所有节点的uuid,相当于重建副本,需选在低峰期全链路停机执行,国内大厂通常一年一次并提前在监管备案。 -
如何证明“删除”已同步到所有边缘节点?
在_changes里过滤doc._deleted === true并对比last_seq,当所有边缘节点的last_seq ≥ 墓碑所在 seq且本地读返回404,即可出具删除闭环报告,满足个人信息保护法第47条“确认删除完成”要求。 -
与国产数据库混搭时的注意点
若下游同步到MySQL做报表,需在Kafka 转换层把墓碑转成DELETE语句,并保留原 _id 作为业务主键,否则会出现重复插入。国内金融项目常把这一逻辑封装成数据总线中间件,通过等保三级测评时才被认可。