如何回传梯度?
解读
在国内 CouchDB 面试中,面试官抛出“如何回传梯度”并不是在问机器学习里的反向传播,而是把“梯度”当成业务增量或冲突解决后的差异数据的隐喻。CouchDB 的核心是多主复制与最终一致性,当两端数据库各自产生写操作后,系统需要把“差异(梯度)”回传到对端,以保证数据收敛。回答时要紧扣 CouchDB 的复制协议、冲突检测、视图增量与批量写回机制,体现你对离线优先场景下数据同步闭环的掌握。
知识点
- 复制序列号(seq)与校验点:CouchDB 用
_changes?since=seq做增量拉取,seq 就是“梯度”的起始刻度。 - 多版本并发(MVCC)与修订树:每个 doc 的
_rev构成树状历史,回传时必须携带完整分支,否则对端无法合并。 - 冲突文档(conflict)与胜出策略:回传后若出现冲突,需通过
winning_revs_only=false把非胜出分支也写回,保证两端视图一致。 - 批量写回(_bulk_docs):梯度数据最终通过 POST /{target}/_bulk_docs?new_edits=false 一次性回传,避免多次往返。
- 过滤函数与选择复制:国内移动网络常按省份、租户做分片,可通过设计文档里的
filter函数只回传特定梯度,节省 4G/5G 流量。 - 离线队列与本地 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 并自行处理部分失败,保证梯度不丢不重。
拓展思考
- 双向梯度风暴:当两端同时高频写入,seq 差值会滚雪球。国内生产环境常用写入限流 + 梯度压缩(把连续修订合并成一次
_bulk_docs)降低带宽。 - 敏感数据脱敏:金融场景下梯度里可能含身份证号,可在过滤函数里用
doc._id.startsWith('shadow_')把真实字段替换为哈希,回传后再由合规服务补全。 - Serverless 回传:把梯度作为对象存入阿里云 OSS 或腾讯云 COS,通过函数计算触发器异步回传,解决 CouchDB 集群跨地域复制延迟大的痛点。
- 版本降级兼容:老版本 CouchDB 不支持
?new_edits=false,需先升级至 2.3+,否则梯度会被误写为新修订,导致冲突爆炸。