密钥轮换后,如何批量 re-encrypt 历史文档而不停机?

解读

在国内金融、政务、医疗等合规场景,国密算法轮换周期≤1年已成硬性要求。CouchDB 本身只负责存储密文,真正的加解密逻辑在业务层或中间层完成,因此“不停机 re-encrypt”的核心是:在持续读写的前提下,完成全量历史文档的加密版本升级,并确保新旧密钥混用期间的数据一致性、可用性与可回滚性。面试官想验证的是:你是否理解 CouchDB 的 MVCC、复制、视图机制,能否设计一套灰度可控、可观测、可回滚的在线批量轮换方案,而不是简单回答“写个脚本遍历”。

知识点

  1. MVCC 与 _rev 机制:CouchDB 每次更新生成新 _rev,旧版仍可读,天然支持“双写双读”灰度。
  2. _changes feed:支持 since=now 持续监听,用于实时捕获增量。
  3. _design/update 函数:可在写路径实时 re-encrypt,实现“写时旋转”。
  4. _bulk_docs:批量提交,配合 all_or_nothing=false 可部分成功,失败重试不阻塞。
  5. _replicator:内部复制任务可带自定义 filter,用于灰度切流。
  6. 国密合规:SM4 轮换需保留**密钥版本号(kv)**字段,密文前缀标识新旧密钥。
  7. 零停机指标:RPO=0(不丢写)、RTO<30s(切密钥窗口)、回滚时间<5min。

答案

整体采用“双密钥双读 + 增量写时旋转 + 后台批量追赶 + 灰度切换”四阶段方案,全程不锁库、不中断业务。

阶段 1:前置准备

  1. 在应用层引入密钥路由器:根据文档中的 kv 字段自动选择解密密钥,支持同时加载新旧两把 SM4 密钥。
  2. 新建 _design/crypto 视图,输出 _idkv,用于后续批量扫描。
  3. 打开用户认证 + db-admin 强制 HTTPS,防止中间人降级。

阶段 2:增量写时旋转(在线)

  1. 在 update_handler(或应用 DAO)中统一拦截写请求:
    • 若传入明文,用新密钥加密,写入 kv=new
    • 若传入密文且 kv=old,立即用新密钥 re-encrypt 后再写,保证增量文档永远使用最新密钥
  2. 开启 _changes?feed=continuous&since=now 监听,实时统计“写时旋转”成功率,低于 99.9% 自动告警。

阶段 3:后台批量追赶(异步)

  1. 使用 _design/crypto 视图分页扫描 kv=old 的文档,按 1 000 条/批提交 _bulk_docs
  2. 每批携带 new_edits=false 并指定最新 _rev,避免冲突;若冲突则延迟 1s 重试,重试 3 次后落入死信队列,人工复核。
  3. 任务部署在 K8s CronJob,限流 50% CPU,通过 _active_tasks 接口实时上报进度;全量 1 亿条文档可在 4h 内完成(8 节点,每节点 8 并发)。

阶段 4:灰度切换与清理

  1. 当追赶进度 ≥99.5% 且持续 30min 无 kv=old 增量时,启动灰度切换:
    • 修改密钥路由器默认加密密钥为新密钥;
    • 通过 _replicator 建立灰度任务,把 5% 流量复制到预发集群,验证解密正确性;
    • 无异常后全量切换,老密钥保留 72h 用于应急回滚。
  2. 切换完成后,删除视图、停批任务、清理死信,出具《密钥轮换报告》留存备审。

通过以上四阶段,业务读写 QPS 零抖动,轮换过程可观测、可回滚,满足国内等保 3 级与国密合规要求。

拓展思考

  1. 如果集群跨两地三中心,_changes 顺序在异地复制时可能出现延迟,如何确保“增量写时旋转”不会重复加密?
    可在 update_handler 里加分布式锁(Redis Redlock),或利用 _rev 前缀做幂等令牌。
  2. 当单个文档 ≥20 MB(附件)时,_bulk_docs 压力陡增,可改用CouchDB 2.3+ 的 _attachments 流式加密接口,边读边写,降低内存峰值。
  3. 未来若引入硬件加密机(HSM),密钥路由器需支持 PKCS#11 动态加载,同时把批量追赶任务拆成MapReduce 作业,在加密机池里并行完成,整体时间可缩短 60%。