Django の runserver は遅い
皆さん開発時に Django の runserver というコマンドを使っていると思いますが、この runserver は本番環境では使えません。
DO NOT USE THIS SERVER IN A PRODUCTION SETTING. It has not gone through security audits or performance tests.
runserver は、パフォーマンスのテストや、セキュリティの検証をしていないからです。とあります。
https://docs.djangoproject.com/ja/3.1/ref/django-admin/#django-admin-runserver
検証用のアプリケーションを用意する
検証用のアプリケーションを用意しました
このアプリケーションの /
に対して、 ApacheBench で大量にリクエストを送り、パフォーマンスを検証します。
主に1秒間にどれだけのリクエストをさばけたかを見ることにします。
アプリケーションの中身は、一定時間 sleep した後に、Hello World を返すだけです。
from django.contrib import admin from django.urls import path from django.http import HttpResponse from time import sleep def index(request): sleep(0.2) return HttpResponse("Hello, world!") urlpatterns = [ path('admin/', admin.site.urls), path('', index), ]
git clone
して docker-compose up -d
するだけで、アプリケーションが立ち上がります。
$ git clone git@github.com:yoshikyoto/django-bench.git $ cd django-bench $ docker-compose up -d
http://localhost:8000 でアプリケーションが起動しています。
runserver に対して ApacheBench を実行
まずは、 runserver に対して ApacheBench を実行して、どれくらいのパフォーマンスが出るかを測定します。
ApacheBench は、アプリケーションに対して大量のリクエストを送り、パフォーマンスを測定するツールで、Mac であればデフォルトでインストールされています。
runserver でアプリケーションが動いていることを確認します
$ docker-compose ps Name Command State Ports ------------------------------------------------------------------------ django bash -c ./manage.py runser ... Up 0.0.0.0:8000->8000/tcp
今回は ApacheBench のコマンド ab
で、1000 リクエストを 100 並列で送ってみます。
$ ab -n 1000 -c 100 http://127.0.0.1:8000/
3回実行した結果、秒間リクエスト数は、 57.26, 35.31, 18.46 となった。
実行結果の詳細を見たい人はこちらを確認してください
gunicorn に対して ApacheBench を実行
今度は、 gunicorn に変更して試してみます。
まずは Docker を終了させます。
$ docker-compose down
docker-compose.yml
で、 runserver の command をコメントアウトし、 gunicorn の command をコメントインします。
gunicorn のワーカーの数なんですが、最初1にして ApacheBench でリクエストを送ってみたところ、CPU使用率が2%くらいしかなかったので、ワーカー数 50 になるようにコマンドを修正します。
gunicorn -w 50 -b 0.0.0.0:8000 testproject.wsgi:application
その後、 Docker を起動します。
$ docker-compose up -d
gunicorn が起動していることを確認します。
$ docker-compose ps Name Command State Ports ------------------------------------------------------------------------ django bash -c gunicorn -w 50 -b ... Up 0.0.0.0:8000->8000/tcp
ApacheBench を3回実行すると、秒間リクエスト数は、 125.97, 137.03, 111.55 となった。
runserver の時よりも倍以上のパフォーマンスになっている。
今回は sleep するだけで、CPU 負荷も、メモリも使わないので、worker を増やせば増やしただけパフォーマンスが向上する可能性が高い。
実際、 sleep するだけのプログラムは無いが、例えば、 DB にクエリを投げて結果を待っている時は、 sleep しているのと揺動、アプリケーションサーバーは CPU を使っていない。つまり、 woker の数を増やせばパフォーマンスが向上することになる。
nginx を使うことについて
実際にはここに nginx を挟んで
nginx -> gunicorn -> Django
といった構成になることが多い。
この場合、 nginx があるため、 gunicorn に直接リクエストを飛ばすよりも、パフォーマンスは劣化する。
ただし、nginx には、
- アクセスログを書く機能
- 特定の ip をブロックする機能
- 秒間リクエスト数を制限する機能
- レスポンスを gzip で圧縮する機能
など、 Web アプリケーションによって必要な機能が揃っているため、導入するメリットが大きい。
今回はやらないが、同様に nginx を挟んだ場合のパフォーマンスも計測してみると良い。
まとめ
ApacheBench を使って調べた結果、 Django の runserver コマンドを使うより、 gunicorn を使って並列数を上げたほうが、処理できるリクエスト数は多くなった。