TT Lab
はじめる
学ぶ 学習パス コース

データパイプライン

この数字はどこから来たのか — 系譜と再現

TT Labで続きを見る

一言でいうと

出力物ごとに、どの入力(コンテンツハッシュ)と、どのコードのバージョンと、どのパラメーターで作られたかを横に書いておき、同じ入力でもう一度回してバイトが同じかで、その記録を証明します。

なぜ必要なのか

四半期レポートの売上が、経理側の数字と3,000万ウォンずれています。パイプラインはその日正常に終了し、ログもきれいです。問いは1つです。この数字はどこから来たのか。

答えようとすると、手元にあるものがほとんどありません。出力ファイル1つと、「その日回った」というログ1行。どのドロップファイルが入ったのか、そのファイルが今もそのときと同じ内容なのか、コードがその後に変わっていないか、しきい値のパラメーターがそのときいくつだったか。どれも残っていません。そのため、ほとんどの調査は、「もう一度回してみよう」に行きます。ところが、もう一度回した結果がそのときと違えば、何が変わって違うのかを、また知りません。

ファイル名と更新時刻は、根拠になりません。名前は同じなのに、上流がファイルを上書きした場合がよくあり、更新時刻は、コピー・移動・バックアップの復元だけでも変わります。逆に、内容が変わったのに時刻がそのままの場合もあります。根拠になるのは、内容そのもののハッシュだけです。

このコースとfde-dataが分かれるところ

fde-dataは、顧客が渡したファイル1つの塊を解きほぐす作業です。そのファイルは一度来るだけで、変なら、送る側に聞けばよいです。このコースは違います。毎日同じ場所に同じ名前でドロップが落ち、同じ変換が繰り返し回ります。そのため、問いは「このファイルをどう読むか」ではなく、「3か月前のあの実行は、どのファイルを読んだか」になります。繰り返しがある場所でだけ、リネージが問題になり、繰り返しがあるから、再現が可能になります。

マニフェストに何を書くか

出力物の横に、同じ名前のJSONを1つ残します。書くものは4つです。

{
  "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"
}

この1枚があれば、前の問いにすべて答えられるようになります。入力ファイルを今もう一度ハッシュしてみれば、そのときと同じかわかり、コードのハッシュを比べれば、その後に変わったかがわかり、もう一度回して出力のハッシュを突き合わせれば、記録が正しいかがわかります。

再現を壊すもの

「同じ入力なら同じ出力」は、自然には成り立ちません。静かに壊すものが4つあります。

1つ目は、現在時刻です。出力物の中に生成時刻を書き込むと、2回回した結果は、必ず違います。時刻が必要なら、出力物ではなくマニフェストに書き、再現の比較から除外される欄だと明記します。

2つ目は、乱数で作った識別子です。実行ごとにuuid4で作ったrun_idをマニフェストに入れると、同じ入力で回しても、マニフェストが違ってきます。識別子は、内容から導出すればよいです。コードのハッシュとパラメーターと出力のハッシュをつなげてハッシュすれば、同じ実行は同じ名前を持ち、違う実行は違う名前を持ちます。

3つ目は、順序が約束されていないものです。os.listdirの順序は決まっておらず、集合(set)の走査順序は、同じプログラムでも実行ごとに変わります(Pythonは、文字列のハッシュに、実行ごとに異なるシードを使います)。そのため、ファイルの一覧もグループの一覧も、ソートして書きます。

4つ目は、浮動小数点を足す順序です。(a + b) + cとa + (b + c)は、浮動小数点では同じ値ではありません。行の順序が揺れれば、合計の最後の桁が揺れ、そうするとバイトが変わります。金額のように、小数の桁が決まっている値は、整数(セント)に変えて足せば、この問題がそもそもなくなります。

データセット単位とカラム単位

リネージには、粒度があります。データセット単位は、「この表は、あの3つのファイルから来た」までです。作りやすく、障害が起きたときに影響範囲をたどるには、これで十分です。

カラム単位は、「この表のamount_centsカラムは、入力のamountとorder_idとshopから来た」まで下ります。上流がカラム1つを変えると知らせてきたときに、私たちの出力物のどの欄が揺れるかを答えられる粒度が、これです。

カラム単位のリネージは、書いておくだけで終わってはいけません。コードが変われば、そのドキュメントはすぐに古くなります。証明する方法は、実験です。入力カラム1つを揺らしてもう一度回し、どの出力カラムが変わるかを見ます。変われば依存があり、変わらなければありません。このように測れば、「使っていると思ったのに使っていないカラム」も、一緒に明らかになります。

現場での姿

実務で本当に大切なこと

次のラボですること

毎日落ちてくる注文ドロップを集計する変換ツールlineage.pyを1ステップずつ育てます。コンテンツハッシュを出すdigestから作って、名前と時刻がなぜ根拠にならないかを確認し、集計の出力物とマニフェストを一緒に出し、2回回してバイトが同じかを証明し、カラム単位のリネージを実験で確認し、マニフェストどおりに作り直して突き合わせるverifyと、「その数字がどの入力から来たか」に答えるtraceを付けます。採点ツールは、毎回異なる店舗と金額で自分のドロップを用意して、自分で作った変換ツールを実際に動かし、入力カラムを直接揺らして、自分で書いたカラムのリネージが正しいかを確認します。