何を捨て、誰を待つのか
一言でいうと
最新の位置と決済の履歴は、同じイベントではありません。何を失ってよいかによって、遅い購読者のポリシーが変わります。
なぜ必要なのか
救助隊がすでに次の路地に移動したのに、地図に5分前の位置が順番に再生されます。サーバーは1件も失っていないと報告しますが、ユーザーにとっては役に立たないライブ配信です。逆に、決済の履歴から古い項目を消して最新の項目だけを見せると、お金の移動そのものを漏らしてしまうことがあります。損失をなくすことと、サービスの目的を守ることは、いつも同じ選択とは限りません。
このモジュールは、3つのポリシーを比べます。空きができるまで待つ、最も長く待った状態を捨てる、遅い購読者を発行の対象から切り離す、です。どれも長所と短所があります。キューのサイズを1つ設定すれば、ポリシーも自動で決まると考えないでください。QueueFullの後に何をするかが、製品の動作を決めます。
どう動くのか
待機ポリシーは、await queue.putで実装できます。データをこっそり捨てはしませんが、パブリッシャーが購読者の一覧を巡回して、それぞれのputを待つと、遅い購読者1人のところで、巡回全体が止まります。購読者ごとにキューを置いたからといって、自動で分離されるわけではありません。プロデューサーがそれらのキューをどんな順序と方式で待つかも、見る必要があります。
最新状態ポリシーは、キューがいっぱいのとき、最も長く待った項目を1つ取り出して、新しい項目を入れます。容量2に10、11が待っていて、12が来たら、11、12が残ります。たった今着いた12を捨てると、名前はlatestでも、実際には古い画面を保存するポリシーです。すでにワーカーが取って処理中の項目は、キューから抜けないので、このポリシーは進行中の作業を巻き戻しません。
捨てた項目もgetでキューから取ったので、task_doneを1回対応させます。そうしないと、queue.joinが永遠に待つことがあります。このとき、joinが終わったからといって、すべてが成功したわけではありません。一部は意図的に破棄し、一部は処理に失敗したかもしれません。成功・破棄・失敗の回数は、業務の指標として別に記録する必要があります。授業では、捨てた項目を返して、ポリシーを目で比べます。
切り離しポリシーは、いっぱいのキューをこっそり変更せずに、SlowConsumer例外を出します。発行関数は、その購読者をディクショナリから外し、所有者に終了を依頼します。残っている購読者には、引き続き配信します。最初の失敗でreturnしてしまうと、一覧でその後ろにいる正常な購読者がイベントを逃します。変更中のディクショナリをそのまま巡回すると、巡回エラーが生じることがあるので、名前・キューのペアのスナップショットを使います。
| データ | 検討するポリシー | 追加で必要なもの |
|---|---|---|
| 現在の位置・進捗率 | 古い待機状態の省略 | 最新のスナップショット、欠落の表示 |
| チャット・通知の履歴 | 遅い接続の切り離しと再接続 | 保管されたログ、最後に確認した位置 |
| 決済・在庫の変更 | 耐久性のある保存とリトライ | 冪等な処理、アトミックな状態変更 |
接続を切ると、サーバーのリソースは回収できますが、配信の問題まで解決するわけではありません。購読者がイベントを受け取ったのに、ACKを送る前に切れたなら、サーバーは処理されたかどうかを知りません。リトライすると重複の可能性があり、リトライしないと欠落の可能性があります。切り離しは、障害の範囲を減らす措置であり、配信保証とは別の設計です。
現場での姿
ダッシュボードは、毎秒数十回変わるCPU使用率のすべての中間値を描く必要がないかもしれません。しかし、監査ログを同じ方式で圧縮すると、事故の経路を失います。チャンネルごとにポリシーを明示し、遅い接続を切り離した回数と、購読者が再同期する時間を一緒に観測する必要があります。切る回数が増えるだけでも、正常な購読者のレイテンシ指標は良く見えることがあります。
このラボの最後のステップは、接続の切り離しポリシーを、実際のTCPで検証します。fastは毎イベントをACKし、slowは最初のイベントのACKを保留します。0から8まで発行したときに、fastがすべて受け取るか、slowだけが1回切り離されるかを確認します。固定のsleepに頼らず、ACKとイベントのバリアを使って、実行の順序を確認します。これはスループットのベンチマークではなく、部分障害の機能検証です。
次の確認ですること
同じ入力で、ポリシーの違いを書いてみてください。slowが0を処理中で、容量2のキューに1と2が待っているときに、3が到着します。待機ポリシーは、パブリッシャーを止めます。最新状態ポリシーは、1を捨てて、2と3を残します。切り離しポリシーは、今回の挿入を拒否して、接続の所有者に知らせます。切り離されたキューの中の1と2を、誰が片付けるかも、必ず決める必要があります。どの場合も、すでに処理中の0の成功かどうかが、自然に確定するわけではありません。
この違いを、ログ1行の成功・失敗だけに圧縮すると、原因を見逃します。キューの飽和、業務ACKのタイムアウト、リモートのEOF、管理者の強制切り離しは、それぞれ別の出来事です。指標の名前と失敗のメッセージを区別すれば、再接続を増やすべきか、消費の速さを改善すべきか、データのポリシーを変えるべきかを、判断しやすくなります。単にエラーをすべて捕まえて送り続ける方式は、ライブ配信が生きているように見えながら、データだけが消える結果を生むことがあります。
各ポリシーが、誰を待たせ、何を失うかを、クイズで確認します。後のラボで、offer_latest、offer_disconnect、broadcastを実装しながら、同じ入力を異なる形で処理する理由を、説明してみてください。