如何基于 _rev 实现模型回滚?

解读

国内面试官抛出该题,核心想验证三件事:

  1. 候选人是否真正理解 CouchDB “多版本并发控制(MVCC)” 机制,而不是把 _rev 当成传统关系库的“版本号”;
  2. 能否在 离线优先、多主复制 场景下,安全地把文档恢复到任意历史版本,且不会引发冲突;
  3. “回滚” 一词的边界认知:是业务语义回滚,还是物理数据回滚?能否给出可落地的工程方案,并权衡一致性、可用性与合规性(等保、审计、GDPR 国内落地要求)。

一句话:不是简单“get 旧版本再 put”,而是要在 “分布式、无锁、最终一致” 约束下,用 _rev 构造一条可验证、可追溯、可重放的回滚链路。

知识点

  1. _rev 的物理含义:以 1-abc2-def 形式出现,前半截是版本计数器,后半截是摘要哈希,任何字段级变更都会生成全新 _rev,旧版本立即变为**“tombstone 候选”**。
  2. MVCC 与追加写:CouchDB 永不原地更新,旧版本在磁盘上保留直到数据库压缩(compaction);因此只要 compaction 未触发,即可通过 revs_info 或 revs=true 拿到全量历史
  3. 显式回滚 vs 隐式回滚
    • 显式:业务层调用 GET /db/doc?revs=true&open_revs=[“2-def”] 拿到指定版本,再 PUT /db/doc 把旧体+旧附件重新写入,生成新 _rev 3-def(注意哈希会变)。
    • 隐式:利用 /_revs_diff/_bulk_docs?new_edits=false 把历史版本直接插回,使其成为最新分支,但需自备完整 revision lineage,否则集群会拒绝。
  4. 冲突窗口:多主复制场景下,若回滚期间其他节点已产生 3-xyz,则回滚后会形成**“分支冲突”**,必须后续用 /_conflicts 视图做冲突裁剪。
  5. 合规与审计:国内金融、政务云要求**“数据可回滚到任意时间点且可审计”,因此须把回滚理由、操作者、审批单号**写入文档的 _local/audit 或独立审计库,并打开 write_quorum≥2 防止回滚过程中出现“脑裂”写入。
  6. 性能陷阱
    • 关闭 auto_compaction 才能保留历史;
    • 回滚大批量文档时,用 /_bulk_docs 批次≤5000,避免 Erlang 虚拟机 GC 抖动;
    • 若附件较大,需把 _attachments 一并带回,否则会出现 “附件丢失型伪回滚”

答案

线上实战回滚七步法(可直接写进面试白板):

  1. 前置检查
    调用 GET /db/doc?revs_info=true 确认目标版本仍存在;若已被 compaction 回收,需先从 快照备份对象存储冷备 恢复缺失 rev。

  2. 生成回滚指令记录
    _local/rollback:{uuid} 写入 {“docId”:”xxx”,”targetRev”:”2-def”,”reason”:”业务冲正”,”operator”:”zhangsan”,”approvalNo”:”20240607012”},满足国内审计要求。

  3. 拉取完整历史体
    GET /db/doc?rev=2-def&attachments=true&revs=true 拿到 body + _attachments + _revisions 数组。

  4. 构造回滚写入
    把上一步 JSON 中 _rev 字段删除(让 CouchDB 生成新 rev),保留 _revisions 数组,表示“我知道自己在插回历史”,调用

    PUT /db/doc?new_edits=false  
    { … 原 body …, _revisions: { start:2, ids:[“def”,”abc”] } }
    

    成功后得到 3-def,实现**“逻辑回滚”**。

  5. 冲突检测与收敛
    回滚完成后立即 GET /db/doc?conflicts=true,若返回 “_conflicts”:[“3-xyz”],则通过业务规则选择胜者,调用 DELETE /db/doc?rev=3-xyz 或合并字段,确保最终一致

  6. 复制域同步
    多主集群中,用 /_replicate 把回滚结果推给所有节点,并设置 “retry”:true 直到 “no_changes”:true,防止**“回滚漂移”**。

  7. 收尾与监控
    打开 auto_compaction,回滚操作写入 Prometheus 指标 couchdb_rollback_total{docId,operator},方便告警;同时把 _local/rollback:{uuid} 移到审计库,本地节点可清理。

通过以上七步,可在零停机、无锁、分布式条件下,把任意文档安全回滚到指定 _rev,并满足国内等保三级对**“数据可追溯、可重放”**的合规要求。

拓展思考

  1. 如果业务要求**“整库级时间点回滚”,单文档 _rev 机制已不够用,需引入 CouchDB 3.x 的 “point-in-time” 备份插件Ceph RBD 快照卷级回滚**,再叠加 增量 _changes feed 重放,面试可主动提及,展示**“版本级+存储级”**混合方案视野。
  2. 对**“高频回滚”场景(如财务冲正),可预置 “回滚模板文档”,把常见历史 rev 缓存到 Redis Cluster,减少 30% 以上磁盘 IO;同时利用 Erlang 热升级 做到“回滚逻辑不停服”**,体现对 CouchDB 底层 BEAM 虚拟机的深度理解。
  3. 国内信创替代背景下,CouchDB 常被要求与国产 ARM 服务器 + 麒麟 OS 混合部署,此时回滚操作需验证 CRC32 硬件指令一致性,防止**“哈希漂移”**导致 _rev 冲突;面试中若能提到使用 erlang:md5kunpeng crypto engine 做交叉校验,可瞬间拉开与其他候选人的差距。