解释“all_or_nothing”与“new_edits:false”在批量更新中的语义差异。

解读

在国内 CouchDB 面试中,这道题常被用来区分候选人是否真正用过“离线优先”与“多主复制”场景。
面试官期望你不仅背出定义,还要能说出冲突产生机制、版本树变化、磁盘写入次数差异、对同步的影响四个维度,否则会被追问“那如果两个节点同时用这两种方式写同一条文档会怎样?”

知识点

  1. 批量更新入口:_bulk_docs
  2. 参数位置:二者都是顶层 JSON 字段,与 docs 数组同级。
  3. 冲突模型:CouchDB 用多版本 + 增量树保存冲突,每个版本以 {_id,_rev} 定位。
  4. 写路径:先走预提交验证 → 磁盘追加 → 视图增量 → 复制日志,二者在第二步产生差异。
  5. 国内业务痛点:移动端离线写、省流量同步、灰度双写,必须让冲突可预期。

答案

  1. 语义定位

    • all_or_nothing事务语义:要求整批文档要么全部落盘,要么全部回滚;只要任意一条校验失败(权限、schema、磁盘满),整个请求返回 417 Expectation Failed,已预写的临时文件会被丢弃。
    • new_edits:false版本覆盖语义:告诉 CouchDB“我带来的 _rev 是外部生成的,不要重新分配”,用于多主复制、离线合并、第三方系统导数;只要 _rev 在版本树中存在且未被删除,就能直接插入,不保证原子性
  2. 版本号处理

    • all_or_nothing 仍走正常版本号递增(_rev 的数值部分 +1),所以后续在线写无需顾虑。
    • new_edits:false 完全信任传入 _rev,即使它插在树中间或产生分支,CouchDB 只检查哈希是否匹配,不会重新计算;因此可能瞬间出现冲突节点
  3. 冲突与可见性

    • all_or_nothing 不会产生额外冲突,失败即无痕迹。
    • new_edits:false 可能把已删除的叶节点重新拉活,或把兄弟分支带回主干,导致查询端必须处理 conflicts=true 的多版本数组。
  4. 性能与日志

    • all_or_nothing 需要写前加锁整批校验,在 4 核 8 G 的国产云主机上,千条文档批能多出 10-15 ms 延迟。
    • new_edits:false 跳过验证与 rev 生成,磁盘只做追加写,同规格主机可省约 20% IO;但若版本冲突过多,后续视图索引会膨胀,读性能反而下降。
  5. 使用场景口诀
    国内落地记住一句话:“在线业务用 all_or_nothing,离线合流用 new_edits:false”
    例如,电商大促时库存扣减走 all_or_nothing 保证不超卖;末端骑手 APP 离线签收后回传,走 new_edits:false 把本地生成的 _rev 直接写回云端,再让客服系统按业务时间戳解决冲突。

拓展思考

  1. 如果同一批 docs 里混用两种模式(顶层带 new_edits:false,单条 doc 里再写 all_or_nothing:true),CouchDB 会直接返回 400 bad_request,因为二者互斥;面试时可主动说出这一点,体现看过源码。
  2. 跨省多活架构里,北京与上海同时用 new_edits:false 写同一文档,_rev 相同但内容不同,会产生**“兄弟冲突”;此时需要在业务层加 region_timestamp 字段,用应用层合并函数**胜出,而不是依赖 CouchDB 自动解决。
  3. 国内等保 2.0 要求操作可审计all_or_nothing 失败记录只留在 erlang 日志,不会进 _changesnew_edits:false 成功后会带 doc_ids 写进 changes 并标记 conflict:true,方便后续稽核。设计合规方案时要利用这一差异。