Ask HN:我们真的撞到了一次 UUID v4 重复
文章摘要
发帖人 mittermayr 自报家门:他用的就是 npm 上那个最常用的 uuid 包,代码简单到不能再简单——import { v4 as uuidv4 } from "uuid"; const document_id = uuidv4(); 然后写库。今早数据库报了一个重复,他还以为是 double-insert,但查下来不是:一条 2025 年插入的旧记录和今早一条新生成的 UUID,完全相同——b6133fd6-70fe-4fe3-bed6-8ca8fc9386cd。数据库里只有约 15,000 条记录,这种规模下 UUID v4 碰撞的理论概率(约 1/4.72×10²⁸)”统计上不可能”。他在 Ask HN 求助:到底哪里出了问题?
他给出的唯一可疑线索是:这两条 UUID 的生成环境不一样。早期 UUID 是在用户手机上生成的,最近几个月迁到了 Ubuntu 服务器上生成。
这是个非常有 HN 风味的求救帖——一个看起来”不可能”的事件,引出整个评论区把”为什么 UUID v4 会撞”的各种可能性几乎枚举了一遍。从结论看,HN 共识非常一致:UUID v4 碰撞远比数学概率暗示的常见,但几乎永远不是真的”中了天文级巧合”,而是熵源出问题、被攻击、被复用、bit 翻转、或者代码里别的地方有 bug。
HN 评论精华
-
jandrewrogers(最高赞,定调评论):这种情况意外地常见。UUID v4 的安全性建立在”高质量熵源”假设之上,而这个假设可能被硬件缺陷、常规软件 bug、或者开发者根本没搞清”高质量熵”是什么这三件事轻易打破。检测熵源是否坏掉本身代价高昂,所以几乎没人定期查——大家都是碰了一次以后才发现。也正因如此,高可靠性 / 高保密性系统里 UUID v4 是被明令禁用的。
-
throwaway_19sz:贴出一个让人笑出声的真实故事——他朋友十年前加入一家 200 工程师的快速增长公司当 CTO,第一周发现公司居然有一个专门生成 UUID 的微服务,三个工程师外加一个 DBA 维护。”安全 UUID”的生成流程是:生成一个 UUID → 去自己 DB 里查它是不是用过 → 没用过就插入并返回。”图个安心”。这个服务有自己的 kanban 和 sprint。
-
CodesInChaos:把诊断思路系统化——一般原因是 PRNG 种子不够(insufficiently seeded)。前端生成 UUID 是不可靠的(被攻击、Googlebot 之类的爬虫可以触发确定性随机数);后端可靠性可控但要看运行环境——VM、沙箱进程、fork 后未重新播种,都可能出问题。
-
beejiu:把矛头指向 Googlebot——它执行 JavaScript 时的
crypto.getRandomValues()是确定性的。如果 OP 早期是前端生成 UUID,就有可能 Googlebot 来爬过页面,每次都吐出同一个”随机”值,于是埋进了数据库。 -
e12e:贴出 GitHub
uuidjs/uuid仓库的 issue #546——确认了”Googlebot 上crypto.getRandomValues()是确定性的”这件事。这是真实存在的工程陷阱,不是脑补。 -
jbverschoor:提出供应链攻击假设——uuid 包依赖的某个底层 RNG 包近期可能被植入了可预测随机数的恶意版本,影响到的不只是 UUID,还有 SSL 和加密货币项目。
-
kst:贴了 Pro Git 那段被引用了 N 遍的 SHA-1 碰撞段子——”全地球 65 亿人都在写代码,每秒钟产生一整部 Linux 内核历史推到同一个仓库,约 2 年后才会有 50% 概率撞到一次 SHA-1。换句话说,自然界撞 SHA-1 比整个团队成员同一晚被互不相关的狼袭击致死还要不可能。”但他随后指出:SHA-1 是确定性 hash,不是 RNG,所以这种数学保证对 UUID v4 不适用——v4 的安全全靠熵源。
-
dist-epoch:另一种思路——比熵源出问题更可能的是 bit-flip。比如数据库内存里有一个最近 index scan 留下的旧 UUID buffer,某个 bit-flip 导致新 UUID 的内存位置被旧的覆盖了。Facebook 写过大量这类”impossible bug”的工程记录(包括
if (false) {do_x();}居然真的执行了 do_x),RocksDB 里专门加了冗余防御。 -
smokel(”先怀疑自己”派):他多次在埋怨编译器、宇宙射线、量子效应、内核 bug 之前,最后发现 bug 是自己写的。15,000 条数据规模下出现一次”碰撞”,他第一反应是去查:是不是重复处理、回放请求、对象复用、日志误导、或者另一条代码路径碰巧复用了同一个 ID。建议 OP 把上下文代码贴出来给大家排错。
-
jordiburgos(最佳玩笑):“求你别用
b6133fd6-70fe-4fe3-bed6-8ca8fc9386cd,我查了我的数据库,我也在用这个 UUID。” -
Geee(多世界诠释派):根据量子力学的多世界诠释,必然存在某个分支宇宙里所有 UUID 都是同一个——想象一下那边的工程师在想什么。
-
整体共识:碰撞 = 熵源出 bug。OP 提到的”前端生成 → 后端生成”切换是最值得查的方向,前端那段历史里很可能埋下了由 Googlebot 或 SSR 环境造成的可预测 UUID,而它今天才被新生成的真随机 UUID”撞上”——其实不是统计学事件,是历史遗留。