信頼できる暗号資産の注文執行を届ける
暗号資産の注文執行の信頼性は、ステートマシン、冪等キー、そして徹底した突合から生まれる。MadaiOps を構築して得た教訓を紹介する。
文 Vitor Lima
ほとんどの暗号資産トレーディング製品は、速さとチャートを売りにしている。難しいのはローソク足を描くことではない。どんなミリ秒の瞬間においても、一見単純に見えるこの問いに答えられることだ。いま、私の注文の本当の状態は何なのか? 私たちが社内向けの注文・トレーディングオペレーションアプリである MadaiOps を作り始めたとき、この問いがすべてを組み立てる軸になった。速さは機能だ。真実こそが製品だ。暗号資産の注文執行の信頼性とは、取引所が劣化していてもアプリがこの問いに正直に答えられる状態のことであり、起きていない約定を表示したり、起きた約定を隠したりするアプリは、どんなレイテンシの勝利でも取り戻せない形で信頼を破壊する。
この記事は、注文アプリを信頼に足るものにするエンジニアリングについてだ。ステートマシン、冪等性、WebSocket 上での突合、そして負荷がかかったときにだけ現れる障害モード。私たちはそのすべてについて確固たる意見を持っている。なぜなら、地味な選択こそが、あなたを破産させずに済ませてくれるものだからだ。
注文を明示的なステートマシンとしてモデル化する
私たちが下した最良の決断は、注文を UPDATE する行として扱うのをやめ、名前付きで列挙された遷移を持つ有限ステートマシンとして扱い始めたことだ。注文は PENDING_NEW、WORKING、PARTIALLY_FILLED、FILLED、CANCEL_REQUESTED、CANCELED、REJECTED、EXPIRED といった状態に存在する。すべての遷移は、現在の状態に加えて到着したイベントの関数であり、不正な遷移は静かに吸収されるのではなく、大きな声で拒否される。
その見返りはこうだ。曖昧さが、午前2時のインシデントではなく、検証時の関心事になる。FILLED の注文が WORKING に戻ることは決してない。CANCELED の注文に約定イベントが到着することは「あり得ない」ことではなく、現実の競合であり、ステートマシンはそれが何を意味するのかを決めさせる。たいていの場合、それはキャンセルが競合に負け、注文が実際に約定し、ローカルの CANCELED が誤っていたことを意味する。注文を可変な行としてモデル化すれば、そのイベントは単に何かを上書きするだけだ。突合が、あるいはユーザーが、それを捕らえるまで、あなたは決して気づかない。
要点
APIコールを一行でも書く前に、状態を列挙せよ。ホワイトボードに図を描けないなら、あなたはまだ自分のドメインを理解していない。そして取引所が手痛い形でそれを教えてくれる。現在の状態だけでなく、遷移 を保存せよ。何が起きたかの追記専用イベントログは、単一の可変ステータス列に勝る。ログは再生可能だが、列は推測にすぎないからだ。
冪等性は任意ではない
ネットワークはリトライする。あなた自身のリトライロジックもリトライする。ユーザーは「買い」をダブルタップする。これらのいずれもが同じ注文を二度送りうるし、取引所において重複注文は本物のお金だ。防御策は、クライアント生成の冪等キーだ。ほとんどの取引所はこれを clientOrderId や newClientOrderId と呼ぶ。ユーザーが確定した瞬間、リクエストがデバイスやサーバーを離れる前に、すべての注文に付与される。
私たちが徹底するルールはこうだ。キーは一度だけ生成され、ネットワークコールの 前 にローカルへ永続化され、すべてのリトライで一字一句そのまま再利用される。最初の試行が応答なくタイムアウトしたなら、リトライは同じキーを運び、取引所は注文を一度だけ作成するか、既存のものを返す。いずれにせよ、あなたは単一の注文に収束する。失敗の 後 にキーを生成すれば、あなたは重複注文製造機を作り上げたことになる。
これはキャンセルと変更にも及ぶ。キャンセルリクエストもまた冪等であるべきだ。すでにキャンセルされた注文をキャンセルすることは、別のリトライループを引き起こすエラーではなく、成功を返す何もしない操作だ。私たちは「望ましい最終状態がすでに真である」ことを、あらゆる場所で成功として扱う。些細に聞こえる。だがこれが、リトライの嵐というカテゴリ全体を根絶する。
要点
成功した後ではなく、行動する前に冪等キーを永続化せよ。「私は送った」と「うまくいったと私は知っている」の間の隙間こそ、お金が消える場所であり、キーだけがそれを閉じられる唯一のものだ。
徹底して突合せよ:WebSocket は真実ではなくヒントだ
取引所は WebSocket 経由で注文の更新をプッシュしてくる。それが機能しているときは素晴らしい。サブ秒の約定、リアルタイムの部分約定数量、キャンセル確認。しかし WebSocket は取りこぼしうるストリームだ。あなたは必ず切断される。メッセージは順不同で届き、再接続中に取りこぼされ、あるいは取引所が劣化しているために単に来ないこともある。もし注文の状態が ソケットが最後に伝えたものだけ なら、ソケットがしゃっくりを起こした瞬間、あなたのアプリは自信満々に間違える。
そこで私たちは二つのループを走らせる。速いループは WebSocket を消費し、イベントを楽観的に適用する。遅いループは REST API を、一定間隔で、そしてすべての再接続の後にポーリングし(GET /order とオープン注文エンドポイント)、そのスナップショットをローカル状態と突合する。REST が真実の源であり、ソケットは低レイテンシのヒントだ。両者が食い違うとき、REST が勝ち、私たちは乖離をログに記録する。なぜなら乖離とは、システムがあなたのために書いたバグレポートだからだ。
順序付けは重要だ。ほとんどの取引所は更新に単調増加のシーケンス番号か updateTime を刻む。私たちはその注文について最後に適用したものより古いイベントをすべて破棄し、これによって順不同の配信は無害になる。再接続時の正しい手順はこうだ。再購読し、ただちに完全な REST スナップショットを取得して隙間を埋め、それから ライブストリームを再び信頼する。スナップショットを飛ばせば、オフラインだった二秒間に起きた約定を取りこぼす。
要点
リアルタイムのフィードを、状態を書き込む唯一の存在にしてはならない。ソケットはあなたを速くし、突合ループはあなたを正しくする。両方が必要であり、両者が衝突するとき、正しさは毎回速さに勝る。
部分約定と切断:あなたをさらけ出す二つ
部分約定は、素朴なアプリが静かに嘘をつく場所だ。1.0 BTC の注文が 0.3、次に 0.4 だけ約定し、そこで止まったものは「オープン」でもなければ「完了」でもない。それは 0.7 が執行済み、0.3 が残っている PARTIALLY_FILLED であり、これらの数字のすべては、単一のステータスフィールドから推測されるのではなく、個々の約定イベントから追跡されなければならない。私たちは離散的な約定から執行済み数量と出来高加重平均価格を積み上げるので、ポジションは常にプリミティブから再構築できる。「約定済み」を見るユーザーは、いくら、どの平均価格で 約定したのかを知る必要がある。それに満たないものは、半分の真実にすぎない。
切断はこれを複合させる。約定の途中でオフラインに落ちると、WebSocket はその 0.4 について決して伝えてこなかったので、あなたのローカル状態は古くなる。まさにこのために「再接続してからスナップショット」という規律が存在する。REST スナップショットは累積執行済み数量を返し、突合ループはユーザーが誤った数字を目にすることなく隙間を埋める。私たちが対策する障害モードは「アプリがクラッシュした」ことではない。クラッシュは正直だ。それは「アプリが動き続け、偽りのものを表示した」ことだ。
要点
累積執行済み数量を第一の事実として追跡し、それ以外のすべてをそこから導出せよ。「残量」を、あなたが書き換える独立したフィールドとして保存してはならない。それを(注文済み − 執行済み)導出せよ。そうすれば現実とずれることがない。
圧力下で真実を語ることは、賢さに勝る
賢くありたいという執拗な誘惑がある。確認前に約定を予測する、「一時的な」エラーを隠す、UIが決してちらつかないよう再接続を滑らかにする。私たちはそのほとんどに抗うことを学んだ。トレーディングオペレーターは滑らかな嘘を望まない。彼らが望むのは、接続がたった今切れて状態が突合中であることを、正直に知ることだ。「再接続中、注文を確認中」という目に見える状態は、蓋を開けてみれば昨日の真実を表示していたシームレスなUIより価値がある。
具体的には、私たちは不確実性を塗り隠すのではなく、それを表面に出す。ローカル状態と取引所が最近突合されていないとき、アプリはそう言う。注文が CANCEL_REQUESTED であってもまだキャンセル確認が取れていないとき、それは早すぎる CANCELED ではなく、まさにその状態を表示する。楽観的UIは To-Do アプリには結構だ。お金においては、楽観主義はサポートチケットと失われた信頼という形で支払う負債だ。
要点
疑いがあるなら、疑いを見せよ。金融アプリがストレス下でできる最も安心させる行いは、推測を拒むことだ。
締めくくりの一言
このどれもが華やかではない。「私たちの冪等キーはネットワークコールの前に永続化されている」ことで拍手が起きるデモなど存在しない。しかし信頼性は、後から付け足す機能ではない。それは早い段階で下し、妥協を拒み、近道が魅力的に見えるたびに守り抜く一連の決断だ。すべてが炎上しているときに真実を語るシステムを構築せよ。そうすれば、速く美しい部分は自ずと片づく。