こちらのログは全件成功、相手には十二件がない
目標
まとめて送ったリクエストで、数件だけが失敗する状況を扱います。件ごとの結果をこちら側に記録し、再送する失敗と直すべき失敗を分け、失敗した分だけを再送し、最後に、送った側と受け取った側の件数と合計を突き合わせる突合表を作ります。
なぜ重要なのか
バッチ送信APIは、リクエスト1つに200を返しながら、本文の中に件ごとの結果を入れます。HTTPの観点では、200は正しいのです。2xxは、リクエストを受け取って受け入れたという意味であり、リクエストの中のすべての項目が成功したという意味ではありません。 そのため、呼び出した側がステータスコードだけを見ると、失敗した数件は、どこにも残りません。エラーも警報もなく何日も積み重なり、相手側の数字と突き合わせているときに発見されます。 直す順序は、4つです。件ごとに記録し、失敗を2つの系統に分け、失敗した分だけを再送し、両側の数を突き合わせます。特に3つ目が重要です。バッチ全体を再送すると、すでに入った件が二重に入ります。再試行の単位は、バッチではなく件です。 採点ツールは、作成した文章を信じません。パートナーサーバーを、採点ツールが選んだポートで新しく起動し、作成したアウトボックスと送信器を、採点ツールのデータベースに対して実際に動かして、相手の元帳と突き合わせます。
ステップ
- /root/recon/receiver.pyを作成してポート8010で起動し、/root/recon/gen_outbox.pyで、送るもの120件を/root/recon/outbox.dbに作成してください。
- /root/recon/send.pyの
--naiveで、ステータスコードだけを見て送ったあと、こちらの記録と相手の元帳の差を、/root/recon/naive.jsonに書いてください。 - send.pyが、件ごとの結果を読んで、
sentの表に、status・reason・attemptsとして記録するようにしてください。 - /root/recon/classify.pyを作成して、失敗の理由を、再送するものと直すものに分けるようにしてください。
- send.pyの
--retryが、再送してよい件だけを選んで再送するようにしてください。 - /root/recon/recon.pyで、送った側と受け取った側を突き合わせて、/root/recon/recon_result.jsonを作成してください。
- 残った件の処理計画を、/root/recon/unresolved.jsonに書いてください。
- /root/recon/recon_report.mdに、4つの節で報告してください。
参考
- パートナーの実行契約:
python3 /root/recon/receiver.py --port <포트>(プレースホルダーはポートです)。POST /batchは、{"batch_id": ..., "items": [{"item_id", "account", "amount"}]}を受け取って、200と一緒に、{"batch_id", "accepted", "rejected", "results": [{"item_id", "status", "reason"}]}を出力します。GET /ledgerは、{"rows": [...], "count": n, "total": m}です。 - パートナーの拒否の理由は、4つです。
invalid_amount(金額が正の整数でない)・unknown_account(知っている口座がAC-01からAC-08までだけ)・duplicate(すでに元帳にある)・temporary_hold(金額が97の倍数の件を、最初の1回だけ保留にする)。判定の順序は、この順です。 - アウトボックスの実行契約:
python3 gen_outbox.py [--db <경로>](プレースホルダーはパスです)は、outbox(item_id, account, amount, batch_id)120件と、空のsent(item_id, status, reason, attempts)を作成します。120件のうち、口座が間違っているものが5件、金額が0のものが3件、97の倍数のものが4件、混ざっている必要があり、バッチは、30件ずつ4つです。 - 送信器の実行契約:
python3 send.py --base <URL> --db <sqlite> [--batch-size 30] [--retry] [--naive]は、{"sent": n, "accepted": n, "rejected": n, "by_reason": {...}, "http_ok": n}を出力します。既定では、まだ送っていない件を、--retryは、先に拒否された件のうち、再送してよいものだけを、送ります。--naiveは、件ごとの結果を読まず、ステータスコードだけを見て、sentの表に何も書きません。 - 分類器の実行契約:
python3 classify.py --reason <이유>(プレースホルダーは理由です)は、{"retryable": true|false, "action": "requeue"|"fix_data"|"ignore"}を出力します。知らない理由は、安全な側(再送しない)で答えます。 - 突合器の実行契約:
python3 recon.py --base <URL> --db <sqlite> --out <결과 JSON>(プレースホルダーは結果のJSONです)は、outbox_items・accepted・rejected・not_sent・partner_rows・partner_total・our_accepted_total・only_ours・only_theirs・by_reason・unresolved・balancedを出力します。balancedは、送っていない件がなく、両側にだけある件もなく、件数と合計がすべて合うときに、trueです。 - 残った件の形式:
{"total": n, "by_action": {...}, "items": [{"item_id", "reason", "action", "owner"}]}。ownerは、人またはチームの名前です。持ち主がいなければ、その件は永遠に残ります。 - よくあるミス: ステータスコードだけを見ること、失敗したものだけを記録すること(送ったことのないものと区別できません)、バッチ全体を再送すること(重複が生じます)、件数だけを突き合わせて合計を見ないこと。
- サーバーはバックグラウンドで起動し、
/healthが200になるまで待ってから、次に進みます。採点ツールは、起動しておいたプロセスを見ず、スクリプトを直接起動し直します。
バッチ受信パートナーと、送るもの120件
/root/recon/receiver.pyを作成してポート8010で起動し、/root/recon/gen_outbox.pyを作成して実行し、/root/recon/outbox.dbに120件を入れてください。口座が間違っているものが5件、金額が0のものが3件、97の倍数のものが4件、混ざっている必要があります。
パートナーは、リクエスト自体には常に200を返し、失敗を本文のresultsの中に入れます。拒否の判定は、金額・口座・重複・一時保留の順に見ます。アウトボックスは、送るものと送った結果を分けて、2つの表にしてください。1つの表に混ぜると、「送ったことのないもの」と「送って失敗したもの」が区別できません。
ステータスコードだけを見ると、何を見逃すか
/root/recon/send.pyに--naiveを作成して、ステータスコードだけを見て、すべて成功として数えるようにしてください。その結果と、相手の元帳の差を、/root/recon/naive.jsonに、batches・http_ok・assumed_sent・partner_rows・gapで書いてください。
リクエストは、本当に成功しました。失敗は、本文の中にあります。こちらの記録が120件成功なのに、相手の元帳には何行あるかを数えてみれば、差がそのまま現れます。このステップでは、sentの表に何も書きません。
件ごとの結果を、こちら側に残す
send.pyが、応答本文のresultsを読んで、件ごとに、sentの表に、status・reason・attemptsを残すようにしてください。成功も失敗も、すべて記録する必要があります。2回目の実行では、すでに送った件を再送してはいけません。
失敗したものだけを書くと、「送ったことがないもの」と「送って成功したもの」が区別できません。応答のresultsの順序が、リクエストの順序と同じだと仮定せず、item_idで合わせて読むほうが、安全です。まだ送っていない件は、outboxにはあって、sentにはない件です。
再送するものと、直すもの
/root/recon/classify.pyを作成して、--reasonで受け取った理由を、{"retryable": ..., "action": ...}で判定するようにしてください。actionは、requeue・fix_data・ignoreの3つで、知らない理由は、再送しない側で答えます。
一時保留は、そのまま再送すれば解決します。不明な口座と不正な金額は、何回送っても同じ答えが返るので、人がデータを直す必要があります。すでに相手の元帳に入った件は、再送するものも、直すものもありません。この分類がなければ、直せない件がキューを永遠に回ります。
バッチではなく、件の単位で再び
send.pyの--retryが、先に拒否された件のうち、再送してよいものだけを選んで再送し、結果をsentに更新するようにしてください。すでに成功した件は、絶対に再送してはいけません。
バッチ全体を再送すると、すでに入った件が二重に入ります。相手が重複を防いでくれれば幸いですが、防いでくれなければ、こちらが事故を起こします。分類器を呼び出して、retryableのものだけを選び、attemptsを上げておかなければ、何回目の試行かが、あとでわかりません。
送った側と受け取った側を突き合わせる
/root/recon/recon.pyで、アウトボックス・こちらの記録・相手の元帳を突き合わせて、/root/recon/recon_result.jsonを作成してください。件数だけでなく、合計と、両側にだけある件まで見る必要があり、すべて合うときだけ、balancedがtrueです。
件数だけを突き合わせると、1件が抜けて別の1件が二重に入った場合を、見逃します。こちらが成功として書いたidの集合と、相手の元帳のidの集合を、互いに引いてみれば、どちらにだけあるかが、すぐに出ます。まだ送っていない件があるかも、一緒に数えます。
残った件には、持ち主がいなければならない
/root/recon/unresolved.jsonに、まだ解決していない件を、total・by_action・items(item_id・reason・action・owner)で書いてください。actionは、分類器の答えと同じである必要があり、ownerは、空にできません。
「直す必要がある8件」を作っておいて、誰が直すかを書かなければ、その8件は永遠に残ります。突合表の最後の欄は、人またはチームの名前です。itemsは、sentの表で、拒否のまま残った件を、そのまま取り出して作ればよいです。
突合報告書
/root/recon/recon_report.mdに、## 무엇을 놓치고 있었나、## 건별 결과를 읽고 나서、## 다시 보낼 것과 고칠 것、## 대사표와 남은 것(韓国語の見出しで、順に何を見逃していたか、件ごとの結果を読んだあと、再送するものと直すもの、突合表と残ったもの、を意味します)の4つの節で書いてください。naive.jsonとrecon_result.jsonの数字が、本文に入っている必要があります。
読む人は、「こちらのログには、すべて成功と出ていますが」と言った人です。そのログが嘘をついたのではなく、リクエストのステータスコードだけを見たものだという点を、まず説明してください。そのあと、理由ごとの件数と、突合表の2つの数字を見せればよいです。