解释“all_or_nothing”与“new_edits:false”在批量更新中的语义差异。
解读
在国内 CouchDB 面试中,这道题常被用来区分候选人是否真正用过“离线优先”与“多主复制”场景。
面试官期望你不仅背出定义,还要能说出冲突产生机制、版本树变化、磁盘写入次数差异、对同步的影响四个维度,否则会被追问“那如果两个节点同时用这两种方式写同一条文档会怎样?”
知识点
- 批量更新入口:
_bulk_docs - 参数位置:二者都是顶层 JSON 字段,与
docs数组同级。 - 冲突模型:CouchDB 用多版本 + 增量树保存冲突,每个版本以
{_id,_rev}定位。 - 写路径:先走预提交验证 → 磁盘追加 → 视图增量 → 复制日志,二者在第二步产生差异。
- 国内业务痛点:移动端离线写、省流量同步、灰度双写,必须让冲突可预期。
答案
-
语义定位
all_or_nothing是事务语义:要求整批文档要么全部落盘,要么全部回滚;只要任意一条校验失败(权限、schema、磁盘满),整个请求返回 417 Expectation Failed,已预写的临时文件会被丢弃。new_edits:false是版本覆盖语义:告诉 CouchDB“我带来的 _rev 是外部生成的,不要重新分配”,用于多主复制、离线合并、第三方系统导数;只要 _rev 在版本树中存在且未被删除,就能直接插入,不保证原子性。
-
版本号处理
all_or_nothing仍走正常版本号递增(_rev 的数值部分 +1),所以后续在线写无需顾虑。new_edits:false完全信任传入 _rev,即使它插在树中间或产生分支,CouchDB 只检查哈希是否匹配,不会重新计算;因此可能瞬间出现冲突节点。
-
冲突与可见性
all_or_nothing不会产生额外冲突,失败即无痕迹。new_edits:false可能把已删除的叶节点重新拉活,或把兄弟分支带回主干,导致查询端必须处理conflicts=true的多版本数组。
-
性能与日志
all_or_nothing需要写前加锁整批校验,在 4 核 8 G 的国产云主机上,千条文档批能多出 10-15 ms 延迟。new_edits:false跳过验证与 rev 生成,磁盘只做追加写,同规格主机可省约 20% IO;但若版本冲突过多,后续视图索引会膨胀,读性能反而下降。
-
使用场景口诀
国内落地记住一句话:“在线业务用 all_or_nothing,离线合流用 new_edits:false”。
例如,电商大促时库存扣减走all_or_nothing保证不超卖;末端骑手 APP 离线签收后回传,走new_edits:false把本地生成的 _rev 直接写回云端,再让客服系统按业务时间戳解决冲突。
拓展思考
- 如果同一批 docs 里混用两种模式(顶层带
new_edits:false,单条 doc 里再写all_or_nothing:true),CouchDB 会直接返回 400 bad_request,因为二者互斥;面试时可主动说出这一点,体现看过源码。 - 在跨省多活架构里,北京与上海同时用
new_edits:false写同一文档,_rev 相同但内容不同,会产生**“兄弟冲突”;此时需要在业务层加region_timestamp字段,用应用层合并函数**胜出,而不是依赖 CouchDB 自动解决。 - 国内等保 2.0 要求操作可审计,
all_or_nothing失败记录只留在 erlang 日志,不会进_changes;new_edits:false成功后会带doc_ids写进changes并标记conflict:true,方便后续稽核。设计合规方案时要利用这一差异。