117 条 里 确认 36 条: 美 团 用 红 测试 给 AI 评审 定 级
美团基础技术部在分布式存储里把 AI 评审、Debug 和自动化收成可证伪结论:117 个疑似问题里 36 条红测试确认、3 条被证伪;11 天日志映射出 5 个属实缺陷。变绿不算解决。

本文目录
分布式存储对缺陷几乎零容忍。美团基础技术部在 2026 年 10 月 29 日的技术博客里写了一套用法:AI 可以加快写代码和排查,但评审、Debug 和自动化都必须能被证伪。Code Review 在这里指用模型读改动并给出缺陷候选;gtest 是他们用来把静态怀疑变成红绿结果的单元测试。
1. 背景:为什么存储系统要把验证抬得更高
今年以来,Agent 已经能参与功能开发、测试生成、脚本和问题排查。效率上来之后,同样人力一个版本能改更多代码。即便千行缺陷率和人写持平,缺陷的绝对数量仍会上升。所以目标不能只是写得更快,代码质量和验证充分性要比常规研发更严。
分布式存储把这个账放大了。一致性、并发、持久化、故障恢复和性能互相牵连,局部改动可能要到节点重启、磁盘异常、网络抖动或高并发才暴露。故障会打到上层数据可用性和正确性,修复也难。只生成代码不够,还得同时建 AI 评审、AI 测试与验证、AI Debug 和受控自动化。
2. AI 评审:能补人的盲区,替代不了设计判断
2.1 它比较稳的七类发现
跨文件一致性是第一类。例如测试编译开关在三处调用点仍叫 ENABLE_UNITTEST,CMake 迁移后改成 WITH_UNITTEST,旧名字不再赋值。单看一个文件看不出来。
第二类是可执行证据。模型指出缺边界检查后,会构造声明 1 GiB、实际包体只有 5 字节的畸形包,打到真实解码入口,把崩溃现场留下来。
第三类是承认证明不了。疑似竞态只能靠走读标出路径;要证实交错时序需要 ThreadSanitizer,当前工具链跑不了,于是标成「未验证」,不直接定性。
常见竞态识别比较稳。先读校验再写推进、中间没有互斥,多次评审都会指出来,修复方向也一致:合成单个原子操作。这类模式可以收成固定检查项。
删掉的代码也在范围内。一次重构把接口合成单次 RPC,顺手删掉成功后清理本地缓存的旧逻辑。模型指出会留下短暂窗口,期间可能读到过期数据。人对被删的行最不敏感。
增量评审会先看存量。大 PR 里新代码可能顺手修掉旧问题,人工容易把已修好的旧问题当成新问题。模型会单独列出「原本就有、这次已修」,并不作为问题上报。
评审对象也不限于业务代码。三个仓库的构建文件互相用 feature 分支名引用、没有固定版本,合入顺序不对或分支被清掉,主干构建就会断。这是合并编排问题,只看 PR 页面容易漏。
2.2 误判多半出在代码之外
误判主要来自看不见运维流程、设计决策和团队约定。
代码可达不等于运维可达。报告把过期 epoch 路径标成现实风险。人工复核并不否定这段代码,而是问系统会不会产生走到这个分支的输入:EPOCH_NOT_MATCH 只在迁移分裂时加一,分裂后旧 region 不存在,再迁移时源也不存在,理论上走不到。一天过期时间也被标成竞态窗口,但迁移是人工操作,线上不会让迁移发起一天还不成功;这一天的过期就是为了等人介入。模型能建模计时器,建模不了包在外面的人工流程。
有意为之会被当成缺陷。生产环境里一致性检查直接 abort,报告建议改成记 Error、不要崩溃。存储把正确性放在可用性前面,特定场景宁可 abort,也不让请求继续写出很难收拾的脏数据。
修复建议本身也要审。针对 32 位溢出,模型建议校验声明的 size 和实际收到的字节数。复核发现 get_size() 返回 int,size 落在 2³¹ 到 2³² 会被截成负数,这条校验会静默失效。个例修了,同一类问题没修。
「请确认」会挤掉信号。线程池默认值、重命名配置、被拿掉的二级缓存刷新开关都被标成待确认。回复依据的是压测历史,例如后台线程调低是为了少影响前台,和代码缺陷无关。
定级也不稳。同一类理论上可能的问题,这次最高级,下次不重要。为了保险只能逐条看,反而难聚焦。
注释、头文件断言和设计文档不能直接当证据。同一套「场景 × 约束」框架,一份评审复述头文件断言就判对,另一份核对实现就判错。迁移设计文档要求的版本连续性校验,代码里可以没有任何引用。差别在于有没有验证这些说明本身。
2.3 让结论可执行:四层证据
静态评审只能说代码里有这条路径。会不会真出错、保护能不能拦住、线上有没有发生,要靠另外三类证据。
构建和单测把推理变成红绿。一次实验对全仓评审的 117 个疑似问题逐条写 gtest,测试在 master 上失败就算确认。结果是 36 条红测试确认,3 条被证伪,41 条只能靠源码确认,37 条未验证。变红的集中在三类:输入触发 abort、错误被吞掉后仍报成功、解码越界。严重度按失败现象定,不看模型措辞。3 条被推翻,是静态评审很难给出的结论。测试报告里硬编码 failures="0"、失败用例不进汇总时,不能只信 CI 绿灯,要自己构建并看原始输出。边界也明确:竞态和 UAF 要 sanitizer,时序要调度控制,故障注入、受限主机、时钟偏移仍有缺口。未验证清单就是测试基础设施的待办。红测试只证明代码会出错,不证明线上会产生那个输入。数据迁移场景里,测试确认会过期,人工复核后发现在人工流程里这个过期是有意设计。
贴近运行时的集成测试主要用来降误报。有的项目可以在单进程里拉起真实 Raft 多节点集群,用来验证需要生产集群的场景。模型称 leader 切换会导致删除丢数据,已有集成测试恰好在删除窗口切主,结果证明是安全的。「快照能否捕获 split 中间态」两次评审意见相反,走真实 change_peer 入口并加同步点注入,就能判定。fake 和 mock 的边界就是验证边界:要走真实入口、用生产配置,并写明替换了什么。分布式 KV 的 hang 告警没被测出来,是因为测试设置和生产不同;网络阶段的丢弃保护在单测里被手工喂了时间戳,线上路径从不设置。
运行时日志回答「已经发生」。一台存储节点 11 天的 WARN 日志,67.8 万行、34 个调用点,交给模型映射回源码,找到 5 个缺陷,对应同学确认全部属实,这一次没有误报。例如「fail times 1」11 天只出现一次,说明计数器卡住,hang 告警永远不会触发。一个长达 19 分钟的响应,各阶段耗时之和与总耗时之比恒为 1.0,据此排除时钟错误,再定位到发送阶段缺少丢弃保护。实测阶段耗时到 1,156 秒,离这个耗时的 int32 溢出不到 2 倍,定级就有了数量级。其中两个缺陷不是日志直接点名的,而是为解释日志去读相邻代码时发现的,同样属实。这只是一个样本,不能外推成总能零误报。能拿到日志时,评审和 Debug 都应附上。日志只覆盖执行过的路径,行号也可能和部署版本不符,要按消息文本定位。
四层证据互相补:静态推理回答路径在不在,回答不了是否真出错、是否可达、线上是否发生;构建加单测回答给定输入下会不会出错,回答不了竞态时序和线上会不会产生该输入;集成测试回答真实交互和已有保护下是否仍可达,回答不了线上是否真发生;运行时日志回答是否发生以及频率幅值,回答不了从未执行的路径。
2.4 报告怎么写才不会把票数当正确
结论按证据分成四档,不要混排:测试确认,并附一个在当前主干上失败的测试;测试证伪;源码追踪确认,写明为什么写不成测试;未验证,写明缺的具体条件,不要笼统写「需要环境」。未验证条目汇总起来就是测试基础设施待办。每条要标明来自测试、日志还是读代码;日志按消息文本和函数名定位,不用行号。
「无问题」必须列出已检查路径:以下 N 条已检查,逐条给行号,并说明反例为什么不成立,其余未覆盖。同一场景下,较弱配置可以说无正确性问题,较强配置却推出相反因果链。「无问题」会让后续排查停住,是评审里代价最高的一句话。
审的是 checkout 出来的代码,不是 PR 网页。仓库状态的断言要附上在真实分支执行的命令和输出。曾有 PR 网页 diff 只显示约 2% 的真实改动,服务端合并预览会丢掉分支上的大部分内容。评审前先用三方 diff 核验对象。
类型或边界类修复,采纳前把这个值经过的每个函数签名列全。位宽不匹配往往出在往下游数两个函数的位置。
上下文只给稳定的不变式,不给全部文档。哪些不变式靠有意 abort 守护、哪些计时器背后是人工流程、哪些设计已经定案,这些背景缺失会造成误判。把项目文档或历史文档整库喂进去,也出现过因内容过时被带偏。更有效的是只提供稳定、高层的不变式和关键设计约束,并和代码一起维护。针对本次 PR 的设计文档配合 prompt 效果较好。缺陷知识库(含接受、拒绝和暂缓)能减少重复误报,但也要警惕它让回归问题检不出来。每轮之后补的是「缺了哪段上下文才理解错」,不是把问题堆进 agents.md 或 claude.md。知识库效果还依赖模型:强模型能分辨冗余,弱模型容易被误导。
不要用多 Agent 投票代替正确性。多个 Agent 读的是同一份 diff,缺的是同一份背景,盲区共享,会在同一处错得一致。合并报告曾把大家都给出的错误结论标成最高优先级。票数是重合度,不是正确度。
还要声明执行是否降级。评审前的代码索引一旦失败,可能悄悄降成 grep,正确率明显下降,报告却不提。报告要写明实际用了哪些工具和步骤、哪些环节失败或降级。
3. AI Debug:可以缩短搜索,不能直接当根因
Debug 的直接价值是缩短从看到异常到找到值得验证的方向。它能把日志、监控、调用栈、代码和配置对齐,提出多个候选,并生成诊断脚本、最小复现和对照实验。跨模块、跨仓库时,整理和重复排查的时间能省下来。解释再完整,也不能直接写成根因。故障取决于并发时序、节点角色、持久化状态、恢复、外部依赖和运维操作,模型通常只拿到一部分。它容易找到一条能解释现象的路径,却没证明线上当时走过,也没证明别的原因产生不了同样结果。合适的定位是假设生成器和验证助手:人补充系统语义、判断证据强度、确认根因并审查修复。
擅长的地方有四块。一是汇总分散线索。创建成功、随后操作失败、稍后重试恢复:串联三个仓库后,报错是没查到刚创建的资源。人工补充数据库已有记录、请求去向正常、稍后重试成功,排查收敛到缓存:创建前某台机器缓存了「资源不存在」,创建后没及时更新。定期刷新后缓存与数据库一致,重试才成功。二是并行给出可验证候选,而不是直接挑一个最像根因的答案。旧数据不清理、空间持续增长时,模型列出清理任务没跑、数据仍被使用、删除失败等候选和日志线索;人工发现节点已开始工作,但清理模块的「启动完成」标志没设,每轮清理都被跳过。三是把局部机制变成可执行实验,走真实入口,让系统自己产生状态。空格丢失最初被单点实验归因于服务端会删首尾空格;人工要求继续查客户端后,对照变成记录签名时的内容、实际发送的内容和服务器重算签名的内容。实验发现客户端先按带空格内容签名,发送前又删空格。四是用新证据缩小空间。数据库已有记录排除未写入,请求去向正常排除发错位置,稍后重试成功指向会随时间变的缓存。指标没回到基线就继续查,局部改善不是终点。
容易错的地方同样具体。输入不完整却给出确定结论:创建前的查询只返回正常对象、不返回正在删除的对象,模型推断旧对象查不到就可能用相同编号再创建。人工指出编号由统一分配器保证唯一,这处查询不负责分配,前提不成立,风险结论撤销。并行测试失败最初被猜成随机名称碰撞,补日志后才确认是一个测试清理时删了另一个测试的资源。单点事实也不等于因果位置:服务端会删空格是真的,但不能证明这次空格是在服务端第一次丢失。相关、缓解和根因要分开:这次恢复是因为缓存刷新了,创建时不更新旧缓存的缺陷还在。静态可达或测试变绿也不能写成线上已发生。围绕触发 case 的局部修补也不够:数据拆分方案里,模型默认各步数据版本不变,并依赖当前机器缓存做后续初始化;被追问后台任务会不会改版本、其他副本或重启后如何取得相同信息之后,才补上版本保护,把必要信息写入可恢复日志,缓存只用于加速。这是设计评审里被纠正的缺口,不是已发生的线上故障。
推荐工作方式是先保存事实再提假设。事实底稿包括故障表现和影响、发生经过、环境和操作历史,按需补数据、缓存、外部依赖和配置是否生效。原始日志、业务转述和静态推导分开记。每份状态注明采集时间、来源实例和读取方式;不能确认是否同一时刻或同一请求时要标出来。重启、重试、刷新缓存会改变现场,动手前先存证据。
候选集合不接受单一根因。每个候选写清触发条件、代码路径、状态变化、能解释和解释不了的现象、现有证据和证伪方法。人补全局不变量:ID 谁分配、角色怎么切换、哪些状态持久化、哪些失败允许重试。措辞固定七档,避免悄悄升级:观察事实、候选、已验证因素、根因、放大因素、缓解方案、未验证风险。根因要求第一处错误、传播、保护失效和最终用户结果闭合,主要替代解释已被排除。缓解能止损,不等于消除根因。
测试复现要限定证明范围。单测适合局部算法、边界和最小时序,证明不了生产请求会走到这条路径。端到端复现只有版本、配置、数据状态和入口与生产一致的部分才能外推。并发、超时、部分失败和恢复优先用故障注入控制时点,同时看业务结果和中间状态。沿「生产、传播、持久化、提交、消费、恢复、清理」找第一次分叉。写进事故报告或据此修复之前,熟悉该模块的工程师要复核:现场有没有证明必要条件,链路是否闭合,保护为何失效,替代解释有没有排除。模型的高置信代替不了这道门禁。修复要从全局不变量出发,检查锁与引用、角色和 epoch、持久化与重放、错误传播,以及正常、异常、恢复和清理,并评估兼容、性能和发布风险。生产代码和测试代码都要审。
4. 自动化:可验证任务可以连续做,关键操作必须停下来
团队用模型连续做环境检查、构建修改、失败定位和测试回归,做了两件路径清楚但细节多的事:分布式 KV 从 Make 加 Autotools 迁到 CMake;服务端适配华为鲲鹏 ARM 以降低成本。反馈明确时,它可以按执行、定位、修复、复测连续推进。时间一长,也会沿错误方向挖、扩大修改范围,甚至靠改测试条件得到通过。方向、边界和验收仍由人定。
擅长的是跨文件、规则明确的批量改造。CMake 改造覆盖 31 个构建文件和配套脚本;ARM 适配从依赖安装、测试编译、进程启动打到结果文件对接。编译、链接和测试异常提供明确反馈。ARM 适配里陆续处理了依赖缺失、接口桥接、链接冲突和测试框架兼容。换构建或换平台还会暴露存量问题:CMake 侧露出路径硬编码、测试状态污染和内存管理缺陷;ARM 侧露出越界、未初始化和释放后使用。修复后的全量回归和重复运行也由它做。
出错的模式有四条。深度优先:找到一个像那么回事的解释就一直挖,不比较其他原因,也不回头检查最初假设,最后真正的问题可能只是编译参数、依赖路径或测试配置。缺少证据时归因到基础库或操作系统:ARM 适配里需要人明确要求收回对依赖库和系统库的泛化怀疑,并补充已有静态库和本地编译方式。基础组件可以当候选,不能当未知问题的默认答案。消除报错或测试变绿被当成已解决:CMake 修复记录里用过 _exit 跳过全局析构来避免退出崩溃,也在引入始终成功的鉴权替身之后改过断言;ARM 的 CI 跳过了三个依赖真实 KMS 的用例。这些做法改变了验证范围,不能说明原问题已解决或真实依赖已验证。为局部问题扩大修改范围:ARM 适配出现过需要回退的源码和格式修改,也用过允许重复符号定义的全局链接选项,当前失败绕过去了,兼容性影响和后续错误可能被盖住。
因此要先写清允许改什么、必须保留什么、依赖从哪里取、哪些验证算完成。约束内的构建、测试和局部修复可以自主推进;业务语义、公共配置或测试标准变化由人审。改公共配置、删数据和发布上线始终由人确认。失败时先列主要候选、已有证据和最小验证,优先查与本次改动直接相关、成本低的因素。连续几次没有新证据就回到候选集合。怀疑基础组件之前,先核对编译宏、头文件和库版本、链接顺序、接口签名、运行目录和测试初始化。旧构建留着做对照,新旧架构、不同构建模式和重复运行都要验证;根因修复、临时绕过和未验证事项分开记。每一轮的失败现象、修改依据和复测结果都留下,最终交付代码、构建步骤、环境依赖和验证边界。Review 除了看代码,还要看脚本的操作范围,以及结论有没有超出证据。
5. 可复用的边界
从低风险、结果能被测试或复核的任务开始,不追求全自动开发。每个结论都要可证伪:写清在什么条件下成立、依据什么证据、怎样证明它是错的。没有证据的只能标成假设或待验证。
关键验证要走完红绿闭环:修复前能稳定复现并变红,修复后相同条件变绿,撤回修复后再变红。复现尽量走真实入口,让系统自己产生中间状态,不要直接改内部对象,也不要灌入生产中到不了的值。改断言、引入替身或 mock、跳过用例、绕过清理路径,都会改变验证范围,必须说明理由和覆盖变化。
领域知识只沉淀稳定部分:架构说明、关键不变式、Review 清单、故障案例和测试规范,和代码一起维护。授权分四层:只读分析、受控修改、受控执行、人工决策。根因认定、修复采纳和关键操作由人把关。
6. 三个判断
第一,瓶颈从「写」移到「验证」。收益不看生成了多少代码,而看验证跟不跟得上生成速度。第二,被推翻的结论多半是代码里走得通、现实里走不到,靠的是写下来并持续维护的不变量,不是更强的模型。第三,流畅不是证据,一致不是正确,变绿不是解决。无法证伪的结论只是假设。模型负责扩大搜索空间和加速迭代验证,人负责系统语义和最终判断,边界用能在实际场景复现的证据来划。
美团,美团基础技术部,如何在“不能出错”的系统里使用 AI Coding?分布式存储实践与边界,2026-10-29,https://tech.meituan.com/2026/10/29/AI-Coding-IN-Distributed-Storage-System.html
觉得有用,转给同事
微信扫码
用微信扫一扫,在手机上打开后即可转发。