如何回传梯度?

解读

在国内 CouchDB 面试中,面试官抛出“如何回传梯度”并不是在问机器学习里的反向传播,而是把“梯度”当成业务增量或冲突解决后的差异数据的隐喻。CouchDB 的核心是多主复制与最终一致性,当两端数据库各自产生写操作后,系统需要把“差异(梯度)”回传到对端,以保证数据收敛。回答时要紧扣 CouchDB 的复制协议、冲突检测、视图增量与批量写回机制,体现你对离线优先场景下数据同步闭环的掌握。

知识点

  1. 复制序列号(seq)与校验点:CouchDB 用 _changes?since=seq 做增量拉取,seq 就是“梯度”的起始刻度。
  2. 多版本并发(MVCC)与修订树:每个 doc 的 _rev 构成树状历史,回传时必须携带完整分支,否则对端无法合并。
  3. 冲突文档(conflict)与胜出策略:回传后若出现冲突,需通过 winning_revs_only=false 把非胜出分支也写回,保证两端视图一致。
  4. 批量写回(_bulk_docs):梯度数据最终通过 POST /{target}/_bulk_docs?new_edits=false 一次性回传,避免多次往返。
  5. 过滤函数与选择复制:国内移动网络常按省份、租户做分片,可通过设计文档里的 filter 函数只回传特定梯度,节省 4G/5G 流量。
  6. 离线队列与本地 WAL:在 React Native、Flutter 或鸿蒙客户端里,梯度先写进本地 PouchDB 的 WebSQL/IndexedDB WAL,待网络恢复后触发 db.replicate.to(remote) 回传。

答案

“梯度”在这里指代两端数据库的差异数据。回传流程分四步:
计算梯度:源端调用 GET /source/_changes?since=last_seq&include_docs=true,拿到自上次校验点以来的所有修订,包括已删除文档。
打包冲突:若文档有多分支,把 ?_revisions=true&_attachments=true 一起带上,确保对端能重建修订树。
批量回传:通过 POST /target/_bulk_docs 把梯度数组一次性写入,参数带上 ?new_edits=false,告诉 CouchDB 直接使用现有 _rev 而不生成新修订,避免梯度被当成新数据。
确认校验点:回传成功后,客户端把响应中最新的 last_seq 记录到本地 _local/offset 文档,下次增量只拉取更新的梯度,形成闭环。

弱网或离线场景下,可先在边缘节点用 PouchDB 缓存梯度,待网络恢复后触发 retry: true, live: true 的同步任务,实现“断点续传”。若业务对顺序敏感,可在 _bulk_docs 前加 ?all_or_nothing=false 并自行处理部分失败,保证梯度不丢不重。

拓展思考

  1. 双向梯度风暴:当两端同时高频写入,seq 差值会滚雪球。国内生产环境常用写入限流 + 梯度压缩(把连续修订合并成一次 _bulk_docs)降低带宽。
  2. 敏感数据脱敏:金融场景下梯度里可能含身份证号,可在过滤函数里用 doc._id.startsWith('shadow_') 把真实字段替换为哈希,回传后再由合规服务补全。
  3. Serverless 回传:把梯度作为对象存入阿里云 OSS 或腾讯云 COS,通过函数计算触发器异步回传,解决 CouchDB 集群跨地域复制延迟大的痛点。
  4. 版本降级兼容:老版本 CouchDB 不支持 ?new_edits=false,需先升级至 2.3+,否则梯度会被误写为新修订,导致冲突爆炸。