尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

一条迷你压力测试揪出 CPU 硬件 Bug:RocksDB 唯一 ID 生成与 RDSEED 缺陷的排查实录

一条迷你压力测试揪出 CPU 硬件 Bug:RocksDB 唯一 ID 生成与 RDSEED 缺陷的排查实录 一条迷你压力测试揪出 CPU 硬件 BugRocksDB 唯一 ID 生成与 RDSEED 缺陷的排查实录【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdb本篇技术文章完整还原了 RocksDB 开发史上一次罕见的测试反杀硬件事件一条四年前为验证随机数质量而编写的多线程迷你压力测试在四年平安运行后突然连续失败最终牵出一个被评定为高危 CVE 的 CPU 硬件缺陷——RDSEED 指令在特定微架构条件下高频返回 0 却报告成功。读完本文你将理解 RocksDB 为 SST 文件生成全局唯一标识的完整设计熵源组合、准随机方案、冗余校验掌握信任但验证的测试方法论以及从偶发失败到根因定位的完整排查路径。背景SST 文件为何需要自己的唯一标识RocksDB 大约在四年前为 SST 文件引入了唯一标识Unique ID对应 PR #9126其核心动机是为跨文件系统的缓存场景提供稳定、可复现的文件身份。此前 RocksDB 依赖操作系统文件系统提供的文件唯一性保证但部分文件系统只保证在现存文件之间唯一并不保证在近期历史上所有文件之间唯一详见当时社区 issue #7405 的讨论。一旦文件系统复用旧 inode 或标识缓存键就可能发生碰撞导致缓存命中错误数据。这背后是一种伟大张力great tension复用现有方案 vs. 代码自给自足。RocksDB 团队既不想重复造轮子也不愿受制于他人实现的缺陷或变动中的需求。最终结论很明确不能把正确性押注在所有可能遇到的文件系统都能提供高质量唯一标识这一假设上。仓库中的落地实现在当前仓库中这一设计凝结在 table/unique_id.cc 中。核心函数GetSstInternalUniqueIdtable/unique_id.cc#L59-L120将三部分信息组合成内部唯一 IDdb_id数据库级别的标识120 位熵通常为 RFC 4122 UUIDdb_session_id进程生命周期内的会话标识20 个 base-36 字符、约 103 位熵file_numberSST 文件编号。组合方式非常讲究session_lower被完整保留以保证同一进程内生成的 ID 必然唯一session_upper约 39 位熵与db_id经Hash2x64哈希后提供极高的全局唯一性熵最后再异或file_number保证同一会话、同一 DB 下按文件号必然唯一table/unique_id.cc#L92-L117。会话 ID 的编解码由EncodeSessionId/DecodeSessionId实现table/unique_id.cc#L15-L57并提供了人类可读的十六进制展示函数UniqueIdToHumanString。这一内部唯一 ID 在 cache/cache_key.cc 中被用来构造缓存键cache key其头部注释详细分析了会话 ID 位宽、碰撞概率与缓存键覆盖范围的数学关系——这正是唯一 ID 必须可靠之所以性命攸关的直接原因。如果你熟悉大随机数例如 128 位自然会认同让每个文件持久化一个随机或准随机quasi-random标识比把正确性寄托在 OS 文件系统的一个次要特性上更安全、更可预测。准随机方案在理论上已被形式化——核心思想是非结构化随机间隔 结构化内部计数器的组合使 N 个生成器各产生 M 个 ID 时的首次碰撞期望点从完全随机方案的n * m 2^64提升到n * sqrt(m) 2^64碰撞概率大幅下降。高质量随机性不信任单一熵源组合三者唯一 ID 方案成立的前提是能够获得高质量随机数至少需要一两个好的种子具体论证见准随机论文。RocksDB 追求跨平台希望尽量减少平台相关依赖、优先使用跨平台依赖——但这又可能绕回原点我们所依赖的某个实现一旦出 bug 或打嗝就会重新受制于人。幸运的是随机熵有一个美妙性质组合多个熵源时结果质量与最好的那个输入源一样好。即使某个源坏了只要不是所有源都坏结果依然可靠。再加上两个工程上的有利条件我们只需要唯一性不需要安全性密码学强度这降低了对随机源的审查要求也允许使用准随机方案准随机方案把所需熵量降到最低因此获取每单位熵的性能开销几乎可以忽略不计。于是代码中把以下三类熵源组合在一起熵源博客原文描述仓库实现env/unique_id_gen.ccstd::random_deviceC11按标准应提供高质量随机但标准允许它不提供EntropyTrackRandomDeviceL91-L106连续取192 / (8 * sizeof(result_type))次r()填充数组环境参数哈希主机名、进程 ID、线程 ID、宏/微秒级时间读数EntropyTrackEnvDetailsL71-L89hostname_buf、process_id、thread_id、unix_time、nano_time平台专用 UUID 生成器仅 Linux 和 WindowsEntropyTrackPortUuidL56-L69调用port::GenerateRfcUuid取前 36 字节三条轨道各自在哈希后都足以产生 128 位熵可在测试中独立启用/禁用生产环境则尽可能全部组合。三轨数据连同 RocksDB 版本标识ROCKSDB_MAJOR/MINOR/PATCH防止熵输入模式变更造成意外物理碰撞一起经Hash2x64压缩为两个 64 位输出env/unique_id_gen.cc#L128-L134。有意思的是源码注释env/unique_id_gen.cc#L53-L54明确给出了 Linux 下的性能对比EntropyTrackRandomDevicestd::random_device底层通常是 RDRAND/RDSEED 指令远快于EntropyTrackEnvDetails后者又远快于EntropyTrackPortUuid。这一性能偏好正是后续硬件 Bug 能隐蔽潜伏多年的土壤——最常用的那个源恰恰是最容易出问题的那一个。此外env/unique_id_gen.h 还定义了两种生成器SemiStructuredUniqueIdGenL56-L85随机基座 原子计数器进程内lower保证每次调用唯一用于生成 DB session ID见 cache/cache_key.cc#L48-L49 的注释UnpredictableUniqueIdGenL91-L117256 位熵池 计数器 RDTSC 时间信息提供合理的不可预测性且初始化后保证不阻塞与std::random_device不同它不会被阻塞。信任但验证用多线程迷你压力测试守护随机源质量方案定下来之后RocksDB 团队做了一件改变后续命运的事对每个熵源持续进行质量验证。对应的单元测试来自 PR #8708——它使用大量线程、基于单个熵源每次只启用一条轨道批量生成成千上万个唯一 ID然后逐一校验唯一性。这条测试的数学底气很硬对 128 位 ID 而言即使只有 128 位中的一小部分有效只要源质量正常数千个样本中出现任意重复的概率都小到可以忽略——即便这些测试连续运行几十年也几乎不可能碰撞。反过来如果测试真的检测到大量重复那就是熵源出了严重问题而不是统计上的偶然。仓库中的测试印证当前仓库的 env/env_test.cc 完整保留了这一测试家族且每条轨道都有独立的针对性用例TEST_F(EnvTest, GenerateRawUniqueId)env/env_test.cc#L3440默认三轨全开多线程并发生成并断言唯一TEST_F(EnvTest, GenerateRawUniqueIdTrackPortUuidOnly)L3456仅启用平台 UUIDTEST_F(EnvTest, GenerateRawUniqueIdTrackEnvDetailsOnly)L3475仅启用环境细节TEST_F(EnvTest, GenerateRawUniqueIdTrackRandomDeviceOnly)L3489仅启用std::random_device——这正是后来揪出 CPU 缺陷的那条测试。测试调用的TEST_GenerateRawUniqueIdenv/unique_id_gen.cc#L147-L155是GenerateRawUniqueId的调试版通过三个布尔参数精确控制哪些熵轨参与从而实现对单一随机源的隔离验证。这种轨道可分离的设计env/unique_id_gen.cc#L43-L54 的注释明言各轨道可分离以便测试是这次硬件缺陷能够被准确定位到std::random_device的前提。异常信号四年平安后两个月内两次失败故事在几个月前迎来转折基于std::random_device的那条测试失败了一次。起初这个失败并不显眼——异常之处在于缺失的唯一 ID 数量不是仅仅少了一个而是少了几十甚至几百个。不过即便这样也能用随机 CPU 抖动或比特翻转导致本来就没生成那么多 ID来解释。你可能已经注意到RocksDB 的开发和 CPU 时间中有越来越多逻辑上冗余、但专为在损坏扩散之前探测 CPU 误算的检查——这些检查的存在让单次失败显得没那么可怕。但一个月后它又失败了。四年零失败然后两个月内连续失败两次——这味道非常不对。深挖细节时发现了一个关键相关性两次失败的测试任务运行在完全不同的数据中心却跑在同一类型的硬件上。冗余校验为何重要CPU 损坏注入工具链博客提到的日益增长的冗余检查在当前仓库的 tools/cpu_corruption_injector/ 目录中有直接印证这是一整套用于模拟 CPU 计算错误的工具链包括injector_register_corruption.py寄存器损坏注入、injector_critical_instruction.py关键指令损坏注入、injector_navigate.py、injector_telemetry.py以及配套的runner.py执行框架runner_build.py、runner_execute.py、runner_randomize_stress_flags.py、runner_report.py。这类工具的意义在于把CPU 算错当作一种需要主动防御的故障模式来演练验证 RocksDB 的校验逻辑能否在错误传播前将其捕获。这也解释了为什么团队面对第一次失败时能保持冷静——失败被设计为可预期、可解释的事件。工程直觉放大复现实验面对两次可疑失败工程师的自然反应是放大规模尝试复现。而这次复现异常顺利——把任务中的线程数提高到接近机器核心数后所有使用同一类型新 CPU 的系统都会快速、稳定地失败其他硬件全部通过进一步设计变体实验定位出两条精确边界std::random_device使用rdrand源和使用/dev/urandom源时不受影响使用 clang 的libc不受影响只有 GCC 的libstdc受影响。这两条边界把嫌疑锁定在了libstdc中std::random_device的默认实现路径——也就是直接执行 CPU 随机数指令的那条路径。根因分析RDSEED 返回 0 却报告成功随后 Meta 的同事展开了底层调查最终定位到处理器硬件缺陷该类型处理器上的RDSEED 指令在复杂的、可在内存负载下复现的微架构条件下返回 0 并置位成功标志的频率远超随机期望——而且只在部分核心上出现。也就是说CPU 声称我给你生成了一个合格的随机数实际上给出的是常数 0。对于准随机方案来说单个 0 值不会立刻致命毕竟还有哈希和其他熵轨兜底但当失败集中在某条仅启用std::random_device的测试上时数十到数百个重复 ID 就完全暴露了问题——这正是每条轨道单独测试的设计价值。应对措施随之展开开发了一个缓解性质的 Linux 内核补丁在这些处理器上将 RDSEED 标记为不可用Meta 内部先行部署在 OEM 提供修复前规避问题AMD 很快承认了问题并公布了计划中的缓解方案官方安全公告 AMD SB-7055其中包括 CPU 微码microcode更新该缺陷最终被评定为高危high severityCVE。从排查视角看这次根因分析链条值得记住随机性测试检验输出质量→ 硬件隔离同一 CPU 型号→ 线程数放大复现→ 实现隔离库/指令路径→ 硬件厂商确认。每一步都排除了一个假设层。披露过程中的插曲与致歉这篇博文还罕见地公开了一段事故复盘作者原本尽力在 OEM 公开承认问题前保密信息但由于 Meta 内部多个基础设施团队过于积极的补救行动信息经由Linux 邮件列表发生了未协调的提前披露。作者代表相关流程致歉并表示正在改进与 OEM 先协调的管控流程。这段插曲本身也是工程技术社区治理的重要案例安全问题的披露不止是发现问题—修复问题还涉及跨团队、跨公司的协调纪律。关键启示博文结尾给出了三条言简意赅的经验这不仅是针对随机数更是针对所有基础依赖的通用方法论测试你所依赖的东西Test what you depend on——不要假设std::random_device、操作系统、文件系统或 CPU 一定按文档工作为你所依赖的东西设置冗余和/或健全性检查Have redundancies and/or sanity checks——组合熵源、逻辑冗余校验、独立轨道测试都是防单点失效的具体形态即便是 CPU 也会出 BugEven CPUs can have bugs——通常是个别单元间歇性故障但偶尔也会出现影响所有单元的缺陷正确的态度是把计算可能出错纳入系统设计的前提。仓库中的工程印证可继续深入的路径如果你想在代码层面继续验证本文的每一个论断以下是当前仓库中的关键入口熵源组合与三轨设计env/unique_id_gen.cc、env/unique_id_gen.h唯一 ID 生成与 SST 表属性集成table/unique_id.cc、table/unique_id_impl.h缓存键如何消费唯一 IDcache/cache_key.cc其头部注释含完整的碰撞概率推导多线程唯一性测试家族env/env_test.cc#L3440-L3496含GenerateRawUniqueIdTrackRandomDeviceOnlyCPU 损坏注入与故障演练工具链tools/cpu_corruption_injector/含 README 与tests/test_injector_critical_instruction.py。最后需要说明适用边界本文描述的唯一 ID 方案面向唯一性而非安全性——GenerateRawUniqueId在 env/unique_id_gen.h 的注释中明确声明未经验证可用于密码学。如果你的场景需要密码学强度随机数请勿直接复用该实现。【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表