リクエストは 200 なのに、三件が消えた
一言でいうと
まとめて送るAPIは、リクエスト1つに200を返しながら、本文の中に件ごとの結果を入れていることが多く、呼び出した側がステータスコードだけを見ると、失敗した数件は、どこにも残らないまま消えてしまいます。そのため、件ごとの結果を記録し、再送するものと直すものを分け、最後に両側の数を突き合わせる突合が必要です。
なぜ必要なのか
「昨日、精算を200件送ったのに、相手側には187件しかないそうです」。この報告で、最初に確認するのは、こちらのログです。ところが、こちらのログには、200件すべて成功と書かれています。嘘をついたわけではありません。こちらが見たものが、リクエストのステータスコードだけだったのです。
バッチ送信APIの応答は、たいていこのような形をしています。
{"batch_id": "B-07", "accepted": 27, "rejected": 3,
"results": [{"item_id": "IT-0031", "status": "rejected", "reason": "unknown_account"},
{"item_id": "IT-0032", "status": "accepted", "reason": null}]}
リクエストは成功しました。サーバーはリクエストを受け取って処理し、その結果が本文に入っています。HTTPの観点では、200は正しいのです。RFC 9110が定義する2xxは、「リクエストを受け取り、理解し、受け入れた」という意味であり、「リクエストの中のすべての項目が成功した」という意味ではありません。
そのため、この事故は、エラーも警報もなく起きます。そして、何日も積み重なってから、相手側の数字と突き合わせているときに、発見されます。
どう動くのか
直す順序は、4つです。
1つ目、件ごとの結果を、こちら側に残します。送った件ごとに、status・reason・시도 횟수(韓国語で試行回数を意味する語です)を記録します。この記録がなければ、次の3つの段階を行えません。よくあるミスは、「失敗したものだけ」を記録することですが、そうすると、「送ったことがないもの」と「送って成功したもの」が、区別できなくなります。
2つ目、失敗を2つの系統に分けます。再送すれば解決する失敗と、人が直さなければならない失敗です。
| 理由 | 再送すると | すべきこと |
|---|---|---|
| 一時保留・レート制限・一時的なエラー | 解決する | キューに入れ直す |
| 不明な口座・不正な値 | 同じ答えが返る | データを直す |
| すでに処理済み(重複) | 同じ答えが返る | 何もしない |
この分類がなければ、2つのうちどちらかになります。すべて再試行すると、直せない件がキューを永遠に回り続け、何も再試行しなければ、一時的に引っかかった件まで、人の手に回ります。レート制限は、RFC 6585の429で来ることが多く、Retry-Afterが一緒に付きます。それは、明らかに再送すべき側です。失敗の理由を、機械が読める形で載せる標準の形式としては、RFC 9457のproblem detailsがあり、typeで系統を区別します。
3つ目、失敗した分だけを再送します。ここでバッチ全体を再送すると、すでに入った件が、二重に入ります。相手が重複を防いでくれれば幸いですが、防いでくれなければ、こちらが事故を起こします。再試行の単位は、バッチではなく件です。
4つ目、突合します。送る側の「成功として記録した件数と合計」と、受け取る側の「実際に入った件数と合計」を、突き合わせます。2つの数字が同じでなければならず、違えば、どの件がどちらにだけあるかまで、掘り下げます。
발신함 120건
├─ 성공 기록 112건 · 합계 X
└─ 실패 기록 8건 (고쳐야 함 8)
상대 원장 112행 · 합계 X ← 건수와 합계가 **둘 다** 맞아야 한다
このコードブロックの韓国語は、アウトボックス120件、成功の記録112件、失敗の記録8件(直す必要がある件8)、相手の元帳112行、件数と合計の両方が合う必要がある、という意味のラベルです。
件数だけを突き合わせてはいけません。1件が抜けて、別の1件が二重に入っても、件数は変わりません。そのため、合計や集合の比較も、一緒に行います。
現場での姿
1つ目、応答のresultsの順序が、リクエストの順序と同じだと仮定します。多くのAPIは、実際に同じ順序で返しますが、ドキュメントにそう書かれていなければ、信じてはいけません。item_idで合わせて読みます。
2つ目、部分的な失敗を、例外として投げます。「3件失敗」を例外として上げて、バッチ全体をロールバックすると、成功した27件まで再送することになり、それが重複を生みます。
3つ目、再試行の回数に上限がありません。一時的な失敗に分類された件が、実は恒久的な失敗なら、その件は、キューを永遠に回ります。試行回数を記録して、何回かあとには、人に渡します。
4つ目、突合を、事故のあとにだけ行います。突合は、事故の調査ツールではなく、毎日回るものでなければなりません。昨日のものと今日のものを比較できてこそ、いつからずれたのかがわかります。
5つ目、残った件に、持ち主がいません。「直す必要がある8件」を作っておいて、誰が直すかを書かなければ、その8件は永遠に残ります。突合表の最後の欄は、人の名前です。
次のラボですること
バッチ送信を受け取るパートナーサーバーを起動して、送るもの120件を作ります。まず、ステータスコードだけを見る方式で送って、こちらの記録と相手の元帳の差を、数字で確認します。そのあと、件ごとの結果を読んでこちら側に記録し、失敗の理由を、再送するものと直すものに分け、再送するものだけを選んで、再送します。最後に、送った側と受け取った側の件数と合計を突き合わせる突合表を作り、残った件の処理計画を書きます。