如何生成时间戳?
解读
国内面试官问“CouchDB 如何生成时间戳”,并不是想听你背“new Date()”这种通用语法,而是考察三点:
- 你是否知道 CouchDB 在文档级与修订级对时间戳的内置机制;
- 你是否能在离线优先、多主并发场景下,给出单调递增且可排序的生成策略;
- 你是否能把时间戳与视图索引、复制冲突检测结合起来,体现性能与一致性意识。
一句话:要答出“CouchDB 没有全局自增时钟,但能用_rev 的 MVCC 版本号 + 业务字段 + 单调时钟”这一核心思路。
知识点
- _rev 不是时间戳:它是 MVCC 的“修订号”,格式 2-abc…,只保证递增不保证与时间线性对应。
- 文档级时间戳字段需自建:国内项目普遍加
"ts": "2024-05-21T08:30:00.123+08:00",类型用字符串 ISO-8601,避免时区坑。 - 离线场景下 NTP 可能失效:移动端需引入单调时钟(performance.now() + 本地起算点)或向量时钟做冲突合并。
- 视图索引按时间范围查询:map 函数里 emit(doc.ts, null) 并配合 startkey/endkey,注意字符串字典序与北京时间对齐。
- 复制冲突时的时间戳策略:国内金融、政务项目常要求“最后写入获胜”,但 CouchDB 默认保留冲突分支,需在2.3+ 版本用
winning_revs_only: true或在业务层对比 ts 字段人工合并。 - 批量导入性能:使用
_bulk_docs时,先在应用层生成 ts,避免在 map 里动态 new Date() 导致视图每次重建。
答案
“在 CouchDB 中,官方不提供全局时间戳,需要在业务文档里自建字段。我的生产做法是:
- 字段名用
"ts",值取东八区 ISO-8601 字符串,精度到毫秒,例如"2024-05-21T08:30:00.123+08:00",保证字典序即可排序。 - 生成时机放在应用层,Node 端用
new Date().toISOString()再拼时区偏移;移动端若离线,先取performance.now()与本地起算点相加,待联网后再用 NTP 校准漂移,校准值写入本地 Storage,下次启动自动补偿。 - 为避免并发覆盖,更新文档时把旧 ts 作为条件放入 update handler,实现乐观锁;若出现冲突,在 list 函数里对比 ts 字段,按“业务时间最大”策略选 winner,并写回
_bulk_docs清理分支。 - 视图查询用
emit(doc.ts, null),利用B+Tree 字典序做范围扫描,支持“近 7 天”这类需求;同时给 ts 字段建partial index(CouchDB 3.3+ 支持 mango index),把热数据留在内存,查询 P99 从 120 ms 降到 20 ms。 - 如果客户要求全局单调递增且可排序,再引入雪花算法,64 位 long 转 18 位字符串作为
_id后缀,既保证顺序又避免 UUID 的随机写放大。”
拓展思考
- 合规留痕:国内等保 2.0 要求“原始记录不可改”,可把 ts 与用户 ID 一起写入
_revisions之外的只读库,再用validate_doc_update禁止修改 ts 字段,实现 WORM。 - 多时区聚合:总部在北京、分公司在乌鲁木齐,视图里统一用 UTC+0 存 ts,查询时根据用户所在时区动态换算,避免夏令时歧义。
- 边缘云场景:KubeEdge 节点断网 48 小时,可把向量时钟
(nodeId, ts, counter)做成附件,与 CouchDB 的_attachments一起同步,回网后自动解决时序冲突。