ゲームラグ白書 › L9 サーバーのゲームプロセス
スレッドプールの枯渇 Thread pool starvation
原因ID sp-threadpool · 主担当 ゲーム開発チーム・サーバー開発
図と実験のあるメインページでこのカードを開く →
処理を担うワーカースレッドがすべて遅い処理に塞がれると、新しいリクエストはいつまでも待たされます。
なぜ ワーカースレッドが外部API・DBの応答待ちで塞がる → すると 新しいリクエストに割り当てるスレッドがない → 画面では ログイン・ショップなど特定の機能が無限ロード
- 症状
- 接続不可・無限ロード, 入力遅延, フリーズ
- 要因
- ストール
- 誰に起きるか
- 特定の機能だけ, サーバー全体
- いつ
- 人が集中したとき, 接続直後・メンテ明け
- 担当
- 主担当 ゲーム開発チーム・サーバー開発
- ゲーム開発チームの対応
- 遅い呼び出しにタイムアウト、機能ごとのスレッドプール分離、非同期化。
- グラフでは
- 上限で頭打ち · スレッドプールのスレッド数・キュー長、リクエストの処理時間
- 確認箇所
- .NETはdotnet-counters monitorでスレッドプールのスレッド数とキュー長(.NET 9以降はdotnet.thread_pool.thread.count・dotnet.thread_pool.queue.length、8以前はThreadPool Thread Count・ThreadPool Queue Length)を確認し、dotnet-stackでワーカースレッドがどこで待っているかを確認。JVM・ネイティブのサーバーはスレッドダンプで同じことを確認
- 該当する場合
- CPU使用率は100%よりかなり低いのに、スレッド数がゆっくり増え続けるか上限に張り付いており、キューがたまり、ワーカーの大半が同じ外部呼び出し(DB・HTTP)の応答を待っている
- 該当しない場合
- キューが空なのに遅ければ呼び出し先そのものが遅いので、カスケード障害・外部サービスへの依存の側
- 確認手段
- インフラのツールで確認(ゲームコード不要)
- もっと詳しく
- パケットの受信とゲームロジックが同じワーカースレッドプールを共有する構造では、数個の遅い処理がワーカーをすべて占有した瞬間に、サーバー全体のパケット処理が止まります。
- 実際の事例
- Riot Games 2021: League of Legends EUWの5時間障害:補助的なDB一つでサーバー全体が停止
出典
- Debug ThreadPool Starvation Microsoft
プールに空きスレッドがなく新しい処理が待たされると応答が遅くなる、スレッドを占有するブロッキングコードが原因。dotnet-countersでCPUは100%よりかなり低いのにdotnet.thread_pool.thread.countがゆっくり増え続けるなら枯渇のサイン(dotnet.thread_pool.queue.lengthも大きいことが多い)、dotnet-stackでスレッドが待っている箇所を確認 - Avoiding insurmountable queue backlogs AWS
同時処理数 = 到着率 × 遅延(リトルの法則)。毎秒100件で遅延が100msから10秒に延びると、スレッドが10本から1,000本に増えてプールが枯渇 - Bulkhead Pattern Microsoft Azure
呼び出し先ごとにコネクションプール・スレッドプールを分けておけば、1つの呼び出し先の障害はそのプールだけを塞ぐ - .NET runtime metrics .NET
dotnet.thread_pool.thread.count(スレッドプールのスレッド数)・dotnet.thread_pool.queue.length(待機中の処理数)は.NET 9から - Well-known EventCounters in .NET Microsoft
.NET 8以前のThreadPool Thread Count(threadpool-thread-count)・ThreadPool Queue Length(threadpool-queue-length)
あわせて読みたい原因
同じ層:L9 サーバーのゲームプロセス
同じ症状(接続不可・無限ロード)を起こすほかの層の原因
図と実験のあるメインページでこのカードを見る