隔離を運用する — 積み、止め、戻す
目標
期待値をファイルに書いておき、ドロップを実行ごとに行単位でふるいにかけて、sqliteのファクトテーブルと隔離テーブルに分けて積むゲートキーパーqgate.pyを作ります。間違いが多すぎる実行は丸ごと止め、直した行を入れ直しても二重計上が起きないようにし、古くなった隔離を見つけ出し、実行の状態とデータの状態を分けて報告します。
なぜ重要なのか
使えない行を捨てずに隔離に残すところまでは、たいていやります。問題は、その次です。隔離は、実行のたびにまた積み重なり、半年後に開いてみると4万行が入っていて、その間、集計はその分が抜けたまま出ていました。パイプラインは毎日成功しました。成功とは、エラーなしに終わったという意味であって、入れるべきものをすべて入れたという意味ではありません。行1つが間違っていることと、ファイル全体が間違っていることも、別の出来事です。上流がカラムの順序を変えて送ると、ほぼすべての行がずれますが、そのときに行単位で隔離すると、隔離テーブルに1万行が入り、集計は空のまま出ていきます。そのようなファイルは、1行も入れずに、実行を止めるほうがよいです。分ける基準は比率で、その比率は、きりのいい数ではなく、普段の失敗率から決めます。そして、直した行を入れ直すループが必要です。再投入は、人が手で回す作業なので、必ず何回も実行され、そのときに、ファクトテーブルに2回入ったり、すでに閉じた隔離をまた閉じて、「3件解決」が2回報告されたりしやすくなります。採点ツールは、提出された文言を信じません。一時ディレクトリに、採点ツールが作ったドロップと期待値を用意して、自分で作ったゲートキーパーを実際に実行したあと、作られたsqliteファイルを直接開いて、ファクトと隔離と実行記録を、採点ツールが直接数えた値と突き合わせます。しきい値と店舗と金額は、実行ごとに変わります。
ステップ
- /root/quarantine/gen_drops.pyを作成して実行し、/root/quarantine/dropsの下にドロップ4つと、/root/quarantine/expectations.jsonを作ってください。
- /root/quarantine/qgate.pyに
checkを作り、期待値で数えるだけにしてください。 runを追加して、sqliteにファクトと隔離と実行記録を積ませてください。run --max-fail-ratioを追加して、間違いが多すぎる実行を丸ごと止めさせてください。requeueを追加して、直した行を入れ直しても、2回入れても数字が変わらないようにしてください。agingを追加して、古くなった隔離を見つけ出させてください。statusを追加して、実行の状態とデータの状態を分けて報告させてください。- 自分のドロップ4つを順番に回して/root/quarantine/pipeline.dbを作り、/root/quarantine/status.jsonと/root/quarantine/quarantine_report.mdを書いてください。
参考
- 実行の契約:
python3 /root/quarantine/qgate.py <명령> ...(プレースホルダーはコマンドです)。答えは、JSONの1つの塊として標準出力に出します。成功すれば終了コード0、ファイルがなければ3、使い方が間違っていれば2、実行が止まれば5です。 - ドロップと再投入ファイルのヘッダーは
order_id,shop,qty,amount,statusです。再投入ファイルは、どこに置いてもかまいません。ステップ5の正解ファイルは/tmp/fixes.csvに作ります。 - 期待値ファイルは
{"version": 1, "rules": [...]}で、ルールごとにname・column・typeがあります。typeは5種類です。not_null、regex(pattern)、range(min・max、境界を含む)、enum(values)、unique(そのファイルの中で最初に出た値だけが通過)。 - ルールの適用ルール: 値は前後の空白を除いて見ます。空の値は、
not_null以外はすべて失敗です。正規表現は、値全体と一致する必要があります。一意性は、ファイルの先頭の行から見ます。 checkの応答:{"drop": 이름, "rows": 정수, "passed": 정수, "failed": 정수, "fail_ratio": 소수, "by_rule": {규칙이름: 정수}}(プレースホルダーは、名前、整数、小数、ルール名です)。failedは、1つ以上のルールを破った行の数で、by_ruleはルールごとに破った行の数なので、合計が違うことがあります。by_ruleには、期待値のすべてのルール名が、0でも入ります。fail_ratioは、小数点以下第6位で四捨五入します。- sqliteの表は3つです。
runs(run_id, drop_name, status, rows, passed, failed, loaded, quarantined)、facts(order_id, shop, qty, amount_cents, run_id)、quarantine(q_id, order_id, rule, detail, raw, first_run_id, resolved_run_id)。run_idとq_idは自動増分で、order_idはファクトテーブルの主キーです。amount_centsは、金額を整数のセントに変えた値です。 runの応答:checkのキーに、run_id・status・loaded・quarantined・max_fail_ratioが加わります。statusはloadedまたはbrokenです。- 隔離は、同じ行の同じ理由がまた来ても、1回だけ開きます(開いている同じ
order_id・ruleがあれば、新しく入れません)。1つの行が2つのルールを破れば、隔離は2行になります。 --max-fail-ratioのデフォルト値は1.0なので、止めません。fail_ratioがしきい値を超えたら(同じなら通過)、実行を止めます。止まった実行は、runsにbrokenとして残し、ファクトも隔離も1行も入れず、終了コード5です。requeueの応答:{"run_id": 정수, "status": "requeued", "rows": 정수, "fixed": 정수, "still_failing": 정수, "resolved": 정수, "facts": 정수}(プレースホルダーは、整数です)。再投入も実行なので、runsにrequeuedとして1行残ります。通過した行は、ファクトテーブルに更新として入れ、そのorder_idのまだ開いている隔離だけを閉じます。factsは、入れたあとのファクトテーブルの件数です。agingの応答:{"max_age_runs": 정수, "latest_run_id": 정수, "open": 정수, "aged": 정수, "by_rule": {규칙이름: 정수}, "oldest": {"q_id": 정수, "order_id": 문자열, "rule": 문자열, "age": 정수} 또는 null, "alert": 참거짓}(プレースホルダーは、整数、ルール名、文字列、真偽値です。oldestはオブジェクトかnullのどちらかを意味し、その間の韓国語の語は「または」という意味です)。古さ(age)はlatest_run_id - first_run_idで、max_age_runs以上なら古くなったものです。by_ruleには、古くなった隔離があるルールだけを入れ、oldestは、開いている隔離のうちfirst_run_idが最も小さいもの(同じならq_idが小さいもの)です。statusの応答:{"runs": {"total": 정수, "loaded": 정수, "broken": 정수, "requeued": 정수, "last_status": 문자열}, "data": {"facts": 정수, "open": 정수, "resolved": 정수, "aged": 정수}, "runs_ok": 참거짓, "data_ok": 참거짓}(プレースホルダーは、整数、文字列、真偽値です)。runs_okは、実行があり、最後の実行がbrokenではないか、data_okは、開いている隔離が0で、古くなった隔離が0かです。--max-age-runsのデフォルト値は3です。- ステップ8のレポートは、
## 이번 실행、## 격리에 무엇이 쌓였나、## 재투입과 이중 계산、## 임계치를 무엇으로 정했나の4つの節で書き(韓国語の見出しで、順に「今回の実行」「隔離に何が積まれたか」「再投入と二重計上」「しきい値を何で決めたか」を意味します)、ファクトの件数と開いている隔離の件数を、数字で書きます。 - 公式ドキュメント: sqlite3モジュール・UPSERT・re.fullmatch・csv
- データベースを目で見るには、
sqlite3 /root/quarantine/pipeline.db 'select * from runs'を使ってください。 - よくある間違い: 隔離を実行のたびにまた入れて、同じ行が積み重なる、再投入を挿入で入れて、ファクトが2倍になる、すでに閉じた隔離をまた閉じる、しきい値を普段の値ではなく、きりのいい数で決める。
ドロップと期待値をファイルとして置く
/root/quarantine/gen_drops.pyを作成して実行し、/root/quarantine/dropsの下にドロップ4つと、/root/quarantine/expectations.jsonを作ってください。期待値は、5種類のtypeをすべて使い、ドロップの1つは、半分を超えて期待値を破っている必要があります。
期待値をコードではなくファイルに置けば、上流とそのファイルを見ながら話せて、破られたルールの名前が、そのまま隔離の理由になります。ドロップには、ステータスの値が一覧にない行、負の金額、範囲を超える数量、空の店舗、規格を守っていない伝票番号、前の行と同じ伝票番号を混ぜてください。3つは、破る行が半分未満で、1つは半分を超える必要があります。
期待値で数えるだけにする
/root/quarantine/qgate.pyにcheck --drop <파일> --expect <파일>(プレースホルダーは、ファイルです)を作り、drop・rows・passed・failed・fail_ratio・by_ruleをJSONで出力させてください。
failedは、1つ以上のルールを破った行の数で、by_ruleはルールごとに破った行の数なので、合計が違います。1つの行が2つのルールを破ることがあるからです。by_ruleには、誰も破らなかったルールも、0として入れてください。値が空なら、not_null以外はすべて失敗と見なします。
ファクトと隔離を分けて積む
run --drop <파일> --db <파일> --expect <파일>(プレースホルダーは、ファイルです)を追加して、通過した行はfactsに更新として入れ、破った行は、破ったルールごとにquarantineに開き、実行をrunsに残させてください。同じ行の同じ理由がまた来ても、隔離は1回だけ開きます。
3つの表のカラム名と順序は、参考の節にあります。ファクトテーブルは、order_idが主キーなので、更新として入れます。隔離を開く前に、同じorder_idとruleで、まだ開いている行があるかを見てください。見なければ、実行のたびに同じ行が積み重なります。
間違いが多すぎる実行は丸ごと止める
run --max-fail-ratio R(プレースホルダーは比率です)を追加して、fail_ratioがしきい値を超えたら、実行を止めるようにしてください。止まった実行は、runsにbrokenとして残し、ファクトも隔離も1行も入れず、終了コードは5です。
行1つが間違っていることと、ファイル全体が間違っていることは、別の出来事です。しきい値は、きりのいい数ではなく、普段の失敗率から決めます。checkでドロップ4つの比率を測ってみてから決めてください。止めるときに、半分だけ入れて止まると、次の人がどこまで入ったかを判断する必要があります。
直した行を入れ直す
requeue --db <파일> --fixes <파일> --expect <파일>(プレースホルダーは、ファイルです)を追加して、直した行を期待値でもう一度見て、通過すればファクトテーブルに更新として入れ、その伝票のまだ開いている隔離だけを閉じるようにしてください。同じ再投入を2回実行しても、ファクトの件数と閉じた隔離の数が、増えない必要があります。
再投入は、人が手で回す作業なので、必ず何回も実行されます。挿入で入れるとファクトが2倍になり、閉じるときに「開いている」条件をかけないと、「3件解決」が2回報告されます。2回目の実行のresolvedは、0である必要があります。伝票番号そのものが壊れた行は、再投入では直せません。番号を直すと、別の行になるからです。
古くなった隔離を見つけ出す
aging --db <파일> [--max-age-runs N](プレースホルダーは、ファイルです)を追加して、開いている隔離の古さをlatest_run_id - first_run_idで測り、max_age_runs以上のものを数えて、open・aged・by_rule・oldest・alertで出力させてください。
件数だけを見ても、減らないものを見られません。古さは、時刻よりも実行回数で測るほうが扱いやすいです。パイプラインが回らなければ、古さも増えないほうが、人の感覚に合っています。oldestは、開いているもののうち、first_run_idが最も小さいものです。
成功したことと正しいことを分けて報告する
status --db <파일> [--max-age-runs N](プレースホルダーは、ファイルです)を追加して、実行の状態(runs)とデータの状態(data)を分けて出力し、runs_ok・data_okの2つの値で答えさせてください。
実行が成功したことと、データが正しいことは、別の軸です。1つの欄にまとめると、どちらも見えなくなります。runs_okは、実行があり、最後の実行が止まっていないかで、data_okは、開いている隔離と、古くなった隔離が、どちらも0かです。両方が真の日は、まれです。
自分のドロップで1週間分を回して報告する
自分のドロップ4つを日付順に回して/root/quarantine/pipeline.dbを作り、statusの答えを/root/quarantine/status.jsonに残し、/root/quarantine/quarantine_report.mdを4つの節で書いてください。停止のしきい値は、普段の失敗率を測って決めてください。
しきい値を普段の値から決めれば、半分を超えて破ったその日の分だけが止まります。status.jsonは、手で書かず、statusコマンドの答えをそのまま保存してください。採点ツールは、データベースを直接開いて突き合わせます。レポートには、ファクトの件数と開いている隔離の件数を、数字で書いてください。