给出一种基于“winningRevsOnly=true”的冲突自动解决策略及其风险。

解读

面试官真正想考察的是:

  1. 你是否理解 CouchDB 的 多版本并发与冲突模型(MVCC + 多主复制)。
  2. 你是否知道 winningRevsOnly=true 只是 “只返回获胜分支” 的读取优化开关,而非真正的“自动解决”机制。
  3. 你能否设计一套 业务层可落地 的冲突消解流程,并冷静评估它在国内真实业务场景下的 数据一致性风险回滚难度
  4. 你能否把技术方案讲成 “可灰度、可监控、可回滚” 的互联网打法,而不是背文档。

知识点

  • 冲突版本(conflict rev)与获胜版本(winning rev):CouchDB 按 revision 树 + 确定性规则 自动选“赢”,但“赢”≠“对”。
  • winningRevsOnly=true只把获胜分支返回给客户端,冲突版本仍在磁盘,视图索引仍带残枝,DB 体积继续膨胀。
  • 自动解决三要素:①冲突检测 ②业务规则 ③写入新“解决”版本。
  • 国内合规风险:金融、支付、医疗等强审计场景,“静默丢数据”即违规;必须留痕、可追踪、支持司法举证。
  • 灰度三板斧:影子读 → 双写对比 → 全量切换,每一步都有 回滚窗口对账脚本

答案

策略(六步闭环,可直接落地)

  1. 检测:在 change feed 里监听 doc._conflicts 长度 >0 的事件,把冲突文档 id 推入 Kafka 队列。
  2. 拉取:消费者带 ?conflicts=true&winningRevsOnly=false 拿到 全量冲突分支,防止“只看赢者”造成信息丢失。
  3. 规则:用 业务语义层 做三路合并
    • 时间戳字段:取 最新业务时间(非 CouchDB 时间)。
    • 金额字段:取 累加值 并记录“调账凭证号”到 audit_log
    • 数组字段:做 集合并集,并标记“合并来源”哈希。
  4. 写回:把合并结果写为新版本,显式把旧冲突分支放入 _deleted_conflicts,同时在新文档保留 merged_revs[] 数组,方便审计。
  5. 校验:双写影子表,T+1 对账脚本比对影子表与官方表,差异超 0.01% 自动告警。
  6. 回滚:若发现业务错误,用 保留的 merged_revs[] 可快速重建冲突版本,走人工仲裁入口。

风险

  • 静默数据丢失:规则覆盖字段时,若业务时间源被篡改,会永久丢弃正确版本,且 winningRevsOnly=true 让前端无感知。
  • 膨胀与性能:冲突版本虽对客户端不可见,但 视图索引仍遍历全部分支,导致 btree 深度 + 磁盘翻倍,国内云主机 IO 预算容易打满。
  • 合规审计:金融场景下,“自动合并”属于账务调整,必须留原始凭证;若未把冲突版本写入冷备,监管现场检查直接判不合格
  • 灰度翻车:一旦在全量打开 winningRevsOnly=true旧客户端可能缓存了非赢版本,回滚时会出现“时光倒流”现象,用户端数据闪现回老版本,引发投诉。

拓展思考

  1. 如果业务无法容忍任何自动合并,可把策略退化为 “冲突标记 + 人工仲裁”:检测后只打 conflict_flag=true,前端弹窗引导用户手动选择;同时用 小程序消息 把仲裁入口推给国内用户的微信,实现 72 小时无操作则客服介入 的闭环。
  2. 超高频写入 场景(如秒杀库存),可引入 Redis 前置排重 + CouchDB 后置对账“漏斗”模型:Redis 用 Lua 脚本保证原子扣减,CouchDB 仅做最终批次写入,冲突概率降至 0.1% 以下,从而 绕过 MVCC 冲突 本身。
  3. 跨省多活 架构里,给每个数据中心分配 单调递增的 region-bit,写入时把 region-bit 编码进 revision 后缀,让冲突版本在业务层即可预判归属,合并规则简化为“本地优先 + 金额对冲”,大幅降低仲裁复杂度。