如何生成时间戳?

解读

国内面试官问“CouchDB 如何生成时间戳”,并不是想听你背“new Date()”这种通用语法,而是考察三点:

  1. 你是否知道 CouchDB 在文档级修订级对时间戳的内置机制
  2. 你是否能在离线优先、多主并发场景下,给出单调递增且可排序的生成策略;
  3. 你是否能把时间戳与视图索引、复制冲突检测结合起来,体现性能与一致性意识。
    一句话:要答出“CouchDB 没有全局自增时钟,但能用_rev 的 MVCC 版本号 + 业务字段 + 单调时钟”这一核心思路。

知识点

  1. _rev 不是时间戳:它是 MVCC 的“修订号”,格式 2-abc…,只保证递增不保证与时间线性对应。
  2. 文档级时间戳字段需自建:国内项目普遍加 "ts": "2024-05-21T08:30:00.123+08:00",类型用字符串 ISO-8601,避免时区坑。
  3. 离线场景下 NTP 可能失效:移动端需引入单调时钟(performance.now() + 本地起算点)或向量时钟做冲突合并。
  4. 视图索引按时间范围查询:map 函数里 emit(doc.ts, null) 并配合 startkey/endkey,注意字符串字典序与北京时间对齐。
  5. 复制冲突时的时间戳策略:国内金融、政务项目常要求“最后写入获胜”,但 CouchDB 默认保留冲突分支,需在2.3+ 版本winning_revs_only: true 或在业务层对比 ts 字段人工合并。
  6. 批量导入性能:使用 _bulk_docs 时,先在应用层生成 ts,避免在 map 里动态 new Date() 导致视图每次重建。

答案

“在 CouchDB 中,官方不提供全局时间戳,需要在业务文档里自建字段。我的生产做法是:

  1. 字段名用 "ts",值取东八区 ISO-8601 字符串,精度到毫秒,例如 "2024-05-21T08:30:00.123+08:00",保证字典序即可排序。
  2. 生成时机放在应用层,Node 端用 new Date().toISOString() 再拼时区偏移;移动端若离线,先取 performance.now() 与本地起算点相加,待联网后再用 NTP 校准漂移,校准值写入本地 Storage,下次启动自动补偿。
  3. 为避免并发覆盖,更新文档时把旧 ts 作为条件放入 update handler,实现乐观锁;若出现冲突,在 list 函数里对比 ts 字段,按“业务时间最大”策略选 winner,并写回 _bulk_docs 清理分支。
  4. 视图查询用 emit(doc.ts, null),利用B+Tree 字典序做范围扫描,支持“近 7 天”这类需求;同时给 ts 字段建partial index(CouchDB 3.3+ 支持 mango index),把热数据留在内存,查询 P99 从 120 ms 降到 20 ms。
  5. 如果客户要求全局单调递增且可排序,再引入雪花算法,64 位 long 转 18 位字符串作为 _id 后缀,既保证顺序又避免 UUID 的随机写放大。”

拓展思考

  1. 合规留痕:国内等保 2.0 要求“原始记录不可改”,可把 ts 与用户 ID 一起写入 _revisions 之外的只读库,再用 validate_doc_update 禁止修改 ts 字段,实现 WORM。
  2. 多时区聚合:总部在北京、分公司在乌鲁木齐,视图里统一用 UTC+0 存 ts,查询时根据用户所在时区动态换算,避免夏令时歧义。
  3. 边缘云场景:KubeEdge 节点断网 48 小时,可把向量时钟 (nodeId, ts, counter) 做成附件,与 CouchDB 的 _attachments 一起同步,回网后自动解决时序冲突。