TT Lab
开始
学习 学习路径 课程

数据流水线

这个数字是从哪来的 — 血缘与可复现

在 TT Lab 中继续学习

一句话总结

在每个产出旁边记下它是由哪些输入(内容哈希)、哪个代码版本、哪些参数生成的,然后用同样的输入重新运行一遍,用字节是否相同来证明这份记录。

为什么需要它

季度报告中的销售额和会计那边的数字差了 3000 万韩元。管道那天正常结束,日志也很干净。问题只有一个——这个数字是从哪里来的?

想回答的时候,手里能抓住的东西并不多。一个产出文件,再加上“那天运行过”的一行日志。哪些投放文件(drop)进来了、那些文件现在的内容是否还和当时一样、代码在那之后有没有改过、阈值参数当时是多少——这些一样都没有留下。所以大多数调查都走向“重新跑一遍看看”。可是如果重新跑出的结果和当时不同,又不知道是什么变了才导致不同。

文件名和修改时间不能作为依据。名字相同但上游把文件覆盖了的情况很常见,而修改时间只要复制、移动、从备份恢复就会变。反过来,内容变了但时间没变的情况也有。能作为依据的,只有内容本身的哈希。

本课程与 fde-data 的分野

fde-data 讲的是把客户给的一个文件拆开来看。那个文件只来一次,有异常就去问发送方。本课程不同——每天都有投放文件以同样的名字落在同一个位置,同样的转换一遍一遍地运行。所以问题不再是“这个文件怎么读”,而是“三个月前的那次运行读了哪些文件”。只有在有重复的地方,谱系才会成为问题,也正因为有重复,复现才成为可能。

清单里记什么

在产出旁边留下一个同名的 JSON,也就是清单(manifest)。要记的有四样。

{
  "code_sha256": "9f2c...",
  "params": {"min_qty": 1},
  "inputs": [{"name": "orders-2026-03-01.csv", "sha256": "4a1e...", "rows": 12}],
  "output": {"name": "shops.csv", "sha256": "7b30...", "rows": 4},
  "run_id": "a41c9d02f7e3b118",
  "created_at": "2026-03-04T02:11:00+00:00"
}

有了这一页,前面的问题就都能回答了。把输入文件现在重新算一遍哈希,就能知道它和当时是否相同;比较代码哈希,就能知道它在那之后有没有改变;重新运行并对照输出哈希,就能知道这份记录是否正确。

破坏复现的东西

“相同的输入得到相同的输出”不会自动成立。悄悄破坏它的有四样。

第一,当前时间。如果在产出里写进生成时间,两次运行的结果就一定不同。需要时间的话,就写在清单里而不是产出里,并明确说明它是复现比较中被排除的字段。

第二,用随机数生成的标识符。如果把每次运行用 uuid4 生成的 run_id 放进清单,即使用相同的输入运行,清单也会不同。标识符应该从内容派生——把代码哈希、参数和输出哈希连起来再做哈希,相同的运行就有相同的名字,不同的运行就有不同的名字。

第三,顺序没有保证的东西。os.listdir 的顺序是不确定的,集合(set)的遍历顺序即使在同一个程序里也会因每次运行而不同(Python 对字符串哈希每次运行都使用不同的种子)。所以文件列表和分组列表都要排序后再写。

第四,浮点数相加的顺序。(a + b) + c 和 a + (b + c) 在浮点数中并不是同一个值。行的顺序一旦摇摆,合计的最后一位就会摇摆,于是字节就不同了。像金额这种小数位固定的值,换成整数分(cent)再相加,这个问题就彻底消失了。

数据集级与列级

谱系有粗细之分。数据集级只到“这张表来自那三个文件”。它容易做,出了故障时,用它来扫一遍影响范围就够了。

列级则深入到“这张表的 amount_cents 列来自输入的 amount、order_id 和 shop”。当上游通知说要改某一列时,能回答“我们产出的哪一列会受到波动”的,就是这个粗细。

列级谱系不能只记下来就完事。代码一改,那份文档立刻就过时了。证明的办法是做实验——让一个输入列发生扰动后重新运行,看哪些输出列发生变化。变了就有依赖,没变就没有。这样一测,“以为在用、其实没用的列”也会一并显露出来。

在现场相遇的样子

实际工作中真正重要的事

下一项实验要做什么

一步步扩充用于汇总每天投放的订单的转换器 lineage.py。先做出输出内容哈希的 digest,确认名字和时间为什么不能作为依据,然后一并输出汇总产出和清单,运行两次证明字节相同,用实验确认列级谱系,再加上按清单重新生成并对照的 verify,以及回答“这个数字来自哪些输入”的 trace。评分器每次都会用不同的店铺名和金额生成自己的投放文件,真正运行你的转换器,并亲手对输入列做扰动,来确认你写出的列谱系是否正确。