Djangoで作成したアプリを本番環境で動かすときにWSGIとASGIのどちらを使うのか

先日、 django-jaのDiscord で出た話題ですが、Djangoで作成したアプリを本番環境で動かす際のアプリケーションサーバーについてです。

具体的にはWSGIとASGIどちらを使うのか、そしてアプリケーションサーバーはどれがよいのか。

WSGIかASGIか

DjangoをWSGIかASGIのどちらで動かすかですが、迷うぐらいなら現状はWSGIでいいです。

「ASGIは必要になったら使う」ぐらいの気持ちでいるのをおすすめします。

ASGIは、PythonのAsync(非同期)に対応するための、アプリケーションインターフェースです。

asgi.readthedocs.io

WSGIをおすすめする理由は次の通り。

ASGIをおすすめしない理由は次の通り。

  • 非同期処理になるのでデバッグしづらい
    • とにかくこれです。IO待ちの状況では並行で実行されてコンピュータリソース的には効率良いわけですが、エラーのときのスタックトレースが読みづらいです。実行順序もアプリによってはバラバラになることもあり、エラーの再現もしづらいです。
  • まだPython標準仕様ではない(が、デファクトスタンダードにはなっている)
    • 積極的に採用するほどでもない、という感じ

ASGIをおすすめする状況は、現状だと限定的かなあと思ってます。

  • Django Channelsを使っている場合
    • ほぼこれです。現在のDjango ChannelsはASGI前提にしているので、Django Channelsを使っている場合はASGIサーバーで動かすことになります。
  • アプリがASGI対応、Asyncのコードになっている場合
    • そもそもasync/awaitを使ったアプリの場合は、ASGIサーバーで動かさないと恩恵があまりないため、この場合もASGIサーバーを利用することになるでしょう。

参考: docs.djangoproject.com

Djangoでasync、awaitを使った場合、I/O待ちの部分では非同期処理によって高速化を期待できます。しかし、CPU依存の処理については特に速くなるわけではありません。

もしもWSGIアプリケーションのパフォーマンスを上げたい場合、次のような方法を試して、どうにもならない場合にようやく非同期IOやASGIサーバーを検討する、ぐらいでいいかなと思います。

  • アプリケーションサーバーの設定見直し
    • プロセス数を増やす
    • スレッド数を増やす
  • WSGIサーバーの変更を検討
    • gunicornの場合はuWSGIに変えてみる、など。
  • DBクエリの最適化
    • n+1クエリをなくす、速度の遅いクエリを改善する
  • DB自体の最適化
    • サーバー性能を上げて高速にする
    • インデックスを作ってクエリが速くなるようにする
  • 外部通信回数の削減
    • DBや外部ストレージなどとの通信レイテンシの影響を減らして高速化する
  • 外部呼び出し部分の非同期タスク化
    • ここでいう非同期タスク化は、Celeryを使ったり、バッチ処理にするという意味

ここに記載した方法以外にも、ウェブアプリ自体のパフォーマンス改善方法が色々あるので、それらを一通りやったあとに、非同期IOについて考えてみるぐらいでいいかも。

おすすめのWSGIサーバー

WSGIサーバーは好みもあるので、人によって答えは異なります。

個人的にはGunicornを長く使っていて、2024年現在でもこれをおすすめしています。

gunicorn.org

個人的に気に入ってる点は次の通りです。

  • Gunicorn自体のコードはPure Pythonで書かれている
    • 何か不具合のときに調査しやすい
    • セットアップでハマりが少ない
  • マルチプロセス+マルチスレッドでの動作に対応している
  • Workerスタイルでの動作のため、アプリケーションが不安定でプロセスが落ちても自動再起動がかかる

一方、気をつける点は以下です。

  • Linuxでしか動かない(そもそもコンテナで動かすなら最近はあまり問題にならないですが..)
  • Pure Pythonなので、限界までCPUリソースを使い切りたいアプリには向かない
  • uWSGIのように機能が豊富ではない(サイドカー的に色々動かすとかはuWSGIのほうが簡単)

おすすめのASGIサーバー

AWGIサーバーについては、おすすめできるほどの運用実績やノウハウがまだ個人的には少ないです。運用実績はあまり急激に増えることもなさそうです。

自社内としてはUvicornを使ってる部分が多いです。ただし、DaphneやHypercornを評価、比較したわけでもないです。

www.uvicorn.org

Uvicornを使っている理由としては、GunicornのWorkerとして動作させることもできるからです。 これはつまり、Gunicornのプロセス管理機能をそのまま使えるということです。

アプリケーション起因でWorkerプロセスが落ちても自動再起動できます。

Daphneで同様のことをしたい場合は、別途Supervisorなどを併用すればよいのですが、Gunicorn+Uvicornで同じPythonプロセスの中で完結するならそのほうが楽かなという印象を持ってます。