如何增量导出避免全表?

解读

面试官问“增量导出”而不是“全量导出”,核心想确认两点:

  1. 你是否理解 CouchDB 最终一致性MVCC(多版本并发控制) 机制带来的“变更感知”能力;
  2. 你是否能在国内真实的 政务内网、混合云、移动边缘节点 等受限场景下,给出低带宽、可断点续传、对线上集群几乎零影响的方案。
    回答时切忌只背 API,要体现“场景+原理+落地细节”。

知识点

  1. seq 索引(update_seq):每个数据库维护一个单调递增的序号,每次写操作都会推高该值,是增量判断的唯一依据。
  2. _changes _feed
    • 支持 since=now|seq 参数,实现“从某一点之后”的增量拉取;
    • 支持 feed=longpoll|continuous 做实时流式消费;
    • 支持 include_docs=true 直接带回文档内容,避免二次 GET;
    • 支持 filter=xxx 做服务端过滤,减少网络传输。
  3. last_seq 持久化:客户端必须本地落盘上一次成功的 last_seq,进程重启后可断点续传。
  4. 压缩与批次:国内专线带宽常只有 2–10 Mbps,建议每批 500–1000 条或 2 MB 做一次提交,并开启 Accept-Encoding: gzip
  5. 权限与隔离:政务场景常用 CouchDB 1.7 安全模块2.x 的 _membership+_security 对象,确保增量接口只暴露给白名单 IP 与证书用户。
  6. 冲突与删除:增量流里 deleted:true 的文档需要硬删除还是打标记,要与下游系统约定一致,避免数据漂移。
  7. 国内监管:若数据含 PII(个人信息),增量导出必须走 脱敏网关 并记录 审计日志,满足《个人信息保护法》第 38 条跨境评估要求。

答案

“增量导出”在 CouchDB 里标准做法是基于 _changes 接口的 seq 机制,具体步骤如下:

  1. 首次运行时调用 GET /db/_changes?limit=1 拿到当前 update_seq 作为基准点,不做全量拉取,仅记录该值。
  2. 后续周期任务携带 GET /db/_changes?since=<last_seq>&include_docs=true&limit=1000,得到新增、修改、删除的文档列表。
  3. 将返回的 last_seq 原子化写入本地 RocksDB 文件或 Redis,确保进程重启后可断点续传。
  4. 若数据量超大,可在服务端预置 filter 函数(设计文档里写 filters: {by_type: function(doc,req){return doc.type==='order';}}),减少 70% 无效传输。
  5. 对每条变更文档计算 sha1 与下游比对,实现幂等写入;遇到 deleted:true 则在下游做逻辑删除或物理删除,保持语义一致。
  6. 网络抖动场景下,利用 feed=longpoll&timeout=20000长轮询,比定时 5 s 轮询节省 40% 空请求,降低政务内网防火墙连接数压力。
  7. 最后,整个导出脚本以 systemd 服务 方式部署,异常退出时由 supervisor 自动拉起,保证 last_seq 不丢失,实现 7×24 小时稳定增量同步。

拓展思考

  1. 如果源库是 CouchDB 3.x 集群_changes 默认是分片局部序号,需拼接 shard=range 参数或改用 global_changes 系统库,避免多节点 seq 不一致导致漏数据。
  2. 当业务要求 秒级实时,可把 _changes?feed=continuous 接入 Go 写的 tailer 协程,通过 Zero-Copy 内存队列 直接写 Kafka,单核可压到 1 万 QPS,延迟 <200 ms。
  3. 若下游是 国产达梦、人大金仓 等关系型库,可在增量脚本里先写 临时表,再调用 MERGE INTO 一次性比对,减少行级更新开销,满足国产化替代验收。
  4. 面对 跨省容灾,可结合 CouchDB 2.x 的 _replicator 文档 做双向过滤同步,把增量导出与增量复制合二为一,节省 30% 带宽,同时满足《关键信息基础设施安全保护条例》中“异地活备”要求。