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

資本市場と決済

再処理は避けられない — 防ぐのは二度目の発注だ

TT Labで続きを見る

一言でいうと

プロセスが死んで蘇ると、どこから再び読むかを選ぶことになりますが、前すぎるところから読めば注文が二重に出て、後ろすぎるところから読めば注文が抜けます。この2つの間に、安全な地点はありません。そのため、答えは「ちょうど1回読むこと」ではなく、再び読んでも同じ注文IDが出るようにして、2回目を受け取る側が除外するようにすることです。

なぜ必要なのか

注文を送り出すプログラムは、たいてい次のような形をしています。シグナルのストリームから1件を読み、注文メッセージを作って送り、どこまで処理したかをチェックポイントに書きます。この3つの動作の間にプロセスが死ぬことがある、というのが問題のすべてです。

チェックポイントを送ったあとで書くと、送ってから書く前に死んだとき、その区間を再び読みます。注文が再び出ます。at-least-onceです。チェックポイントを送る前に書くと、書いてから送る前に死んだとき、その区間は二度と読まれません。注文が抜けます。at-most-onceです。しかも、チェックポイントは、通常1件ごとには保存せず、数件ずつまとめて保存します。そのため、重なる区間も抜ける区間も、1件ではなくまとまりの大きさの分だけ開きます。

注文では、この2つの重みは同じではありません。抜けた注文は、出し直せばよいのですが、二重に出た注文は、すでに約定しているかもしれず、そうすると、望まないポジションが生じます。そのため、送信経路は、通常at-least-onceを選び、重複を受け取る側で除外します。重複除外をどこに置くかが、この問題の実際の設計上の決定です。

どう動くのか

除外するには、「同じ注文」だとわかる目印が必要です。注文メッセージには、送る側が付ける識別子があり、FIX系の規格では、これをClOrdIDと呼びます。訂正と取消は、自分のIDを新しく付けながら、OrigClOrdIDで前のIDを指します。規格の意味はFIXimateで、規格自体はFIX Trading Communityの標準ページで確認できます。

ここで、人が最もよくつまずく箇所があります。IDをどう作るか。実行ごとに1番から振る連番、プロセスIDや開始時刻を混ぜたプレフィックス、そして乱数のUUID。3つとも一般的で、3つとも再処理に無力です。同じシグナルを再び読んでも、別のIDが出るからです。受け取る側は、それが新しい注文なのか、さっき受け取った注文なのか、知る方法がありません。

直す方法は単純です。IDを入力の安定したフィールドだけで作ります。ストリーム名、オフセット、銘柄、売買方向、数量、価格のように、再び読んでも同じ値をつなげてハッシュすれば、同じシグナルは、何回読み直しても同じIDを出します。時刻も乱数もプロセスIDも入れません。

そして、もう1つ付いてくることがあります。訂正と取消が指すOrigClOrdIDも、同じ規則で再計算できる必要があります。オフセットとIDをつなぐ表をメモリにだけ置くと、再起動のときに消え、そうすると、再処理された取消メッセージは、存在しない注文を指します。取引所はそれを拒否しますが、その間、元の注文は生きています。取り消そうとした注文が、取り消されないまま残ることが、このチェーンが切れたときの実際の結果です。

最後に、重複除外ウィンドウをどれだけ長く取るかが残ります。これは好みではなく、観測値です。同じシグナルが最初に出た時刻と、再処理で再び出た時刻の差を、データから測り、それに余裕を掛けます。ウィンドウが、観測された最大の再処理の遅延より短ければ、重複除外はあってもなくても同じです。

現場での姿

ある送信経路で、障害のあとの二重発注を調査したことがあります。送信ログだけを見ると、同じオフセットが2回出ているので、すべてが重複に見えましたが、取引所の受付確認と突き合わせると、半分ではありませんでした。一方は数量の上限に引っかかって拒否されており、もう一方は、取消メッセージが知らない注文を指して拒否されていました。実際に危険なのは、両方とも受け付けられた件だけで、その数は、最初に数えたものの半分もありませんでした。送った記録ではなく、受け入れられた記録で数えることが、調査の出発点です。

もう1つ。事故レポートに「重複除外を入れる」とだけ書かれている場合を、よく見ます。ウィンドウの長さがなければ、その文は仕事ではなく意見です。データで測った遅延があれば数字が付き、数字が付けば、あとでその値が合っていたかを問い直せます。

3つ目によく出会うのは、復旧を人が手で行う場合です。障害が起きると、担当者がオフセットを目で見て値を書き込みますが、その値が1桁ずれれば、そのまま二重発注になります。手で選ぶ手順をなくせないなら、せめてその値が何を意味するかを、画面に一緒に表示する必要があります。このオフセットから読めば、何件が再び出て、そのうち何件がすでに受け付けられた注文か。その2つの数字が見えれば、人はたいてい、正しい値を選びます。復旧手順書にオフセットだけを書いて、その影響を書かないことが、実際の事故の半分です。

そして、再処理は注文で終わりません。同じシグナルが2回流れると、リスク限度の計算、ポジションの集計、監査ログも、一緒に2回動きます。注文だけを重複除外して、残りをそのままにすると、注文は1回出たのに、帳簿には2回計上される、さらに悪い状態になります。重複除外の地点を1か所にまとめ、その後ろに来るすべての計算が、その地点を通るようにすることが、設計の核心です。

次のラボですること

48件のシグナルストリームと、2回の実行が残した送信ログ、取引所の受付確認、チェックポイント、障害記録から始めます。再処理の区間を探し、再び出た注文を数え、決定的な注文IDを自分で実装したあと、受付確認と突き合わせて、本当に2回受け入れられた注文だけを残します。その次に、チェックポイントの保存時点を前後に変えて、重複と漏れをそれぞれ数えて比べ、切れた取消と訂正のチェーンを探し、最後に、重複除外ウィンドウの長さをデータから求めて、復旧手順書を書きます。