👻

Django + daphne + channels構成のWebsocketサーバーで502エラー

2024/02/03に公開

概要

Django + Dapahne + Channlels 構成の Websokcet サーバーにて、502エラーが発生する事態に遭遇しました。

原因や解決策を、調べた範囲で書いていきます。

結論

サーバー側に、実質的な Websocket 接続上限が存在していた。

上限を超えてWebsocket接続を試みた結果、接続時に502エラーが発生した。

Websocketの上限は、マシンの CPU コア数に依存する模様。

また、環境変数で上限を設定することもできる。

構成

architecture

遭遇した問題

ある日、WebsocketクライアントからWebsocket接続を試みたところ、エラーが発生するようになった。

このエラーを観察したところ、以下の特徴があった。

  • 常に失敗するのではなく、失敗したり成功したりする
  • 一度失敗すると、連続で失敗する傾向にある
  • しばらく時間を置くと、成功することがある

ログを確認したところ、以下を確認。

  • Websocketクライアント(≒フロントエンド)側にて、 502 ステータスコードが記録されていること
  • Websocketサーバー(≒バックエンド)側にて、 1006 ステータスコードが記録されていること

原因(仮説)

ASGI(≒Websocketサーバー)側で、スレッドの上限を管理している模様。

以下、 https://docs.djangoproject.com/ja/5.0/_modules/asgiref/sync/ より該当箇所を引用。

    # If they've set ASGI_THREADS, update the default asyncio executor for now
    if "ASGI_THREADS" in os.environ:
        # We use get_event_loop here - not get_running_loop - as this will
        # be run at import time, and we want to update the main thread's loop.
        loop = asyncio.get_event_loop()
        loop.set_default_executor(
            ThreadPoolExecutor(max_workers=int(os.environ["ASGI_THREADS"]))
        )

また、 github の issue に、ASGI_THREADS 環境変数でスレッド数制限を試みた旨の記載が確認できた。

マシンスペック不足で起こりがちな エラーの特徴 も相まって、「このスレッド1つが、1つのWebsocket接続を処理しているのでは?」「スレッドが足りなくなるほど接続を試みた結果、接続に失敗したのでは?」と推測。

以上より、このスレッドが、Websocket 接続に使用されているのでは?という仮説を立てた。

検証(エラーの再現)

ASGI_THREADS の値だけ、Websocket接続用のスレッドが作られる」と仮定するなら、
ASGI_THREADS 環境変数を1に設定すると、2つ以上の Websocket 接続時にエラーが発生すると思われる。

ASGI_THREADS=1 に設定し、2つ目の Websocket 接続を試みたところ、以下を確認。

  • クライアント側では、 hang up のメッセージと共に切断
  • サーバー側では、ステータスコード 1006 を返しつつ切断

仮説通りの結果となった。

解決策

幾つかの案が考えられる。

No メリット デメリット
1 スケールアップ(マシンのCPUを増設)する 手っ取り早い 手動運用(接続数が更に増えた場合、手動で更なるスケールアップ)が必要
2 スケールアウト(Websocket接続数に応じてインスタンスを増設)する 自動運用可能 Websocket接続数にてスケールアウトする仕組みの開発が必要
3 ASGI_THREADS の値を設定する Websocket接続数をコントロールできる。上記案との併用も考えられる。 (調べた限り)適正値不明

案1は、暫定対応といった、応急処置に近い。

案2は、恒久対応に近い。

ASGI_THREADS に大きな値を割り当てれば解決しそうな気はするが、どのような挙動になるかはわからない。

その他

ASGI_THREADS 未設定時のスレッド数は不明。

issue を見ると、 「CPU * 5 かもしれないが、確かではない」といった記述がみられる。

参考文献

Discussion