如何验证“_bulk_docs”在节点失联时重试无重复写入?

解读

国内生产环境普遍采用 三节点以上 CouchDB 集群 + 反向代理(Nginx/HAProxy) 的部署模式,网络抖动、容器重启、K8s Pod 漂移都会导致“节点失联”。
_bulk_docs 是业务批量写入口,幂等性 直接决定重试时是否产生脏数据。
面试官想确认两点:

  1. 你是否清楚 CouchDB 的 幂等机制(revision 树 + 批量事务)。
  2. 你是否能设计 可落地的验证方案,而不是背文档。

知识点

  1. 幂等基石:_bulk_docs 默认带 new_edits=true,CouchDB 以 (id,rev) 作为主键,相同 rev 重复提交会被视为“无新内容”而直接返回 201,不会生成新版本
  2. 重试场景
    • 客户端收到 502/504/连接重置 即认为“失联”,立即换节点重试。
    • 若第一次其实已写成功,第二次请求仍带同一批 (id,rev),CouchDB 会返回 已存在响应,但不会新增 rev。
  3. 验证关键:需要构造“客户端以为失败、实际已成功”的灰色场景,并用 rev 历史佐证没有分叉。
  4. 国内常用工具
    • 压测:JMeter + 自写 JSR223 脚本,可控制 TCP 断开。
    • 抓包:Wireshark 验证是否出现两次成功的 201 响应体。
    • 审计:CouchDB 日志开 debug=true,grep HTTP/1.1 201Updating rev 确认 rev 只出现一次。

答案

验证步骤(可直接在面试白板写出):

  1. 准备数据
    构造 100 条 JSON,每条含自定义字段 traceId=uuid,方便后续对账。

  2. 集群埋点
    在负载均衡层(如 Nginx)对 _bulk_docs 接口设置 50% 概率的 100 ms delay + 10% 概率的 TCP reset,模拟“半失联”。

  3. 客户端重试策略
    用 Python requests 封装:

    • 超时 3 s,读超时 2 s;
    • 遇到 ConnectionError、502、504 立即换节点重试,最多 2 次;
    • 每次重试使用相同请求体与相同 X-CouchDB-OV: bulk-docs,确保 rev 不变。
  4. 采集证据
    a) 脚本把每次请求与响应落本地日志,记录返回的 rev
    b) 压测结束后,用 _all_docs?include_docs=true 拉取全量文档,对比 traceIdrev

    • 若重试导致重复写入,同一 traceId 会出现 两条不同 rev
    • 幂等正常时,一条 traceId 只对应一条 rev
  5. 交叉验证
    打开 CouchDB 的 /_log?bytes=1000000,grep traceId,确认只有一条“successful”记录,无“conflict”或“new revision”。

  6. 报告结论
    出具小报告:
    “在 10 万次批量写入、模拟 2187 次节点失联重试中,零文档出现 rev 分叉,验证 _bulk_docs 重试无重复写入。”

拓展思考

  1. 如果业务用了 new_edits=false(导入历史数据),上述方法会失效,此时必须在应用层给每条文档预生成 deterministic rev(如 1-abc),并自行保证全局唯一,否则重试就会产生冲突。
  2. 国内金融场景会要求 “写后读”对账,可在第 4 步后立刻调用 _find 做二次校验,确保 read-your-write 语义。
  3. 当集群跨两地三中心,RTT>80 ms 时,建议把重试次数降到 1 次,避免客户端堆积;同时把 _bulk_docs 批次大小限制在 500 条以内,降低事务冲突窗口。