Slide 1

Slide 1 text

お前はまだ本当の
 Django ASGIを知らない
 @denzowill / Denzow Yamada
 2022/10/15
 #PyConJP2022


Slide 2

Slide 2 text

自己紹介
 2


Slide 3

Slide 3 text

LAPRAS
 3


Slide 4

Slide 4 text

LAPRAS
 4
 https://lapras.com/public/denzow

Slide 5

Slide 5 text

本セッションの概要
 5


Slide 6

Slide 6 text

本セッションの概要
 6
 ASGI?


Slide 7

Slide 7 text

本セッションの概要
 7
 ASGI?
 WebSocket?


Slide 8

Slide 8 text

本セッションの概要
 8
 ASGI?
 WebSocket?
 Django Channels?


Slide 9

Slide 9 text

本セッションの概要
 9
 ASGI?
 WebSocket?
 Django Channels?
 Async View?


Slide 10

Slide 10 text

Django の ASGI 対応
 10


Slide 11

Slide 11 text

そもそもASGI?
 11
 ASGI?

Slide 12

Slide 12 text

そもそもASGI?
 12
 Asynchronous Server Gateway Interface

Slide 13

Slide 13 text

そもそもASGI?
 13
 Asynchronous Server Gateway Interface

Slide 14

Slide 14 text

そもそもASGI?
 14
 Asynchronous Server Gateway Interface 非同期
 サーバー
 ゲートウェイ
 インターフェース


Slide 15

Slide 15 text

そもそもASGI?
 15
 https://asgi.readthedocs.io/en/latest/introduction.html ASGI は、 Web サーバー、フレームワーク、およびアプリケーション間の 互換性のための長年の Python 標準であるWSGIの精神的な後継者です。

Slide 16

Slide 16 text

WSGI?
 16
 WSGI?

Slide 17

Slide 17 text

WSGI?
 17
 Web Server Gateway Interface

Slide 18

Slide 18 text

WSGI?
 18
 Web Server Gateway Interface

Slide 19

Slide 19 text

WSGI?
 19
 Web Server Gateway Interface Web (同期的)
 サーバー
 ゲートウェイ
 インターフェース


Slide 20

Slide 20 text

WSGIはPEPにある
 20


Slide 21

Slide 21 text

ASGIはWSGIの後継
 21
 https://asgi.readthedocs.io/en/latest/introduction.html 「なぜ WSGI をアップグレードしないのか」と疑問に 思うかもしれません。 これは何年にもわたって何度も尋ねられてきましたが、 通常、問題は、WSGI の単一呼び出し可能なインターフェースが、 WebSocket のようなより複雑な Web プロトコルには 適していないということです。

Slide 22

Slide 22 text

ASGIのPEP?
 22
 ASGIのPEPは?

Slide 23

Slide 23 text

ASGIのPEP?
 23
 ASGIのPEPは? 今の所予定なし (今後は不明)

Slide 24

Slide 24 text

そもそもASGI?
 24
 WSGI ASGI

Slide 25

Slide 25 text

そもそもASGI?
 25
 WSGI ASGI アプリケーションサーバと アプリケーション間の規約


Slide 26

Slide 26 text

そもそもASGI?
 26
 アプリケーションサー バ
 アプリケーション
 フレームワーク
 WSGI

Slide 27

Slide 27 text

そもそもASGI?
 27
 アプリケーションサー バ
 アプリケーション
 フレームワーク
 ASGI daphne


Slide 28

Slide 28 text

そもそもASGI?
 28
 アプリケーションサー バ
 アプリケーション
 フレームワーク
 ASGI daphne
 LAPRASでは daphneを採用中

Slide 29

Slide 29 text

そもそもASGI?
 29
 WSGIの最小ケース
 def application(environ, start_response): body = b'Hello world!\n' status = '200 OK' headers = [('Content-type', 'text/plain')] start_response(status, headers) return [body]

Slide 30

Slide 30 text

そもそもASGI?
 30
 WSGIの最小ケース
 def application(environ, start_response): body = b'Hello world!\n' status = '200 OK' headers = [('Content-type', 'text/plain')] start_response(status, headers) return [body] 呼び出し可能で 2つの引数をとる

Slide 31

Slide 31 text

そもそもASGI?
 31
 WSGIの最小ケース
 def application(environ, start_response): body = b'Hello world!\n' status = '200 OK' headers = [('Content-type', 'text/plain')] start_response(status, headers) return [body] 第2引数のオブジェクトを ステータスとヘッダーとともに呼 び出す

Slide 32

Slide 32 text

そもそもASGI?
 32
 WSGIの最小ケース
 def application(environ, start_response): body = b'Hello world!\n' status = '200 OK' headers = [('Content-type', 'text/plain')] start_response(status, headers) return [body] レスポンスボディをiterableな バイト文字列を戻す

Slide 33

Slide 33 text

そもそもASGI?
 33
 ASGIの最小ケース(httpの場合)
 async def application(scope, receive, send): assert scope['type'] == 'http' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ], }) await send({ 'type': 'http.response.body', 'body': b'Hello, world!', }) 呼び出し可能な非同期オブジェクト scope: 接続の詳細を含むdict receive: クライアントからイベントを受信 send: クライアントへメッセージを送信

Slide 34

Slide 34 text

そもそもASGI?
 34
 ASGIの最小ケース(httpの場合)
 async def application(scope, receive, send): assert scope['type'] == 'http' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ], }) await send({ 'type': 'http.response.body', 'body': b'Hello, world!', }) scope: 接続の詳細を含むdict receive: クライアントからイベントを受信 send: クライアントへメッセージを送信

Slide 35

Slide 35 text

そもそもASGI?
 35
 ASGIの最小ケース(httpの場合)
 async def application(scope, receive, send): assert scope['type'] == 'http' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ], }) await send({ 'type': 'http.response.body', 'body': b'Hello, world!', }) WSGIでのstart_responseにあたる ヘッダーとステータスをクライアントへ 送信

Slide 36

Slide 36 text

そもそもASGI?
 36
 ASGIの最小ケース(httpの場合)
 async def application(scope, receive, send): assert scope['type'] == 'http' await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ [b'content-type', b'text/plain'], ], }) await send({ 'type': 'http.response.body', 'body': b'Hello, world!', }) レスポンスbodyの送信

Slide 37

Slide 37 text

そもそもASGI?
 37
 DEMO


Slide 38

Slide 38 text

Django の ASGI/WSGI
 38


Slide 39

Slide 39 text

DjangoのWSGI / ASGI
 39
 Djangoのwsgi.py/asgi.py
 asgi_sample/ ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py

Slide 40

Slide 40 text

DjangoのWSGI / ASGI
 40
 Djangoのwsgi.py/asgi.py
 asgi_sample/ ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py createprojectした時点で 用意されている(3.x以降)

Slide 41

Slide 41 text

DjangoのWSGI / ASGI
 41
 wsgi.py / asgi.py
 アプリケーションサーバへの Djangoのエントリポイント


Slide 42

Slide 42 text

DjangoのWSGI / ASGI
 42
 wsgi.py 
 https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/gunicorn/

Slide 43

Slide 43 text

DjangoのWSGI / ASGI
 43
 asgi.py 
 https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/daphne/

Slide 44

Slide 44 text

DjangoのWSGI / ASGI
 44
 wsgi.py 
 import os from django.core.wsgi import get_wsgi_application os.environ.setdefault( 'DJANGO_SETTINGS_MODULE', 'asgi_sample.settings' ) application = get_wsgi_application()

Slide 45

Slide 45 text

DjangoのWSGI / ASGI
 45
 django.core.handlers.wsgi.WSGIHandler
 class WSGIHandler(base.BaseHandler): : def __call__(self, environ, start_response): set_script_prefix(get_script_name(environ)) signals.request_started.send( sender=self.__class__, environ=environ ) request = self.request_class(environ) response = self.get_response(request) : start_response(status, response_headers) : return response

Slide 46

Slide 46 text

DjangoのWSGI / ASGI
 46
 django.core.handlers.wsgi.WSGIHandler
 class WSGIHandler(base.BaseHandler): : def __call__(self, environ, start_response): set_script_prefix(get_script_name(environ)) signals.request_started.send( sender=self.__class__, environ=environ ) request = self.request_class(environ) response = self.get_response(request) : start_response(status, response_headers) : return response

Slide 47

Slide 47 text

DjangoのWSGI / ASGI
 47
 asgi.py 
 import os from django.core.asgi import get_asgi_application os.environ.setdefault( 'DJANGO_SETTINGS_MODULE', 'asgi_sample.settings' ) application = get_asgi_application()

Slide 48

Slide 48 text

DjangoのWSGI / ASGI
 48
 django.core.handlers.asgi.ASGIHandler
 class ASGIHandler(base.BaseHandler): """Handler for ASGI requests.""" : async def __call__(self, scope, receive, send): # Serve only HTTP connections. # FIXME: Allow to override this. if scope["type"] != "http": raise ValueError( "Django can only handle ASGI/HTTP connections, not %s." % scope["type"] ) async with ThreadSensitiveContext(): await self.handle(scope, receive, send)

Slide 49

Slide 49 text

DjangoのWSGI / ASGI
 49
 django.core.handlers.asgi.ASGIHandler
 class ASGIHandler(base.BaseHandler): """Handler for ASGI requests.""" : async def __call__(self, scope, receive, send): # Serve only HTTP connections. # FIXME: Allow to override this. if scope["type"] != "http": raise ValueError( "Django can only handle ASGI/HTTP connections, not %s." % scope["type"] ) async with ThreadSensitiveContext(): await self.handle(scope, receive, send)

Slide 50

Slide 50 text

Django Channels と ASGIの関係
 50


Slide 51

Slide 51 text

Django Channels
 51
 https://channels.readthedocs.io/en/stable/

Slide 52

Slide 52 text

Django Channels
 52
 https://channels.readthedocs.io/en/stable/ WebSocketなどのプロトコルをサポートするDjango拡張 ASGIをベースに構成されている

Slide 53

Slide 53 text

Django Channels
 53
 https://github.com/django/channels/tags

Slide 54

Slide 54 text

Django Channels
 54
 https://github.com/django/channels/tags 現行は3.x系

Slide 55

Slide 55 text

Django Channels
 55
 https://speakerdeck.com/denzow/djangoto vuedezuo-rukanbanapurikesiyon?slide=55

Slide 56

Slide 56 text

Django Channels
 56
 https://speakerdeck.com/denzow/djangoto vuedezuo-rukanbanapurikesiyon?slide=55 LAPRASではDjango 2.x + Channels 2.x系で導入 現在Channels 3.x以降への移行を検討中

Slide 57

Slide 57 text

Django Channels
 Channels 2.xはDjango 2.xの時代から


Slide 58

Slide 58 text

Django Channels
 58
 Channels 2.xはDjango 2.xの時代から
 DjangoのASGIサポートは3.xから


Slide 59

Slide 59 text

Django Channels
 59
 Channels 2.xはDjango 2.xの時代から
 DjangoのASGIサポートは3.xから
 2.xではChannelsがASGI全体を担う


Slide 60

Slide 60 text

Django Channels
 60
 https://channels.readthedocs.io/en/2.x/deploying.html

Slide 61

Slide 61 text

Django Channels
 61
 https://channels.readthedocs.io/en/2.x/deploying.html asgi.pyはないので 自分でつくる

Slide 62

Slide 62 text

Django Channels
 62
 https://channels.readthedocs.io/en/2.x/deploying.html asgi.pyはないので 自分でつくる

Slide 63

Slide 63 text

Django Channels
 63
 Channels 2.xはDjango 2.xの時代から
 DjangoのASGIサポートは3.xから
 2.xではChannelsがASGI全体を担う


Slide 64

Slide 64 text

Django Channels
 64
 Channels 2.xはDjango 2.xの時代から
 DjangoのASGIサポートは3.xから
 3.xからChannelsはDjangoと協調してASGIを担う


Slide 65

Slide 65 text

Django Channels
 65
 Django 3.x 以降は ASGI対応なんだから
 Channelsはいらないんじゃ?
 🤔

Slide 66

Slide 66 text

Django Channels
 66
 Djangoのwsgi.py/asgi.py
 asgi_sample/ ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py

Slide 67

Slide 67 text

Django Channels
 67
 django.core.handlers.asgi.ASGIHandler
 class ASGIHandler(base.BaseHandler): """Handler for ASGI requests.""" : async def __call__(self, scope, receive, send): # Serve only HTTP connections. # FIXME: Allow to override this. if scope["type"] != "http": raise ValueError( "Django can only handle ASGI/HTTP connections, not %s." % scope["type"] ) async with ThreadSensitiveContext(): await self.handle(scope, receive, send)

Slide 68

Slide 68 text

Django Channels
 68
 django.core.handlers.asgi.ASGIHandler
 class ASGIHandler(base.BaseHandler): """Handler for ASGI requests.""" : async def __call__(self, scope, receive, send): # Serve only HTTP connections. # FIXME: Allow to override this. if scope["type"] != "http": raise ValueError( "Django can only handle ASGI/HTTP connections, not %s." % scope["type"] ) async with ThreadSensitiveContext(): await self.handle(scope, receive, send) DjangoのASGIはHTTPしかサポートしない

Slide 69

Slide 69 text

Django Channels
 69
 https://channels.readthedocs.io/en/stable/deploying.html#configuring-the-asgi-application 


Slide 70

Slide 70 text

Django Channels
 70
 https://channels.readthedocs.io/en/stable/deploying.html#configuring-the-asgi-application 
 asgi.pyを書き換えろ(自作しろ)

Slide 71

Slide 71 text

Django Channels
 71
 : os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") django_asgi_app = get_asgi_application() from chat import AdminChatConsumer, PublicChatConsumer application = ProtocolTypeRouter({ # Django's ASGI application to handle traditional HTTP requests "http": django_asgi_app, # WebSocket chat handler "websocket": AllowedHostsOriginValidator( AuthMiddlewareStack( URLRouter([ path("chat/", PublicChatConsumer.as_asgi()), ]) ) ), })

Slide 72

Slide 72 text

Django Channels
 72
 : os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") django_asgi_app = get_asgi_application() from chat import AdminChatConsumer, PublicChatConsumer application = ProtocolTypeRouter({ # Django's ASGI application to handle traditional HTTP requests "http": django_asgi_app, # WebSocket chat handler "websocket": AllowedHostsOriginValidator( AuthMiddlewareStack( URLRouter([ path("chat/", PublicChatConsumer.as_asgi()), ]) ) ), }) httpはDjango自体のASGI対応に流す

Slide 73

Slide 73 text

Django Channels
 73
 : os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") django_asgi_app = get_asgi_application() from chat import AdminChatConsumer, PublicChatConsumer application = ProtocolTypeRouter({ # Django's ASGI application to handle traditional HTTP requests "http": django_asgi_app, # WebSocket chat handler "websocket": AllowedHostsOriginValidator( AuthMiddlewareStack( URLRouter([ path("chat/", PublicChatConsumer.as_asgi()), ]) ) ), }) websocketプロトコルはChannelsに流す

Slide 74

Slide 74 text

Django Channels
 74
 ASGIはサーバとアプリケーション間の非同期規格
 ASGI != WebSocket等のプロトコルサポート


Slide 75

Slide 75 text

パフォーマンスの話
 75


Slide 76

Slide 76 text

パフォーマンスの話
 76
 DjangoでWSGIをASGIに書き換えたら早くなるのか


Slide 77

Slide 77 text

パフォーマンスの話
 77
 Djangoで同期ビューを非同期ビューに書き換えたら早 くなるのか


Slide 78

Slide 78 text

パフォーマンスの話
 78
 ● 3.0 → ASGIサポート(AsyncViewサポートなし)
 ● 3.1 → ファンクションベースのAsyncViewサポート
 ● 4.1 → クラスベースビューでのAsyncViewサポート


Slide 79

Slide 79 text

パフォーマンスの話
 79
 ● 3.0 → ASGIサポート(AsyncViewサポートなし)
 ● 3.1 → ファンクションベースのAsyncViewサポート
 ● 4.1 → クラスベースビューでのAsyncViewサポート
 書き換えの土壌が整った!

Slide 80

Slide 80 text

パフォーマンスの話
 80
 そもそも書き換えるには
 https://docs.djangoproject.com/en/4.1/topics/class-based-views/#asynchronous-class-based-views import asyncio from django.http import HttpResponse from django.views import View class AsyncView(View): async def get(self, request, *args, **kwargs): await asyncio.sleep(1) return HttpResponse("Hello async world!")

Slide 81

Slide 81 text

パフォーマンスの話
 81
 そもそも書き換えるには
 https://docs.djangoproject.com/en/4.1/topics/class-based-views/#asynchronous-class-based-views import asyncio from django.http import HttpResponse from django.views import View class AsyncView(View): async def get(self, request, *args, **kwargs): await asyncio.sleep(1) return HttpResponse("Hello async world!")

Slide 82

Slide 82 text

パフォーマンスの話
 82
 そもそも書き換えるには
 https://docs.djangoproject.com/en/4.1/topics/class-based-views/#asynchronous-class-based-views import asyncio from django.http import HttpResponse from django.views import View class AsyncView(View): async def get(self, request, *args, **kwargs): await asyncio.sleep(1) return HttpResponse("Hello async world!") 非同期対応のライブラリに 差し替える必要がある

Slide 83

Slide 83 text

パフォーマンスの話
 83
 そもそも書き換えるには
 https://docs.djangoproject.com/en/4.1/topics/class-based-views/#asynchronous-class-based-views import asyncio from django.http import HttpResponse from django.views import View class AsyncView(View): async def get(self, request, *args, **kwargs): await asyncio.sleep(1) return HttpResponse("Hello async world!") 同期処理はsync_to_asyncデコレータを 使う必要もある

Slide 84

Slide 84 text

パフォーマンスの話
 84
 そもそも非同期のメリットを得られるのは
 CPUバウンドな処理ではなくI/Oバウンドな処理
 例: 外部API連携、ファイルI/O..


Slide 85

Slide 85 text

パフォーマンスの話
 85
 Djangoで一番I/Oに関わるのはORM


Slide 86

Slide 86 text

パフォーマンスの話
 86
 Djangoで一番I/Oに関わるのはORM
 非同期対応は道半ば…

Slide 87

Slide 87 text

パフォーマンスの話
 87
 Djangoで一番I/Oに関わるのはORM
 非同期対応は道半ばだが 4.1で非同期用の インターフェース導入

Slide 88

Slide 88 text

パフォーマンスの話
 88
 Djangoで一番I/Oに関わるのはORM
 今後よりネイティブにAsyncを 使える未来が近い

Slide 89

Slide 89 text

パフォーマンスの話
 89
 ASGIを全力で活かしたいが、いま時点で は書き換えのコストを払えない
 😭

Slide 90

Slide 90 text

パフォーマンスの話
 90
 来年、Viewを全部Asyncしてみた、みたい な話で登壇できたらいいなぁ…
 😇

Slide 91

Slide 91 text

まとめ
 91


Slide 92

Slide 92 text

まとめ
 ● ASGIはWSGIの精神的後継
 ● WebSocketしたいなら今もChannelsが必要
 ● Djangoは3.0からASGIをサポートしサポート範囲がどんどん増えて いる
 ● Asyncによるメリットを得やすい土壌は整ってきた
 ○ Async ORMの今後の拡充に期待
 ○ LAPRASのViewを全部Asyncに書き換えたい人募集
 92


Slide 93

Slide 93 text

宣伝
 93
 このあとすぐ!