自动化流水线的「幽灵数据」:为什么你的门禁检查统计不到自己刚创建的任务
从 WBS→Asana 流水线中写入111条但门禁只读到19条的真实 Bug,拆解多步骤自动化中数据一致性陷阱
自动化流水线的「幽灵数据」:为什么门禁检查统计不到自己刚创建的任务
上周我们的团队在 WBS → Asana 自动化流水线里撞上了一个极其诡异的 Bug:脚本明明写入了 111 条任务,Asana 后台也能看到 111 条记录,但流水线末尾的「完整性门禁」却只统计到 19 条,导致整个发布永远卡在门禁这一关过不去。更诡异的是——所有数据都在,没有丢失、没有报错、没有网络异常。它只是「看不见」。
这就是我所说的「幽灵数据」:数据客观存在,但在某一环节的视角里被系统性地忽略。这种 Bug 是多步骤自动化流水线里最难查的一类——它不会让你的程序崩溃,只会让你的判断崩溃。
一、现象:111 写入,19 可见
流水线分三步:(1) WBS 解析器把需求结构化为任务树;(2) Sync 模块调用 Asana API 批量创建任务;(3) Gate 模块回查 Asana,核对任务数量是否与 WBS 一致。我们预期三个数字完全对齐,但实际情况是:
WBS 解析出 111 条 → Sync 成功写入 111 条(API 返回 201 × 111) → Gate 回查只拿到 19 条。
第一反应当然是怀疑 Asana 的最终一致性延迟。我们加了 30 秒等待、60 秒等待、甚至手动隔一小时再查,19 这个数字岿然不动。这不是延迟,是结构性问题。
二、根因:创建路径和查询路径用了两套过滤逻辑
花了大半天时间定位,终于在代码里抓到元凶。Sync 模块创建任务时,调用的是 projects/{gid}/tasks 接口,直接把任务挂到项目下,不带 section 参数——意思是「先扔进项目,后续再分配 section」。而 Gate 模块回查时,为了「精确」,用的是 sections/{gid}/tasks 接口,只查已分配 section 的任务。
于是事情就是这样:111 条任务全部成功创建,其中 19 条因为带了默认 section 被查到,另外 92 条因为还没分配 section,对 Gate 的视角来说压根不存在。它们不是没写进去,是「写进去了但查询维度不覆盖」。数据库里该有的都有,但两段代码看待同一批数据的 WHERE 子句不一样。
这个 Bug 的危险之处在于:它不会在任何单元测试里暴露。Sync 的单元测试只验证「写入是否成功」,Gate 的单元测试只验证「查询是否返回结果」,两边各自跑得漂亮。只有在端到端场景下,当写入口径和查询口径不匹配时,幽灵才会出现。
三、为什么这个坑在自动化流水线里特别常见
我复盘了一下,这类 Bug 的根本成因其实只有一条:在多阶段流水线里,写入方和查询方往往由不同的人在不同的时间点写的,他们对「一条有效数据的定义」没有对齐。
Sync 的作者心里,「有效任务 = 成功 POST 到 Asana 的任务」;Gate 的作者心里,「有效任务 = 归属到合法 section 的任务」。这两个定义在各自的代码里都是合理的,放到一起就错位了。类似的坑在 CI/CD 系统里也很常见——上游构建产物标签是 v1.2.3-rc,下游部署环节只认 v1.2.3,于是「构建成功但永远没东西可部署」。
更隐蔽的版本是状态字段:创建时是 pending,查询只看 active,中间没有状态机推进,数据就永久消失在系统的缝隙里。
四、可复用的三条原则
**1. 写入和查询必须共用同一个「数据视图定义」。**把「什么叫一条有效任务」抽成单一的常量或函数,Sync 和 Gate 都从同一个地方引用。不要让两段代码各自写 filter 条件。
**2. 门禁的第一性校验应该用「最宽口径」,而不是「最精确口径」。**Gate 的工作是确认数据已落地,不是确认数据已经到位于终态。校验应当用 projects/{gid}/tasks 拉全量,然后在内存里做细分判断,而不是直接向 API 要一个过滤视图。
**3. 每条自动化流水线都要有一个「守恒律断言」。**写入 N 条,下游可见必须等于 N 条,不等就立刻报错并打印两边各自的 ID 集合差集。这一行代码本来 10 分钟能写,能帮你省下一整天的排查。
幽灵数据之所以是幽灵,是因为它同时符合「存在」和「不可见」两个条件。打破任何一个条件——让不可见变可见——Bug 就现形了。
如果你在构建 AI 工程团队,欢迎参考我们开源的 Synapse 框架。