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

データパイプライン

隔離した行はどこへ行くのか

TT Labで続きを見る

一言でいうと

隔離は行を捨てない仕組みであって、それ自体が解決ではありません。直して入れ直すループと、古さを見る目がなければ、隔離は誰も開けない引き出しになります。

なぜ必要なのか

品質検査を付けました。使えない行は捨てずに、隔離テーブルに理由と一緒に残します。ここまでは、たいていやります。

半年後にそのテーブルを開いてみると、4万行が入っています。理由は6種類で、そのうち1つが3万行で、その3万行は、すべて同じ日に、上流がフィールドを1つ移し替えたことで生じたものです。その日以降、誰もそのテーブルを見ませんでした。売上集計は、その間3万件が抜けたまま出ていて、ダッシュボードはずっと緑ランプでした。

ここで明らかになるのが、実行の状態とデータの状態が別の軸だという事実です。パイプラインは毎日成功しました。成功したというのは、「エラーなしで終わった」という意味であって、「入れるべきものをすべて入れた」という意味ではありません。2つを1つの欄にまとめて報告すると、どちらも見えなくなります。

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

fde-dataのCSVラボは、顧客が渡したファイル1つの塊から、使えない行を切り離して、リジェクトファイルとして残すところまで行きます。そのファイルは一度来るだけで、リジェクトの一覧は、顧客に送れば終わりです。このコースは、毎日回るパイプラインなので、そこで終わりません。隔離は、実行のたびにまた積み重なり、昨日隔離した行が今日もまた来て、誰かが直して入れると、その行が2回数えられることがあります。1回解きほぐす作業と、回り続ける作業は、設計が違います。

期待値をコードではなくファイルで

期待値(expectation)は、「このデータが正しいなら、こうなっているはず」を書いたものです。条件文でコードの中に散らばらせておくと、何を見ているのか誰もわからなくなります。1つのファイルに一覧として書いておくと、3つのことが変わります。上流と、そのファイルを見ながら話せて、ルールが増えてもコードが変わらず、破られたルールの名前が、隔離の理由になります。

{"rules": [
  {"name": "order_id_format", "column": "order_id", "type": "regex",
   "pattern": "^ORD-[0-9]{6}$"},
  {"name": "qty_range", "column": "qty", "type": "range", "min": 1, "max": 50},
  {"name": "status_enum", "column": "status", "type": "enum",
   "values": ["NEW", "PAID", "CANCELLED"]}
]}

理由をルール名にすると、「なぜ隔離されたか」が、自由な文章ではなく、数えられる値になります。数えられて初めて、理由ごとに何件かを報告し、どの理由が増えているかを見られます。

しきい値を何で決めるか

行1つが間違っていることと、ファイル全体が間違っていることは、別の出来事です。前者は行単位の隔離で扱い、後者は実行単位の停止(circuit break)で扱います。上流がカラムの順序を変えて送ってくると、ほぼすべての行がずれますが、そのときに行単位で隔離すると、隔離テーブルに1万行が入り、集計は空のまま出ていきます。そのようなファイルは、1行も入れないほうがよいです。

分ける基準は、比率です。そして、その比率は普段の値から決めます。「5%」のようなきりのいい数を先に決めると、どちらかになります。普段が7%のデータでは毎日止まり、普段が0.1%のデータでは、20倍壊れても止まりません。直近の実行の失敗率を測っておき、その上に余裕を持たせてとります。しきい値は、データごとに違い、そのため、データごとに書いておく必要があります。

停止するときは、何も入れません。半分だけ入れて止まると、次の人がどこまで入ったかを判断する必要があり、その判断は、いつか間違えます。

入れ直すループと二重計上

隔離した行は、直して入れ直して、初めて終わりです。ここで、2つが壊れやすいです。

1つ目は、ファクトテーブルに2回入ることです。直した行をそのままINSERTすると、元の行と直した行が両方残ります。自然キーに一意制約をかけて、更新として入れれば、この問題はなくなります。

2つ目は、隔離を2回閉じることです。同じ修正を2回実行すると、すでに閉じた隔離をまた閉じてしまい、「今回3件を解決した」が2回報告されます。閉じるときに、まだ開いているものだけを閉じれば、2回目の実行は0件になります。再投入は、必ず何回も実行されます。人が手で回す作業だからです。

そして、直せない隔離もあります。キーそのものが壊れた行です。伝票番号が規格を守らずに隔離されたなら、番号を直した瞬間に、それは別の行になります。このようなものは、再投入ではなく、上流に差し戻す必要があります。

古くなった隔離にアラートをかける

隔離は、積み重なるのが正常です。問題は、減らないことです。そのため、「何件が隔離されたか」ではなく、「最も古い隔離が、何回前の実行のものか」を見ます。

古さ(age)は、時刻よりも実行回数で測るほうが扱いやすいです。1日1回回るパイプラインで、「3回以上前の隔離」は3日を意味し、デプロイが止まってパイプラインが回らなければ、古さも増えません。そのほうが、人の感覚に合っています。

現場での姿

実務で本当に大切なこと

次のラボですること

期待値をファイルに書いておき、ゲートキーパーqgate.pyを1ステップずつ育てます。数えるだけのcheckから作って、sqliteにファクトと隔離を分けて積み、間違いが多すぎる実行は丸ごと止め、直した行を入れ直して、2回入れても数字が変わらないようにし、古くなった隔離を見つけ出し、実行の状態とデータの状態を分けて報告します。採点ツールは、毎回異なるしきい値と異なる店舗で、自分のドロップと期待値を用意して、自分で作ったゲートキーパーを実際に動かし、データベースを直接開いて、ファクトと隔離が正しく入っているかを数えます。