如何生成训练集快照而不锁定?
解读
面试官问的是“训练集快照”,本质上是把 CouchDB 某一时刻的数据导出成固定集合,用于离线训练或模型回溯;同时要求“不锁定”,即读写不能阻塞线上业务。国内互联网场景下,训练数据往往 TB 级,且 7×24 对外服务,任何秒级锁都会触发告警,因此必须给出零阻塞、可重试、可并行的方案。回答时要先澄清“快照”≠“备份”,再给出复制+增量+过滤的组合打法,体现对 CouchDB 内部机制(MVCC、_seq、_changes、shard)的透彻理解。
知识点
- MVCC 与 seq 树:CouchDB 采用多版本并发控制,每次写操作产生新 doc_rev 并递增 _seq,读始终无锁。
- _changes feed:持续获取“数据库级 WAL”,支持
since=now与limit=n,可断点续传。 - 复制协议:CouchDB 原生多主复制基于
_changes+_revs_diff+_bulk_get,本身不锁库。 - shard 与 range:集群版按 hash 分片,每个 shard 独立 seq,可并行抓取。
- 过滤函数:
_changes?filter=…可在服务端过滤训练所需字段,减少 IO。 - HTTP 条件 GET:利用
ETag/If-None-Match实现客户端缓存,避免重复拉取。 - 国内合规:快照若含用户数据,需脱敏或加密落盘,满足《个人信息保护法》要求。
答案
线上零锁快照的四步落地法:
- 选起始 seq:对业务低峰期(如凌晨 02:00)执行
GET /db/_changes?limit=1&descending=true拿到当前最大 seq,记为end_seq;再回退 5 min 取start_seq = end_seq - 5min_offset,保证时间窗覆盖。 - 并行拉取:按 shard 维度拆任务,每个 worker 负责一段连续 seq 区间,调用
GET /db/_changes?since={start}&limit=5000&include_docs=true&filter=app/train_filter
其中train_filter是设计好的过滤函数,只返回训练所需字段,不返回附件,降低 70% 流量。 - 本地拼装:worker 把每批 docs 按
_id去重(同一 doc 可能有多 rev),保留winning rev,写成本地 JSON Lines;同时记录最后一条 seq 作为断点。 - 完整性校验:全部 shard 完成后,汇总 seq 区间,二次抽样 1‰ 文档做哈希比对,确认与线上当前 winning rev 一致;若不一致,仅重跑对应 seq 区间,无需全量,实现“可重试”。
整个过程全程只读,不触发 compaction,不占用写锁,对线上 QPS 影响 <1%,符合国内大厂 SLA。
拓展思考
- 增量快照:每天凌晨只拉取
since=yesterday_seq的变更,结合 _deleted 标志做“逻辑删除”样本,保证训练集与线上分布一致。 - 多云容灾:把快照直接流式写入对象存储 OSS(阿里云)或 COS(腾讯云),利用 CouchDB 的
multipart/mixed响应边拉边上云,本地不落盘,节省磁盘 IO。 - 时序一致性:若训练需要跨库关联(如用户画像库 + 日志库),可在每个库快照时记录全局时间戳向量(vector clock),训练阶段按向量对齐,避免“读到未来”样本。
- 合规脱敏:在过滤函数里调用内置
crypto模块,对手机号、身份证号做可逆加密(AES-256-GCM),密钥托管在 KMS,满足国内监管“可用不可见”要求。