如何原子性更新设计文档而不影响正在进行的查询?
解读
国内生产环境普遍要求“零停机发布”,而 CouchDB 的设计文档(_design)本质上是普通文档,更新后其索引会异步重建。如果直接 PUT 同名设计文档,Map/Reduce 视图会瞬间失效,导致线上查询 404 或返回陈旧数据,这在金融、电商场景属于P0 事故。因此面试官真正想考察的是:能否在不停服务的前提下,让新视图“平滑”上线,且对客户端透明。
知识点
- 设计文档版本化命名:利用 _design/{name}-{version} 约定,如 _design/order_v1 → _design/order_v2,实现“蓝绿视图”。
- _rev 机制与 MVCC:CouchDB 的每次写都会生成新 _rev,旧版本仍可读,保证原子性写入与快照隔离。
- stale=ok & update=lazy:查询参数可强制读旧索引,避免重建期间抖动。
- 复制过滤函数:在双向同步场景下,通过过滤函数屏蔽中间版本设计文档,防止移动端拉取未完成索引。
- 国内云厂商限制:阿里云 CouchDB 版对视图重建并发数有限流,需提前在控制台提工单白名单扩容,否则重建时间从分钟级变成小时级。
答案
步骤如下,全程零中断:
- 版本化写入
PUT /db/_design/order_v2
附带新 Map/Reduce 函数,_id 中携带版本号,避免覆盖线上版本。 - 预构建索引
在业务低峰(国内通常选凌晨 02:00-04:00)触发
GET /db/_design/order_v2/_view/by_user?limit=1&stale=update_after
利用 update_after 让 CouchDB 后台构建索引,不阻塞线上查询。 - 灰度切换
通过 nginx+lua 在 API 网关层把部分用户流量(如 uid%10==0)路由到 order_v2,验证结果一致性。 - 原子别名替换
确认索引构建完毕且数据校验通过后,在一个本地事务内执行:- 将 order_v1 改名为 order_v1_deprecated
- 将 order_v2 改名为 order_v1(_id 改回线上别名,_rev 沿用新生成值)
CouchDB 的 MVCC 保证上述两步在单节点原子;集群场景下,使用 /_node/_local/_config 把 rename 操作封装成单文档批量更新,避免中间状态。
- 清理
保留 order_v1_deprecated 72 小时(应对国内监管审计要求),随后 DELETE 并压缩文件。
拓展思考
- 如果业务不允许双份索引占用磁盘,可在私有云部署 CouchDB 3.3+ 的 “view compaction” 特性,在替换完成后立即触发 在线压缩,节省 30% 空间。
- 对于移动端离线同步场景,可把设计文档版本号写入 replication doc 的 filter 字段,确保 App 端只在 Wi-Fi 环境拉取新视图,避免4G 流量超标被用户投诉。
- 国内等保 2.0 要求操作可审计,建议把每次设计文档变更写入 Kafka 审计队列,字段包括操作人、工单号、灰度比例、回滚预案,方便公安部现场检查时一键出具报告。