当需要回滚单个文档时,如何快速定位并 PUT 历史版本?
解读
国内生产环境常把 CouchDB 当“离线优先”的中央节点,手机端、小程序、IoT 网关随时可能上传脏数据。面试官问“回滚单个文档”,不是让你把整个库还原,而是在不停服、不影响其它文档的前提下,把某一条记录瞬间恢复到指定历史版本。考点集中在三点:
- 如何秒级定位目标版本(版本号、时间、业务键);
- 如何拿到完整历史文档(不是 diff);
- 如何原子写入并解决并发冲突,确保回滚可重入。
答不上来就直接掉分,答得太重(比如备份还原、全量复制)会被判“思路不清”。
知识点
- MVCC 机制:CouchDB 把每次更新存成新 revision,旧 revision 物理仍在文件里,直到数据库压缩。
- _rev 树:每个 doc 维护一棵 revision tree,叶子节点才是“可见”版本;回滚本质是把某个非叶子节点重新变回叶子。
- GET /db/doc?revs=true&open_revs=[...]:可一次性拉回指定 revision 的完整内容,无需遍历 _changes。
- PUT 带 new_edits=false:把历史 revision 直接插回 tree,不生成新 rev,实现“真回滚”而非“新增覆盖”。
- 国内常用封装:阿里云函数计算、腾讯云 SCF 里常把上述两步包成 HTTP 接口,供小程序前端一键“撤销”。
答案
分三步走,全部用官方 REST 接口,不依赖第三方插件,线上 200 ms 内完成:
-
定位版本号
若已知业务时间戳,用GET /db/_changes?doc_ids=["订单666"]&include_docs=true&since=0过滤出该 doc 的所有事件,按本地时间戳倒序找到目标 rev(形如3-abc)。
若已知 rev,直接跳过本步。 -
拉取历史文档
GET /db/订单666?rev=3-abc&revs=true
返回体里_revisions.ids可验证 tree 路径,确保该 rev 未被压缩(国内默认revs_limit=1000,7 天内必在)。 -
原子回滚
把第 2 步的 JSON 原样 PUT,但加请求头:PUT /db/订单666?new_edits=false Content-Type: application/json { "_id": "订单666", "_rev": "3-abc", ... // 其余字段保持历史值 }由于
new_edits=false,CouchDB 直接把3-abc重新标为最新叶子,不产生 4-def,实现“秒级回滚”。
返回 201 即成功;若返回 409,说明并发又写了新 rev,可重试或抛给业务层人工二次确认。
一句话总结:用 new_edits=false 把旧 rev 重新插回 tree,是官方支持且国内云厂商默认可用的“单文档回滚”黄金命令。
拓展思考
- 压缩窗口:国内磁盘紧张常把
revs_limit调到 100 以下,过期的 rev 会被物理删除,此时只能走备份通道(如couchdb-backup工具),面试官会追问“备份回滚”方案,需准备POST /db/_bulk_docs带all_or_nothing=true的脚本。 - 权限隔离:生产库多租户模式下,回滚接口必须走子库管理员密钥,防止普通用户伪造历史版本;可在 nginx 层加
X-Auth-CouchDB-Roles二次校验。 - 审计留痕:回滚后立刻写一条
{type:"rollback", from:"5-xyz", to:"3-abc", operator:"张三"}到同库审计文档,国内金融客户过等保三级时必须可追踪。