青い照明のサーバールームに並ぶネットワーク機器のラック
設計と運用

AWS ALBのアイドルタイムアウト60秒がWebSocket切断を招く理由

目次を見る

音声系やリアルタイム通信のアプリをAWS上でWebSocket(常時接続を保てる双方向通信の仕組み)で構築しているエンジニアに向けた内容です。AWSのALB(Application Load Balancer、HTTP/HTTPSやWebSocket通信を振り分けるロードバランサー)には、デフォルトで60秒のアイドルタイムアウトが設定されています。この設定が原因で、通信が一定時間静かになるタイプのアプリでは接続が断続的に切れる事象が起きます。

ある音声AIエージェントの実運用では、1週間に412セッション中58セッション、割合にして14%が意図しない形で切断されていた事例が報告されています。例外も5xxエラーもダッシュボードには出ず、ロードバランサーが設定通りに動作していただけという点が、この問題の厄介なところです。原因調査から対策までの流れを、アーキテクチャ設計の視点で整理します。

ALBのアイドルタイムアウトが何をしているか

ALBのアイドルタイムアウトは、接続上で一定時間データのやり取りがないと、その接続を強制的に切断するタイマーです。デフォルト値は60秒で、双方向のどちらかでバイトが流れるたびにリセットされます。

重要なのは、ALBがWebSocketへのアップグレード後の接続を特別扱いしない点です。HTTPのロングポーリングであろうとWebSocketであろうと、ALBから見れば「静かなソケット」は等しく切断対象になります。

チャットアプリの入力中インジケーターや、価格ティッカーのようなダッシュボードは、常に何かしらのデータが流れているため、このタイムアウトにほとんど引っかかりません。一方、音声エージェントのように「ユーザーが話していない間は何も送らない」設計のアプリは、仕様上どうしても長い無通信区間が生まれます。

なぜ一部のセッションだけ切断されたのか

報告された事例では、VAD(Voice Activity Detection、音声の有無をローカルで検知する仕組み)を使い、ユーザーが話している間だけ音声フレームをサーバーに送信する設計が採用されていました。これは帯域と文字起こしコストの節約として合理的な判断です。

しかし、ユーザーが難しい質問に対して70秒考え込むと、その間クライアントは何も送信しません。サーバー側も待機するだけで、双方向ともゼロバイトの状態が60秒を超えた時点でALBが接続を切ります。

興味深いのは、3種類あったエージェントの性格設定のうち「フレンドリー」タイプだけが、45秒の無音後に「ごゆっくりどうぞ」という音声ナッジを送る仕様になっていたことです。このTTS(Text-to-Speech、音声合成)音声がトラフィックとしてALBのタイマーをリセットし、結果的に切断を回避していました。つまり、UX上の配慮として入れた機能が、意図せずインフラの制約を回避する副作用を持っていたことになります。ログを見ると、60秒超の無音ギャップがあった71セッションのうち、フレンドリー型の13セッションは生存し、残り58セッションはすべて切断されていました。

原因特定に使われた手法と表れた症状

切断時にブラウザ側で観測されたWebSocketのクローズコードは1006でした。このコードは「クローズフレームを受け取らずに接続が切れた」ことを示します。

ALBはWebSocketの正規のクローズハンドシェイクを行わず、TCP接続そのものを切断します。そのため両端とも1006を受け取り、これはWi-Fiが切れた場合と同じコードになるため、エラートラッキング上は「不安定なネットワーク」として埋もれやすい性質があります。

原因特定には、クローズイベント発生時に「最後にフレームが流れた時刻との差分」をログに残す方法が使われました。

ws.on('close', (code) => {
  const idleMs = Date.now() - ws.lastFrameAt;
  log.info({ sessionId, code, idleMs }, 'ws closed');
});

このログを集計すると、異常終了はすべてcode: 1006で、idleMsが60,000〜61,400ミリ秒の範囲に集中していました。「だいたい1分前後」ではなく「60秒ちょうどに断崖がある」分布です。失敗の分布がキリのよい数値で急に立ち上がっている場合、まずコードのバグよりも設定のデフォルト値を疑うべきというのは、障害調査における汎用的な着眼点です。

関連技術との比較で見える設計判断

この問題は、WebSocket特有というよりロードバランサーの標準的な挙動に起因します。AWSのALBだけでなく、NLB(Network Load Balancer)やNginx、HAProxyなど、多くのL4/L7プロキシにもアイドルタイムアウトの概念があります。設定値や挙動は製品ごとに異なるため、採用しているインフラのドキュメントを個別に確認する必要があります。

日本国内のサービスでも、音声通話・ライブ配信のコメント欄・IoTデバイスとの常時接続など、「意図的に無通信区間が発生する」構成は珍しくありません。クラウドのマネージドロードバランサーを前段に置く構成を取る場合、WebSocketを使うというだけで安全とは言えず、アプリの通信パターンとタイムアウト設定の相性を個別に検証する姿勢が求められます。

この種の障害は、可用性に関わる非機能要件の見落としとして分類できます。機能要件のテストでは「接続できるか」「メッセージが届くか」は検証されますが、「60秒以上沈黙する利用シナリオ」は機能仕様としてテストケースに現れにくく、設計レビューの段階で意識的に洗い出す必要があります。

今日確認できること

自分のアプリがこの問題の影響を受けるかどうかは、次の観点で確認できます。

  • ALBのidle_timeout属性を確認する(AWSマネジメントコンソールのロードバランサー属性、またはCLIでdescribe-load-balancer-attributes)
  • アプリの通信パターンで、60秒以上双方向にデータが流れない区間が発生しうるか洗い出す
  • クライアント・サーバー双方でクローズコードと最終フレーム時刻をログに残し、1006が特定の経過時間に集中していないか調べる
  • サーバーから25秒程度の間隔でWebSocketのpingフレームを送る実装になっているか確認する
  • 再接続時にセッションを最初からやり直すのではなく、状態を復元(レジューム)できる設計になっているか確認する

報告された事例では、サーバーから25秒ごとにpingを送り、ALBのidle_timeoutを引き上げ、再接続時にセッションを再開できるようにした結果、436セッション中2件の切断まで改善しています。この2件も実際のネットワーク断による正常な切断だったとされています。

AWSのALBのidle_timeoutはデフォルト60秒で、最大4000秒まで引き上げ可能です。ただし、タイムアウト値を伸ばすだけでは根本対策になりません。クライアントやサーバー、途中の中間機器が予期せず落ちた際、沈黙したままの「ゾンビ接続」が長時間残るリスクが増えるためです。pingによる死活監視と組み合わせることが前提になります。

まとめ

ALBのアイドルタイムアウトは、WebSocketを特別扱いしない仕様であり、意図的な無通信区間を持つアプリでは切断の原因になり得ます。

切断時のクローズコード1006は、ネットワーク障害とロードバランサーによる切断を区別できないため、サーバー側で最終フレーム時刻を記録してギャップを可視化する調査方法が有効です。

対策は、サーバーからの定期ping送信、ALBのidle_timeout値の見直し、そして再接続時のセッション復元の3点セットで考えると抜け漏れが少なくなります。自分のサービスのトラフィックパターンに「意図的な沈黙」が含まれるかどうか、まず設計書やログを見直すところから始めてみてください。

参考

AWS ALB Idle Timeout Killed 14% of My Voice WebSockets at 60s

この記事について: 本記事は AI を活用して作成し、forva AI 編集部が内容を確認・監修しています。

AI 駆動開発のご相談は forva AI へ。まずはお気軽にどうぞ。