0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめてのアドベントカレンダーAdvent Calendar 2023

Day 5

[App Service] Flask Socket.ioでリアルタイム通信を安定化させる方法

Posted at

はじめに

Azure App Serviceを使用してFlaskとSocket.ioベースのリアルタイム通信アプリをデプロイするとき、通信が不安定になる・すぐに接続が途切れるといった問題に直面しました。本記事では、この問題の解決方法について解説します。

やりたいこと

以下のようなコードで作成されるFlaskアプリをAzure App Serviceにデプロイすることを想定します。基本的なSocket.ioによる通信が含まれています。

# startup.py
from flask import Flask
from flask_socketio import SocketIO

app = Flask(__name__)
socketio = SocketIO(app)

@app.route('/')
def index():
    return 'Hello world'

@socketio.on('message')
def handle_message(message):
    print('Received message: ' + message)

if __name__ == '__main__':
    socketio.run(app)
# スタートアップコマンド
# Azure Portalで 設定→構成→全般設定→スタックの設定 から設定できる
gunicorn --bind=0.0.0.0 --workers=2 startup:app

開発環境

  • Azure App Service (Free F1プラン・Linux)
  • Python 3.10
  • Flask 3.0.0
  • Flask-SocketIO 5.3.6
  • gunicorn 21.2.0

問題

ページ読み込み直後はSocket通信によってデータを受信できましたが、その後すぐに通信が切断されます。クライアントのコンソールには以下のようなエラーメッセージが大量に表示されていました。

GET https ://example.azurewebsites.net/socket.io/?EIO=********* 400 (Bad Request) polling.js:298

解決方法

スタートアップコマンドにEventletを用いることによってリアルタイム通信が十分に機能するようになりました。具体的には以下のようにファイル・コマンドを変更しました。

  1. スタートアップコマンドの修正

    # スタートアップコマンド
    # Azure Portalで 設定→構成→全般設定→スタックの設定 から設定できる
    gunicorn --worker-class eventlet -w 1 startup:app
    
  2. Pythonコードの修正

    # startup.py で以下を変更
    # これは明示的に同期方法を指定している。あくまでおまじない。
    socketio = SocketIO(app, cors_allowed_origins="*", async_mode="eventlet")
    
  3. 必要なライブラリをインストールさせる

    # requirements.txt に以下を追加
    gunicorn
    eventlet
    gevent
    

試したけれど問題解決に繋がらなかったこと

  1. App Serviceのプランをアップグレードする:最上位プラン(Premium v2 P3V2)を1時間借りて試しましたが、パフォーマンス・安定性の向上に繋がりませんでした。上記の対策後は無料プラン(Free F1プラン)でも快適に動作しています。
  2. Azure PortalからCORSの設定を変更する:一切触らなくても通信の安定化はできました。

なぜこの方法で解決できたのか?

デフォルトのGunicorn設定では、同期ワーカーが使用されます。同期ワーカーでは、1つのワーカーが一度に処理できるリクエストが1つだけです。1つの接続がブロックされると、他の接続も待たされることになり、通信の不安定さにつながります。そのため、長時間接続を維持するSocket通信には不適切です。

一方、gunicorn --worker-class eventlet -w 1 startup:app コマンドを使うと、Eventletがワーカークラスとして設定されます。Eventletは非同期I/Oをサポートし、一つのプロセスで複数の接続を並行して処理できるようになります。これにより、同時に多くの接続を処理できるため、リアルタイム通信の安定性が向上できたというわけです。

なお、-w 1はワーカーの数を1に設定しますが、一般的には(2*コア数)+1に設定することが推奨されているようです。

結論

Socket通信を扱う場合、非同期処理をサポートするEventletのようなワーカークラスの使用が推奨されます。これにより、多数の同時接続を扱う際のパフォーマンスと安定性が向上します。

参考文献

デプロイメント Flask-SocketIOドキュメント
設計 Gunicorn 21.2.0ドキュメント

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?