エンジニアリング

LLMキューの大量滞留から見直した、処理基盤のスケーリング設計

LLMキューの大量滞留から見直した、処理基盤のスケーリング設計
#LLM#Amazon Bedrock#SQS#ECS#オートスケーリング

はじめに

私たちが開発している Acsim は、要件定義を支援するプラットフォームです。業務フローの生成・分析や変更計画の策定など、複数の機能で LLM(大規模言語モデル)を活用しています。

LLM の呼び出しは Amazon Bedrock 経由で行い、SQS キューと ECS 上のワーカーで非同期に処理しています。普段は Bedrock の TPM を前提にした並行処理の設計 で安定稼働していましたが、ある日、需要の集中によりキュー滞留が膨らみ、ユーザーの待機時間が最大80分を超えるインシデントが発生しました。

システムは止まっておらず、見かけ上は正常に動いている。しかしユーザーから見れば「処理を依頼しても結果が返ってくるまで普段より長く待たされる」状態です。この記事では、インシデントの検知から原因分析、設計変更までの流れを振り返ります。

何が起きたか

ある日の午後、多数のユーザーが同時に複数の AI 機能を利用しました。業務フローの生成、画像からの業務フロー取り込み、課題分析、変更計画の策定など、8種類の LLM 処理が短時間に集中しました。

当時の処理基盤は、単一の実行タスク内で複数の Consumer(SQS からメッセージを受信するワーカー)がメッセージを取り出す構成でした。 Consumer の数は通常運用では十分な並列度でしたが、約150分間に399件のジョブが投入されると、毎分数件の処理速度では追いつきません。

指標
投入ジョブ数399件
処理成功350件(87.7%)
キュー滞留ピーク173件
最長所要時間(待機+処理)80分超
平均待機時間(処理種別による)16〜40分

成功率87.7%だけ見れば通常時とおおむね同水準で、大きな障害には見えません。しかし、平均40分待たされる「変更計画生成」や、95パーセンタイルで67分を超えた「画面・機能の一括作成」など、体験としては深刻でした。

どう気づいたか

早期に検知できたのは、SQS キューの最古メッセージ待機時間(ApproximateAgeOfOldestMessage)を監視し、しきい値超過で即時アラートを出す運用を整備していたためです。

当日はアラート発火の時点で調査を開始しました。キューの滞留数やワーカーの処理速度をメンバーが分担して確認し、「処理は止まっていないが、投入ペースに追いついていない」状態だと早い段階で把握できました。問い合わせベースの後追いではなく、メトリクス起点で検知から調査へつなげられた点は、運用設計が機能した場面です。

インシデント中の意思決定

状況を把握した時点で、チームは3つの選択肢を検討しました。

  1. 静観する - システムは設計通り動いている。時間はかかるがキューは捌ける
  2. 並列数を上げる - ワーカーを増やせば処理速度は上がるが、Bedrock の TPM(Tokens Per Minute)制限に当たるリスクがある。本番で未検証の変更を即座に入れることへの懸念もある
  3. キューをクリアする - 滞留は解消するが、ユーザーが投入したジョブが失われる

最終的に 静観 を選びました。システムは正常に動いており、構成変更による二次障害のリスクの方が高いと判断したためです。人的監視を続けつつ、お客様には状況を連絡し処理が順次完了する見通しを共有しました。

結果として約2.5時間で自然復旧し、キューが0件になりました。

ボトルネックはどこにあったか

最初の仮説:TPM 制限

初動では、Amazon Bedrock の TPM 制限(当時1,500,000トークン/分)を第一候補として疑いました。しかし CloudWatch の実測値を確認すると、ピーク時でも上限には達していませんでした。

ピーク時 1分間合計:
  InputTokenCount:  895,113
  OutputTokenCount:  72,652
  推定TPM消費: 895,113 + 72,652 × 5(burndown rate)= 1,258,373

※ Claude 系モデル(Opus 4、Sonnet 4 など)では、出力トークンの burndown rate が 5 倍に設定されています。1 つの出力トークンが TPM クォータを 5 トークン分消費する計算です。

TPM 上限1,500,000に対して約84%の消費率で、上限には達していませんでした。ただし、5分間の平滑化で見ると、仮に並列度を2倍にしていたら上限に達していた計算になります。並列度を引き上げる際にすぐ当たる天井であることが分かりました。

なお、その後の調査で Bedrock 側の TPM クォータがすでに引き上げられていたことも判明しました。引き上げを申請しようとした時点で、すでに適用済みでした。ただし、クォータが上がっても処理能力は自動的には伸びません。実行基盤側の設計を追随させて初めて、その余裕を活かせます。

真のボトルネック:実行環境のメモリ制約

調査を進めると、実際の制約は「理論上のトークン枠」ではなく「ECS タスクのメモリ」にあることが分かりました。

前述のとおり、1つの実行タスク内で複数の Consumer を同時起動する構成でしたが、Consumer の最大数は TPM 制限から逆算して動的に決まる設計でした。メモリの物理的な上限を考慮すると、安全に動作できる並列度には限りがあります。

つまり、Bedrock 側に TPM の余裕ができても、実行環境のメモリが先に制約になって並列度を上げられない状態でした。当時の設計では、 「トークン上限に余裕があること」と「実際に処理能力を伸ばせること」は同じではなかった のです。

どう設計を変えたか

対応は2つの段階で進めました。

1. タスク内の並列度を安定させる

1つの実行タスク内の Consumer 数を、メモリ制約を前提にした固定値に変更し、従来の約2倍の並列度を確保しました。

前節で述べたとおり、従来は TPM 消費量から Consumer 数を動的に算出しており、メモリの物理的な上限を超える並列度が設定されうるリスクがありました。固定値にすることで、タスク1つあたりの挙動が予測可能になり、次のスケール設計を入れやすくなります。

並列度を上げれば当然 TPM クォータに近づきますが、Bedrock API の呼び出しには exponential backoff によるリトライを組み込んでいるため、仮にクォータに到達しても自動的にペースを落としながら処理を継続できます。クォータ到達時のリトライによるレイテンシ増加と、並列度向上による全体スループットの改善を天秤にかけた結果、後者を優先しました。なお、TPM クォータ上限が当初の150万から大幅に引き上がっていたことも、この意思決定の背景にあります。

2. キュー滞留量に応じた ECS タスクのオートスケーリング

次に、SQS のメッセージ滞留数(ApproximateNumberOfMessagesVisible)に基づいて ECS タスク数を自動で増減する仕組みを導入しました。

従来は実行タスク数が固定で、オートスケーリングは実質無効でした。CPU やメモリベースのスケーリングポリシーは存在していたものの、キューの滞留には直接反応できません。

新しい設計では、backlog per instance(メッセージ滞留数 ÷ 実行中タスク数)をカスタムメトリクスとして、ターゲット追跡スケーリングポリシーに組み込んでいます。キューが溜まればタスクを増やし、落ち着けば戻す構成です。

これにより、平常時はコストを抑えつつ、今回のようなスパイクが発生しても人手を介さずに処理能力を引き上げられるようになりました。

「遅延したが捌けた」をどう評価するか

今回のインシデントでは、399件中350件が正常に完了しました。成功率87.7%という数字だけを見れば大きな障害には見えません。しかし、平均40分の待ち時間が発生した時点で体験品質は大きく下がります。とくに複数の AI 機能を連続で使う場面では、1回の遅延が後続の操作全体に波及します。

一方で、このインシデントをきっかけに処理基盤のスケーリング設計を根本から見直す機会を得ました。単一タスクに閉じた固定構成から、キュー滞留に応じて処理能力が伸縮するアーキテクチャへ進化させることができたのは、結果として大きな前進です。

もともと待機時間の監視は行っていましたが、それを検知だけでなくスケーリングのトリガーとして組み込んだことで、同種のスパイクにシステムが自律的に対応できる基盤になっています。

学び

今回の対応を通じて、LLM 基盤の運用で意識しておきたい観点が3つ見えてきました。

1. LLM 基盤のボトルネックはトークン制限だけではない

TPM に余裕があっても、実行環境のメモリが先に天井になることがあります。LLM 固有の指標(TPM、レイテンシ)と汎用的なインフラ指標(メモリ、CPU)の両方を見て、どちらが先に制約になるかを把握しておく必要があります。

2. 「壊れていないが遅い」は見過ごされやすい

成功率が高くても、待機時間が長ければユーザー体験は悪化します。キューベースの非同期処理では、エラー率だけでなく滞留量と待機時間を監視し、「壊れないこと」と「待たせないこと」を分けて評価することが重要です。

3. スケールの余地を設計段階で確保しておく

需要スパイクがいつ起きるかは予測できなくても、起きたときに伸ばせる構造は事前に作れます。今回「静観」しか選べなかったのは、スケールアウトの仕組みがなかったからです。オートスケーリングの導入により、次に同じ負荷が来ても人手を介さず対応できる基盤になりました。

まとめ

今回のインシデントは、システム停止ではなく、急激な需要増に対してスケール設計が追いつかなかった事例でした。

対応のポイントは以下の2点です。

  • タスク内の並列度: メモリ制約を前提に安定動作する固定値へ調整し、約2倍に引き上げ
  • タスク数のスケーリング: SQS 滞留数に基づくオートスケーリングの導入

この変更により、「固定の処理能力で耐える」設計から「需要に応じて処理能力が伸びる」設計へ転換できました。LLM 基盤の運用では、トークン制限への対処だけでなく、実行環境のリソース制約とキュー設計を含めた全体最適が求められます。

今後の改善としては、処理内容に応じた LLM モデルの自動切り替えも検討しています。Amazon Bedrock ではモデルごとに TPM クォータが別枠で設定されているため、簡単な処理に軽量モデルを割り当てることで、利用可能な TPM の総量を増やしスループットをさらに向上できる見込みです。

関連記事

この記事をシェア: