如何删除过期?

解读

国内面试官问“如何删除过期”时,通常不是简单让你背一条 DELETE 语句,而是想验证三件事:

  1. 你是否理解 CouchDB 无内置 TTL 的现实;
  2. 你是否能把“过期”抽象成“业务时间戳 + 视图过滤”;
  3. 你是否能在高并发、大容量、离线优先场景下给出可灰度、可回滚、对同步友好的工程方案。

一句话:要证明你能在生产环境安全、可控、可观测地清理过期文档,而不是把数据库打挂。

知识点

  • CouchDB 无 TTL 机制,过期逻辑必须应用层或外围脚本实现。
  • 文档唯一版本号 _rev 决定并发安全,删除操作必须携带最新 _rev。
  • 删除本质是写入 _deleted:true 的墓碑(tombstone),墓碑会参与同步,不可物理抹除。
  • 视图(map/reduce)可过滤过期文档,但视图只是索引,不会回收空间。
  • _purge 可物理清除墓碑,但不可逆、不同步,国内合规场景慎用。
  • _changes feed 支持按时间戳回溯,可做增量清理调度。
  • 国内云厂商托管版(如腾讯云 TDSQL-CouchDB)禁止直接访问底层文件,_purge 需提工单审批
  • 离线优先场景下,移动端可能长时间不同步,清理前必须确认所有副本已收敛

答案

给面试官一个“三步走”生产级方案,每步都带灰度与观测

第一步:业务字段标记过期
在每条文档里显式写入 "expireAt": "2025-06-01T00:00:00Z"(ISO8601,UTC),确保字段有统一时区规范

第二步:视图过滤 + 增量扫描
新建设计文档 "_design/expiration",视图函数:

function (doc) {
  if (doc.expireAt && new Date(doc.expireAt) <= new Date()) {
    emit(doc._id, null);
  }
}

每天凌晨通过 /_view/expiration 分页拉取过期 ID 列表,批量 HEAD 请求获取最新 _rev,再按 100 条/批次发送 POST /{db}/_bulk_docs,payload 为:

{"docs":[{"_id":"xxx","_rev":"yyy","_deleted":true}]}

返回结果里统计 failuresconflicts,写入 Prometheus,告警阈值 1%

第三步:空间回收与合规

  1. 墓碑保留 7 天后再执行 _purge
  2. _purge 前必须
    • 确认 /_active_tasks 无复制任务;
    • /_scheduler/docs 检查所有节点 checkpoint 时间 > 墓碑写入时间
  3. _purge 操作走国内云厂商工单,双人复核,审计日志落盘 3 年
  4. 最后触发 /_compact 回收空间,监控磁盘下降速率 < 100 MB/min,防止 IO 打满。

拓展思考

  1. 如果业务要求 毫秒级过期,CouchDB 并不适合,可引入 Redis + CouchDB 双写,Redis 做滑动窗口,CouchDB 做最终归档。
  2. 多主复制场景下,不同节点时钟漂移可能导致“提前过期”,需用 NTP + 最大时钟漂移容忍值(如 5 秒) 二次校验。
  3. 国内等保 2.0 要求“数据可恢复”,**_purge 前务必做 逻辑备份(couch-dump 到 OSS 冷存),备份保留周期 180 天
  4. TB 级单库,可改用 按时间分库(db-2025q2) 策略,到期直接 删除整个库,避免墓碑膨胀;删除库前用 /_membership 确认集群无重平衡任务。