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

キューと非同期API

キューは時間を買う道具だ

TT Labで続きを見る

一言でいうと

キューは処理速度を上げません。リクエストを受け取る速度と処理する速度を切り離して、 瞬間的な急増を時間で吸収します。

なぜ必要なのか

画像アップロードAPIがあるとしましょう。原本を保存し、サムネイルを3つ作り、 メタデータを抽出し、検索インデックスに入れます。すべてを同期で行うと、応答に4秒かかります。 普段は耐えられます。ところがプロモーションの日にアップロードが10倍に増えると、4秒かかるリクエストが 10倍積み上がってワーカースレッドがすべて塞がり、ヘルスチェックまで失敗します。

キューを入れると、APIは原本だけを保存して「受け付けました」と答えます。200msです。残りは ワーカーが自分の速度で処理します。急増が来てもAPIは変わらず200msで答え、キューの深さだけが 増えます。増えた深さは処理の遅延になります。4秒かかっていたものが3分かかることも あります。その代わり、システムは死にません。

この交換がキューのすべてです。遅延を受け入れて、可用性を買います。

安定条件は不等式1つ

キューを使うのに向いた条件は3つです。1つ目、作業がすぐに終わらなくてもよいか。2つ目、リクエストの 速度が処理の速度より瞬間的に速くなりうるか。3つ目、失敗したとき、あとでやり直して もよいか。3つとも「はい」なら、キューが合っています。

逆に、キューを入れてはいけない場合もはっきりしています。ユーザーが結果をすぐ見なければならない参照、 そして平均の流入が平均の処理能力より大きい場合です。後者を誤解している人が 多いです。キューはバーストを吸収しますが、慢性的な過負荷は解決できません。

λ = 초당 유입 건수      μ = 워커 하나의 초당 처리 건수 × 워커 수

λ < μ  →  큐 깊이가 0 근처로 돌아온다 (안정)
λ = μ  →  깊이가 무작위로 떠돈다 (경계 — 운영하면 안 되는 지점)
λ > μ  →  깊이가 선형으로 자란다 (터진다)

数値で見ると明確です。μ = 100/sで、λが普段は60/sなのに、30分間90/sに 上がったとしましょう。この区間で毎秒なくなる余裕は10件です。30分でキューに残るものは なく、うまく吸収されます。ところがλが110/sに上がると、毎秒10件ずつ溜まって30分で18,000件に なります。この状態が終わらなければ、メモリやディスクを食い尽くして壊れます。

λ > μが続くなら、必要なのはキューではなくワーカーの増設か流入制限です。

遅延は利用率に比例しない

ここで、人々がよく見落とすことがあります。待ち時間は利用率(ρ = λ/μ)に比例して 増えるのではなく、非線形に爆発します。 おおまかな感覚は、こうです。

利用率 ρ 待ち時間の倍率(1/(1−ρ)) 意味
0.5 2倍 余裕がある
0.8 5倍 そろそろ目立ってくる
0.9 10倍 もう少し上がると急激に悪化する
0.95 20倍 運用が難しい
0.99 100倍 事実上の障害

そのため、ワーカーの容量を「平均の流入にぴったり合わせて」設定してはいけません。利用率70–80%を目標に 設定し、それを超える分は自動スケーリングや流入制限が受け止めます。

現場での姿

キューを入れると、新しい運用メトリクスが生まれます。キューの深さとコンシューマーラグ(consumer lag) です。この2つをダッシュボードに載せなければ、キューは静かなブラックホールになります。 ユーザーは 「アップロードしたのに出てきません」と報告し、APIのダッシュボードはすべて緑です。問題は APIの後ろに溜まっています。

深さだけを見るのでは不十分です。深さ1,000が深刻かどうかは、処理レートによって 違います。深さ÷処理レート=消化時間も一緒に見ないと判断できません。毎秒100件を 処理するなら10秒分で、毎秒1件なら17分分です。アラートもこの値で設定します。

アラートをもう1つ。深さが0から動かないのも異常のサインです。 コンシューマーがすべて 死んで誰も取り出さなければ、流入だけが溜まるはずなのに、プロデューサーまで死んでいると、深さが0で 平らになります。処理完了件数が0のまま深さが0なら、パイプライン全体が止まっているのです。

そして、キューを入れると、ユーザー体験の設計も一緒に必要になります。「受付済み」の状態をどう 見せるか、どれだけ待てばよいか、失敗したらどう知らせるか。これを決めずにキューだけを 入れると、ユーザーは、ただ消えたリクエストを見ることになります。最低でも3つは決める必要があります。

キューを選ぶ前に答えるべき4つのこと

道具を選ぶのは最後です。その前に4つの性質を先に決める必要があり、これを 決めずにRedisやKafkaを選ぶと、あとですべて作り直すことになります。

配信保証。 少なくとも1回(at-least-once)が基本です。ちょうど1回は、キューが与えてくれる ものではなく、コンシューマーを冪等にして得るものです。処理したジョブIDを記録して おき、同じものがまた来たら飛ばします。

順序。 グローバルな順序を守るキューは、並列処理ができません。実務では、たいていキー単位の 順序で十分です。同じユーザーのジョブだけが順番どおりであればよく、ほかのユーザーとの順序は 関係ありません。パーティションキーをユーザーIDにすると、これが得られます。

リトライと諦め。 何回リトライし、間隔をどう延ばし、いつ諦めるかを決めます。 指数バックオフにジッターを混ぜないと、失敗したジョブが同じ時刻に集中してリトライします。

デッドレターキュー(DLQ)。 リトライを使い切ったジョブの行き先です。DLQがなければ、そのジョブは 永遠に循環するか、静かに消えます。DLQに溜まった件数には、必ずアラートをかけます。 ここに溜まるのは、人が見なければならないものだからです。

次のクイズで確認すること

このモジュールは概念だけを扱います。次のモジュールから、Redisのリストとストリームでキューを 実際に作り、順序・重複・消失がどこで生じるかを1つずつ再現します。