附录E:常见数据工程 Bug 调试手册¶
E.1 附录定位¶
本附录面向数据工程里最常见、也最消耗时间的一类问题:数据管线“看起来都对”,但结果就是不对。它不是一份通用故障库,而是一份更接近本书语境的调试手册,帮助读者把常见问题从“经验性猜测”变成“可定位、可归类、可回归测试”的对象。
在大模型数据工程里,Bug 往往不会只出现在代码层。它可能出现在数据源、切分、去重、脱敏、解析、标注、评测、缓存、权限、回写或版本引用的任何一层。更麻烦的是,这些 Bug 还经常相互叠加,表现为“指标没变化但样本错了”“样本没错但版本错了”“版本没错但发布错了”。近年的数据处理系统和数据泄漏研究都表明,数据链路中的小错会被训练、评测和发布流程放大,因此排障必须把数据、代码、配置和评测协议一起看(Chen et al. 2024; Kapoor and Narayanan 2023)。
因此,本附录强调的不是“修复技巧”,而是调试顺序。先定位层级,再归类症状,最后才谈修复。
E.2 调试总原则¶
- 先确认问题是否可复现。
- 先定位是哪一层出错,再看是不是跨层传播。
- 先检查数据版本,再检查代码版本。
- 先找最小可疑输入,再扩大范围。
- 先保留证据,再做修复。
- 先修逻辑,再调阈值;先修结构,再修表象。
- 先验证回归测试能否抓住问题,再宣布问题已解决。
很多团队在排查时会直接进入“改代码”阶段,这通常会带来两个副作用:一是原始证据消失,二是相似问题以后还会复发。更稳妥的方式是把 Bug 处理成一个完整闭环:发现、复现、定位、修复、验证、回归、记录。
E.3 常见 Bug 分类¶
表 E-1 给出一个适合本书内容的调试分类。
| 类别 | 常见症状 | 首要检查点 |
|---|---|---|
| 来源问题 | 找不到原始样本、授权不明、版本漂移 | 来源记录、快照、许可 |
| 解析问题 | 字段缺失、表格错位、OCR 乱码 | 解析器、编码、版面规则 |
| 清洗问题 | 误删样本、去重过度、脱敏过强 | 规则版本、阈值、抽检样本 |
| 切分问题 | 训练/测试泄漏、评测污染 | split 文件、随机种子、索引 |
| 标注问题 | 标签不一致、指南漂移、争议上升 | 标注说明、仲裁记录、样本分布 |
| 评测问题 | 分数异常、切片失真、对比不稳 | 指标脚本、基线、评测集版本 |
| Agent 问题 | 工具误调、轨迹丢失、权限越界 | 事件日志、tool call、授权边界 |
| 合规问题 | 出口不清、用途漂移、日志外泄 | 法域、审批、留痕、发布策略 |
这个分类的目的不是给 Bug 贴标签,而是帮助团队决定先看哪里。比如“评测分数突然掉了”,并不一定是模型退化,也可能是评测集被污染,或者某个切片样本在上游被误删。分类越早,排查越快。
E.4 采集与解析 Bug¶
E.4.1 网页采集后内容缺失¶
常见原因:
- 选择器过窄。
- 页面结构变化。
- 反爬触发后返回了替代页。
- 编码或渲染方式变化。
优先动作:
- 保留原始 HTML。
- 比较抓取前后的 DOM。
- 检查是否被重定向到验证码页。
- 固化一份结构回归样本。
如果采集的是长文档、公告、论文或知识库页面,需要额外检查分页加载和懒加载内容。很多“缺字”并不是没抓到,而是页面只在滚动后才渲染。此时应把“截图”与“DOM”同时保存,避免只看其中一种证据。
E.4.2 PDF / 文档解析错位¶
常见原因:
- 页眉页脚干扰。
- 表格线缺失。
- OCR 与版面分析顺序错误。
- 页码与内容页混淆。
优先动作:
- 对单页做人工比对。
- 切换解析器做 A/B 对照。
- 保留页级截图和中间产物。
如果解析结果中出现大量“标题断裂”“表格合并”“列表顺序错乱”,不要急着改后处理,先看版面层是否已经把阅读顺序弄乱。很多时候,后处理只是给错误排序做修饰,真正的问题在更前面。DocLayNet 和 Nougat 等文档理解工作也说明,PDF/OCR 问题常常来自版面、阅读顺序、公式和表格结构,而不是单纯的文本清洗问题(Pfitzmann et al. 2022; Blecher et al. 2023)。
E.5 清洗与切分 Bug¶
E.5.1 去重后样本数骤降¶
先问三件事:
- 去重粒度是不是太粗。
- 是否把模板样本误删成脏样本。
- 是否把不同来源的同类样本当成重复。
更进一步,要区分“字面重复”和“语义重复”。字面重复适合规则去重,语义重复则需要看任务目标。对于训练集,适度重复未必是坏事;对于评测集,重复常常意味着污染。两者不能用同一把尺子。机器学习复现危机中的数据泄漏案例提醒我们,切分、近重复样本和评测污染会让指标看起来更好,却削弱真实泛化能力(Kapoor and Narayanan 2023)。
E.5.2 训练效果突然变差¶
优先排查:
- 新版本数据是否混入旧问题样本。
- 切分是否泄漏。
- 评测集是否被污染。
- 清洗规则是否改了但版本没升。
很多团队会先怀疑模型结构变化,其实真正造成波动的常常是数据。尤其在增量更新场景中,哪怕只改了少量规则,也可能影响长尾样本的分布。建议把“规则版本”和“数据版本”绑定记录,避免只记其中一个。
E.6 标注与评测 Bug¶
E.6.1 标注一致性下降¶
通常不是“标注员变差了”,而是:
- 指南不够清楚。
- 边界样本增多。
- 新类别引入后口径没同步。
处理建议:
- 抽取争议样本做集中复盘。
- 为边界样本补充正反例。
- 为新类别补充判定优先级。
- 设定仲裁记录的可追踪字段。
E.6.2 评测分数异常波动¶
优先检查:
- 指标脚本版本。
- 评测集版本。
- prompt / 模板版本。
- 是否启用了新的后处理。
如果分数变化只出现在少数切片上,不要急着整体否定模型。先看是不是切片定义变了,或者样本分布发生漂移。很多“整体变差”其实是局部坏点被放大了。
E.6.3 评测结果和人工感知不一致¶
这是最常见也最容易被忽略的问题之一。模型分数看起来更高,但人工体验更差,通常说明以下几种情况之一:
- 指标没有覆盖真正关心的行为。
- 评测集过于单一。
- 生成后处理把错误包装得更像正确答案。
- 人工在看的是场景级体验,而指标只看局部字段。
这类问题的处理方式不是“再调一次模型”,而是重新确认你到底要优化什么。若目标是检索准确率,指标就应围绕召回和排序;若目标是可用性,应该加入可读性、完整性和失败可解释性。
E.7 Agent 与 DataOps Bug¶
E.7.1 Agent 调错工具¶
优先检查:
- 工具描述是否足够明确。
- 任务上下文是否丢失。
- 权限是否过宽。
- 失败重试是否改变了状态。
对 Agent 系统来说,很多 Bug 不是“不会做”,而是“做错了还继续做”。因此一定要把工具调用记录成结构化事件,并区分“计划”“执行”“重试”“回滚”四种状态。否则排查时只看见最终输出,看不见中间过程。AIOpsLab 等面向自治运维 Agent 的评测框架也把故障注入、遥测数据、交互接口和任务轨迹作为评估 Agent 排障能力的核心条件(Chen, Shetty et al. 2025)。
E.7.2 回滚后问题复发¶
优先检查:
- 是否只回滚了数据,没回滚规则。
- 是否只回滚了表,没回滚缓存。
- 是否只回滚了结果,没回滚引用链。
这里最容易出现“表面恢复、根因还在”的假象。建议所有回滚动作都带版本号、时间戳、责任人和影响范围,并在回滚后执行最小验证集。
E.8 调试记录模板¶
建议每次 Bug 都记录以下字段:
| 字段 | 内容 |
|---|---|
| 问题描述 | 发生了什么 |
| 首次发现时间 | 什么时候发现 |
| 影响范围 | 哪些数据/任务/章节受影响 |
| 可复现步骤 | 如何稳定复现 |
| 可疑版本 | 相关代码、数据、配置版本 |
| 根因判断 | 当前最可能的原因 |
| 修复动作 | 做了什么修改 |
| 回归结果 | 是否已验证修复 |
如果一个问题修了两次还会回来,通常不是技术细节问题,而是记录不完整。调试记录的价值不只是“留档”,更是给后续类似问题建立路径依赖,让团队少走重复路。
E.9 三个常见案例¶
E.9.1 表格字段错位¶
某批文档在解析后所有表格都少了一列,第一反应是“解析器坏了”。但人工对比后发现,原始文档中有部分单元格合并,且新版模板把表头换了位置。真正的问题不是解析器,而是模板版本与解析规则不一致。处理方式是把模板版本纳入输入条件,并为关键版式建立单独规则。
E.9.2 切分泄漏导致评测虚高¶
训练集和测试集共享了近似样本,指标因此抬高。修复后分数下降,但人工确认结果反而更真实。这个案例说明,高分不一定代表系统更好,尤其当数据流转链条过长时,污染比退化更常见。
E.9.3 Agent 回写覆盖旧结果¶
Agent 在自动回写时没有检查版本,导致新结果覆盖了旧结果。表面上看系统运行正常,实际上历史记录被改写。修复思路是增加版本门禁,所有回写都必须带目标版本、来源轨迹和可追溯凭证。
E.10 回归测试清单¶
修复 Bug 后,至少检查以下内容:
- 最小复现样本是否恢复正常。
- 邻近样本是否被误伤。
- 上游数据是否还会触发同类问题。
- 下游评测是否仍然稳定。
- 日志、缓存、版本记录是否同步更新。
- 回滚路径是否仍可用。
- 监控是否能再次发现类似问题。
如果以上清单没有过一遍,就不算真正修完。很多“已修复”只是“这次看起来没事了”。
E.11 附录小结¶
调 Bug 的关键,不是快,而是稳。先留证据,再缩小范围;先看层级,再看症状;先修根因,再补回归。这样做的好处是,问题不会只被“修掉一次”,而是被系统性地吸收进工程流程里。
E.12 一套更完整的排障路径¶
如果把一个 Bug 从发现到恢复看成一条链路,那么推荐的排障顺序不是“哪儿坏修哪儿”,而是下面这条路径。
- 识别异常:先确认是单点问题、批量问题,还是长期漂移。
- 固定现场:保存输入、输出、日志、版本和截图。
- 构造最小样本:把问题压缩到最小可复现集合。
- 归属层级:判断是来源、解析、清洗、切分、标注、评测、Agent 还是发布。
- 查最近变更:先看最近改了什么,而不是先猜模型退化。
- 验证假设:逐个关闭可疑因素,而不是并行改一堆东西。
- 确认根因:只有能解释症状、可复现、可回归验证的原因,才算根因。
- 设计回归:把这次问题变成以后自动能抓到的测试。
这条路径的价值在于减少“修补式排障”。很多问题一旦只看表面,很容易把局部症状压下去,却把更深层的故障藏起来。
E.13 观察点与埋点¶
调试难,很多时候不是系统复杂,而是系统没有足够的观察点。对于本书涉及的数据工程链路,建议至少保留以下埋点;这些观察点也对应了现代数据处理系统和自治运维评测中对中间产物、遥测信号和回归样本的要求(Chen et al. 2024; Chen, Shetty et al. 2025):
- 原始输入快照。
- 中间结构化结果。
- 关键阈值和规则版本。
- 采样和过滤日志。
- tool call 和 action log。
- 回写前后的版本号。
- 失败时的异常上下文。
如果没有这些信息,Bug 就只能靠猜。猜一次两次可以,猜十次就会把团队训练成“经验主义修复器”。
E.13.1 日志不是越多越好¶
日志太少不好,太多也不好。日志的原则是可读、可筛、可关联。建议把关键日志拆成三类:
- 审计日志:记录谁在什么时间做了什么。
- 运行日志:记录程序在什么条件下跑到了哪里。
- 诊断日志:记录遇到异常时的状态、输入和中间值。
三类日志不要混在一起写成一大坨文本,否则最后会出现“日志很多,但没一个能直接定位问题”的情况。
E.14 版本、缓存与权限¶
很多看起来像代码问题的 Bug,根本原因其实是版本、缓存或者权限。
E.14.1 版本问题¶
版本问题最常见的表现是“昨天能跑,今天不行”。这种问题通常来自:
- 数据快照变了。
- 配置文件变了。
- 依赖包变了。
- 模板或 prompt 变了。
- 评测脚本变了。
处理时,不要只看代码仓库的提交,还要看数据版本和外部依赖版本。对于工程链路较长的项目,版本号本身就是证据。
E.14.2 缓存问题¶
缓存问题常常表现为“我已经改了,但结果没变”。这通常意味着:
- 旧结果还在缓存中。
- 索引没有重建。
- 中间产物没有清理。
- 调用被路由到了旧服务。
修复此类问题时,最有效的方法往往不是改逻辑,而是明确清缓存、重建索引、强制刷新和重跑最小样本。
E.14.3 权限问题¶
权限问题最容易伪装成“数据缺失”或“系统异常”。例如:
- 读取不到数据,其实是权限不够。
- 回写失败,其实是目标表不允许写。
- 工具调用失败,其实是 Agent 没有执行权限。
因此,排障时要先确认是不是权限边界在起作用,而不是默认服务或接口坏了。
E.15 复盘模板¶
一次 Bug 处理结束后,最好补一份轻量复盘。建议至少写下这些内容:
| 字段 | 内容 |
|---|---|
| 起因 | 问题是怎么来的 |
| 触发点 | 是哪个变化把问题引出来的 |
| 发现方式 | 系统、人工还是监控发现的 |
| 临时处理 | 第一时间怎么止损 |
| 根因 | 最终确认的根本原因 |
| 长期修复 | 后续要改什么制度或代码 |
| 预防措施 | 今后如何更早发现 |
复盘不是追责文档,而是把经验转成制度。只有当一个问题能被后续流程自动拦住,它才真正结束。
E.16 常见排障误区¶
E.16.1 只看最终分数¶
最终分数异常,原因可能在很早的输入端。只盯着分数,很容易错过切分、污染和样本损失。
E.16.2 只改一个地方¶
有些问题表面上是一个点,实际上是两三个环节同时出问题。只改一个地方就停手,往往会得到“偶尔好、经常坏”的系统。
E.16.3 没有固定回归样本¶
没有固定回归样本,就没有稳定的比较基准。每次修复都像在不同地形上跑步,最后很难知道问题是真修了还是碰巧没触发。
E.16.4 把“没再出现”当成“已解决”¶
这个判断风险较高。很多问题只是暂时没撞上。只有在最小样本、邻近样本、上游样本和回归样本上都通过验证,才能算真的解决。
E.17 附录补充建议¶
若要把这一附录用于团队实践,可以再往下加三类材料:
- 典型事故单:按类别收集真问题。
- 标准排障脚本:把常见检查点自动化。
- 复盘知识库:把根因和修复方式沉淀下来。
这样做的结果,是 Bug 不再只是“谁碰到了谁去修”,而是变成了团队共同积累的工程知识。
E.18 从症状到层级的快速判断¶
当 Bug 刚冒出来时,先别急着修,先判断它更像哪一层的问题。
| 症状 | 更可能的层级 |
|---|---|
| 数据突然少了一大截 | 来源、解析或清洗 |
| 评测分数突然抖动 | 切分、评测集或脚本 |
| 输出看起来对,但人工觉得不对 | 标注、评测定义或后处理 |
| Agent 经常调用错工具 | 工具描述、权限或上下文 |
| 回写后历史记录被覆盖 | 版本、回写策略或锁机制 |
这张表不是为了替代排查,而是为了减少第一轮猜测成本。层级判断越早,修复路径越短。
E.19 一个更完整的事故复盘框架¶
复盘时建议至少回答五个问题:
- 这次问题的直接诱因是什么。
- 这次问题为什么没有更早被发现。
- 哪个控制点没有起作用。
- 哪个环节本来可以提前拦住。
- 今后要加什么机制避免复发。
如果只写“原因是规则有误”,复盘价值就很有限。真正有用的复盘,应该能看出是定义、流程、监控还是权限出了空洞。
E.20 把排障做成标准动作¶
成熟团队通常不会把每次排障都当成临场发挥,而是把它变成一组标准动作。
E.20.1 标准排障动作¶
- 固定输入。
- 复现输出。
- 检查版本。
- 检查日志。
- 定位层级。
- 验证假设。
- 形成回归。
E.20.2 标准化的好处¶
标准化不是为了形式统一,而是为了降低认知负担。每次都按同一顺序看,团队成员之间更容易共享判断,也更容易在交接时承接问题。
E.21 三种常见复发模式¶
E.21.1 规则复发¶
修了一个规则,但另一个地方仍保留旧逻辑。结果看似修复,实际上只是把问题移动了位置。
E.21.2 数据复发¶
旧样本或脏样本再次流入新版本,导致问题周期性出现。这个问题常见于增量更新和多来源汇聚场景。
E.21.3 流程复发¶
某个控制点没有被纳入正式流程,于是每次换人、换环境或换版本,问题都会再来一次。流程复发比技术复发更难根治,因为它需要制度而不只是代码。
E.22 附录收束¶
调试手册的价值,不是给出某个 Bug 的孤立修复方式,而是帮助团队形成一套稳定的判断流程。这样做之后,排障不再依赖“某个特别会修的人”,而是逐渐变成可复制的工程能力。
E.23 20 类常见问题清单¶
为了贴近真实项目,这里把常见问题进一步压实成 20 类。它们不是互斥的,但能帮助团队快速归类。
| 类别 | 先看什么 |
|---|---|
| 采集缺失 | 入口页、重定向、反爬、编码 |
| 解析错位 | 版面、OCR、表格线、页码 |
| 清洗误删 | 规则版本、阈值、抽检样本 |
| 去重过度 | 粒度、指纹、语义近似 |
| 切分泄漏 | split、随机种子、索引 |
| 标注漂移 | 指南、争议样本、仲裁记录 |
| 评测失真 | 脚本、基线、评测集版本 |
| 指标抖动 | 版本变化、后处理、缓存 |
| Agent 越权 | 工具权限、上下文、审批 |
| 回写覆盖 | 版本号、锁机制、目标表 |
| 索引不新 | 重建流程、刷新策略、缓存 |
| 对齐错误 | 标签口径、字段映射、排序 |
| 反馈污染 | 回流规则、训练边界、审计 |
| 合规遗漏 | 法域、用途、留痕、审批 |
| 资源耗尽 | 算力、磁盘、并发、队列 |
| 版本漂移 | 代码、数据、配置、模板 |
| 接口变更 | 参数、字段、依赖、返回值 |
| 日志缺失 | 关键埋点、异常上下文、追踪 ID |
| 权限失败 | 读写、工具、目录、密钥 |
| 回滚失败 | 快照、依赖、锁、恢复路径 |
这张表的重点不是“记住所有类目”,而是让团队在第一次排查时就能快速锁定范围。问题越早归位,修复越少走弯路。
E.24 从采集到合规的一条线¶
本书相关项目里,Bug 往往不是单点出现,而是沿着一条链路传导:采集出问题,解析不稳;解析不稳,清洗就错;清洗一错,切分和评测都可能偏;最后在 Agent 回写和合规审批时暴露出来。
所以,排障时最好按这条线反向看:
- 先看出口是否出错。
- 再看中间层有没有传染。
- 然后回到入口和来源。
- 最后确认是否有制度层的缺口。
这样做的目的,是避免团队在最表层的症状上反复打转。真正难修的,常常不是某个函数,而是链路里少了一道约束。
E.25 日常维护建议¶
为了减少 Bug 复发,建议把下面三件事变成日常动作:
- 每次上线后保存一份回归样本。
- 每次改规则后记录一次版本差异。
- 每次出现异常后补一条复盘说明。
如果这三件事能持续做,调试手册就不只是出事时翻一翻,而会成为日常工程节奏的一部分。
参考文献¶
Blecher L, Cucurull G, Scialom T, Stojnic R (2023) Nougat: Neural Optical Understanding for Academic Documents. arXiv preprint arXiv:2308.13418.
Breck E, Cai S, Nielsen E, Salib M, Sculley D (2017) The ML Test Score: A Rubric for ML Production Readiness and Technical Debt Reduction. In: IEEE International Conference on Big Data, pp 1123-1132.
Chen D, Huang Y, Ma Z, Chen H, Pan X, Ge C, Gao D, Xie Y, Liu Z, Gao J, Li Y, Ding B, Zhou J (2024) Data-Juicer: A One-Stop Data Processing System for Large Language Models. In: Companion of the 2024 International Conference on Management of Data, pp 120-134. https://doi.org/10.1145/3626246.3653385.
Chen Y, Shetty M, Somashekar G, Ma M, Simmhan Y, Mace J, Bansal C, Wang R, Rajmohan S (2025) AIOpsLab: A Holistic Framework to Evaluate AI Agents for Enabling Autonomous Clouds. arXiv preprint arXiv:2501.06706.
Kapoor S, Narayanan A (2023) Leakage and the reproducibility crisis in machine-learning-based science. Patterns 4(9):100804. https://doi.org/10.1016/j.patter.2023.100804.
Pfitzmann B, Auer C, Dolfi M, Nassar A S, Staar P (2022) DocLayNet: A Large Human-Annotated Dataset for Document-Layout Analysis. In: Proceedings of the 28th ACM SIGKDD Conference on Knowledge Discovery and Data Mining, pp 3743-3751.