筒井@ryu22eです。8月の
Webアプリケーションの非同期処理とは何か?
非同期処理をサポートするWebアプリケーションでは、複数のリクエストを受け取った際、シングルスレッドの中で各リクエスト用の処理を細かく切り替えながら、同時に動かしているように見せかけて実行します
非同期処理の利点として、Djangoの公式ドキュメントでは以下のように説明しています[1]。
The main benefits are the ability to service hundreds of connections without using Python threads. This allows you to use slow streaming, long-polling, and other exciting response types.
つまり、非同期処理により、少ないリソースで大量のリクエストを効率よく捌くことができます。
Python製Webアプリケーションで非同期処理を実行するには
Webアプリケーションを動かすには、WebサーバーとPythonアプリケーションの間でデータのやり取りを仲介する標準インターフェースが必要です。Pythonの世界では、WSGI
ところが、WSGIは同期処理を前提とした仕様なので、非同期処理を実行するWebアプリケーションには対応できません。
そこで、WSGIの
ASGIはWSGIとの互換性を持ち、同期アプリケーション、非同期アプリケーションの両方をサポートします。
Djangoで非同期アプリケーションを作るには
Djangoは2019年12月2日にリリースされたバージョン3.
そして、2021年3月にリリースされたバージョン3.async
を付けると非同期関数ベースViewになります[3]。
import datetime
from django.http import HttpResponse
async def current_datetime(request):
now = datetime.datetime.now()
html = '<html><body>It is now %s.</body></html>' % now
return HttpResponse(html)
しかし、バージョン3.
そして、今月その状況をさらに改善する大きなリリースがあります。2022年8月リリース予定のバージョン4.
Django公式サイトの4.
- Django 4.
1 release notes - UNDER DEVELOPMENT | Django Documentation - https://
docs. djangoproject. com/ en/ dev/ releases/ 4. 1/
非同期クラスベースViewの書き方は簡単です。以下のように、メソッド定義の先頭にasync
を付けるだけです。
import asyncio
from django.http import HttpResponse
from django.views import View
class AsyncView(View):
# メソッドの先頭にasyncを付ける
async def get(self, request, *args, **kwargs):
await asyncio.sleep(1)
return HttpResponse("非同期ビューのレスポンス")
上記Viewは、1秒間待機した後
async
を付けなかった場合は、従来どおりの同期Viewとして動作します。
非同期Viewの中で呼んではいけない処理と、その回避方法
以下のコードを見てください。非同期Viewの中でModelを操作して、データベースの内容をレスポンスとして返しています。
from django.http import HttpResponse
from django.views import View
from .models import Book
class AsyncView(View):
async def get(self, request, *args, **kwargs):
titles = []
for book in Book.objects.all():
titles.append(book.title)
return HttpResponse(",".join(titles))
一見問題ないコードですが、このViewにリクエストを送ってみると、以下のようにSynchronousOnlyOperation
エラーが発生します。
SynchronousOnlyOperation
エラーは、非同期処理の中で安全に呼べない処理が書かれていた場合に発生するエラーです。データベースの操作は同期専用の処理なので、このままでは非同期Viewの中に書くことができません。
上記コードを動かすには、以下のようにModelの操作にasync
を付けます。
from django.http import HttpResponse
from django.views import View
from .models import Book
class AsyncView(View):
async def get(self, request, *args, **kwargs):
titles = []
async for book in Book.objects.all():
titles.append(book.title)
return HttpResponse(",".join(titles))
Model操作以外の同期専用の処理は、sync_
from asgiref.sync import sync_to_async
def sync_only1():
"""同期処理専用の関数"""
...
# sync_to_asyncでラップして実行
await sync_to_async(sync_only1)()
# 関数デコレータとしても使える
@sync_to_async
def sync_only2():
"""同期処理専用の関数"""
...
SynchronousOnlyOperation
エラーが発生するが、とりあえずコードを動かしてみたい場合は、環境変数DJANGO_
ブラウザ上で動作確認するならDJANGO_
、テスト実行ならDJANGO_
とすれば、SynchronousOnlyOperation
エラーを防ぐことができます。
ただし、DJANGO_
は開発環境で一時的に動作確認する以外の用途で使わないでください。Djangoには非同期の実行を想定していない、グローバルな状態を保持する処理があり、SynchronousOnlyOperation
エラーはそれらを非同期で実行させないための仕組みです。
SynchronousOnlyOperation
を無視すると、想定外の挙動でデータの損失または破損が発生する可能性があるので注意してください。
Djangoで作った非同期アプリケーションを本番環境で動かすには
runserver
コマンドはあくまで開発環境用の機能です。本番環境で動かすには、アプリケーションサーバーと組み合わせる必要があります。
非同期アプリケーションを本番環境で動かすには、ASGIに対応したアプリケーションサーバーが必要です。Python用アプリケーションサーバーといえばGunicornやuWSGIなどが有名ですが、これらは ASGIに対応していません。非同期アプリケーションを動かすことはできますが、本来のパフォーマンスを発揮できません。
ASGIに対応したアプリケーションサーバーには以下のようなものがあります。
今回は、Uvicornを使ってアプリケーションを起動する例を紹介します。
Uvicornを使う方法はいくつかありますが[4]、今回はGunicornのワーカープロセスとして利用してみましょう。
まず、pip
コマンドでUvicornとGunicornをインストールします。
$ pip install uvicorn gunicorn
gunicorn
コマンドの引数にASGI用設定が書かれたasgi.
の場所を指定する必要があります。
asgi.
はstartproject
コマンドでDjangoプロジェクトを作成した際に、プロジェクト名と同名のディレクトリの直下に作成されるファイルです。場所の指定は{プロジェクト名}.asgi:application
のように書きます。-w
オプションでワーカー数、-k
オプションでワーカークラスを指定します。Uvicornが提供するワーカークラスはuvicorn.
です。
Djangoプロジェクト名がdjango41_gunicorn
コマンドには以下のような引数を渡します。
$ gunicorn django41_example.asgi:application -w 4 -k uvicorn.workers.UvicornWorker
アプリケーションが起動すると、http://
が使えるようになります。
まとめ
Djangoの非同期Viewは多少気をつける点はあるものの、簡単に書けるし、間違っていても例外で教えてくれるので安心感がありますね。この記事を読んで興味を持った方は、ぜひ自分でもアプリケーションを作ってみてください!