2026年のDjango async viewとASGI: パフォーマンスと面接質問

2026年のDjango async viewとASGIを深掘りします。内部の仕組み、どのserverをデプロイすべきか、async ORMとSynchronousOnlyOperationの罠、そして面接質問までを解説します。

並行リクエストを処理する Django の async view と ASGI アーキテクチャ

Django の async view を使うと、接続ごとに thread を割り当てることなく、単一の worker で数百の I/O バウンドなリクエストを同時に処理できます。ただしその効果が得られるのは、view から個々のネットワーク呼び出しに至るまで、コードパスが非同期のまま保たれている場合に限られます。Django 3.1 で async def view が導入され、Django 4.1 で async ORM インターフェースが追加されたことで、このフレームワークは実用に耐える ASGI プラットフォームになりました。しかし本番環境の多くは依然として WSGI 上で動作しており、この並行性は活かされないままです。本記事では、ASGI 上で async view がどう振る舞うのか、2026 年にどの server を選ぶべきか、SynchronousOnlyOperation を引き起こす ORM の落とし穴、そして async Django を実際に運用したエンジニアと、読んだだけのエンジニアを見分ける面接質問を掘り下げて解説します。

async が本当に効果を発揮する場面

async view が報われるのは、リクエストの大半の時間が、制御下にない I/O の待機に費やされる場合です。サードパーティの HTTP API、遅い下流サービス、あるいは長時間維持されるストリーミングレスポンスなどが該当します。CPU バウンドな処理や高速なローカルデータベースクエリでは、Gunicorn の worker を増やした同期 view の方が通常は高速で、動作を追うのもはるかに容易です。

ASGI 上で Django の async view はどう動くのか

WSGI は本質的に同期的です。処理中の各リクエストは最初から最後まで 1 つの worker thread またはプロセスを占有するため、遅いリクエストが 50 個あれば 50 個の worker が必要になります。ASGI はこのモデルを event loop に置き換え、単一の worker 上で多数のリクエストを交互に処理します。coroutine は I/O を await するたびに中断され、データが到着すると再開されます。Django 公式の async ドキュメントは、この共存を可能にする 2 つのアダプタを説明しています。ASGI 上では、Django は async def view を loop 上でネイティブに実行し、同期 view は sync_to_async で threadpool に押し出します。WSGI 上では、async view を async_to_sync でラップして完了まで実行し、並行性の恩恵をすべて捨ててしまいます。

実務上の帰結は単純明快です。WSGI server 上にデプロイされた async def view は、より遅い同期 view として振る舞います。並行性が実際に発現するのは ASGI server 上だけです。外部 API を呼び出す最小限の async view は、同期版とほぼ同一の見た目になり、違いは await に集約されます。

python
# views.py
import httpx
from django.http import JsonResponse

async def dashboard(request):
    # ASGI 上ではこの coroutine が event loop 上で実行されるため、httpx が
    # ネットワークの往復を await している間、worker は他のリクエストを処理できます。
    async with httpx.AsyncClient(timeout=5.0) as client:
        response = await client.get("https://api.example.com/metrics")
    return JsonResponse(response.json())

ここで得られるのは、単一のリクエストが速く完了することではありません。実時間(wall-clock time)は同じです。得られるのは、await の間に worker がブロックされないことであり、その結果、プロセス数を一定に保ったまま並行負荷下のスループットが向上します。

同じモデルは、WSGI 上では扱いにくいレスポンスパターンも可能にします。Django 4.2 以降、StreamingHttpResponse は async generator を受け取れるようになり、view は上流のソースが生成するたびにチャンクを yield できます。これは接続が数秒間開いたままになる Server-Sent Events や、プロキシされた LLM のトークンストリームに適しています。WebSocket のような永続的な双方向プロトコルは、素の HTTP view ではなく依然として Django Channels を経由しますが、どちらも同じ ASGI 基盤の上に成り立っています。そのため async view を採用したプロジェクトは、後からリアルタイム機能を追加する際のインフラコストをすでに支払っていることになります。

2026 年における Django の ASGI server の選び方

Django は asgi.py に ASGI アプリケーションオブジェクトを同梱していますが、それ自体は動作しません。event loop を駆動するのは専用の ASGI server です。ASGI 仕様は、これらの server がすべて実装するインターフェースを定義しています。だからこそプロトコルレベルで相互に置き換え可能であり、違いは主に性能とプロトコル対応範囲に現れます。

Server実装言語適した用途
UvicornPython (uvloop)汎用 ASGI、HTTP/1.1、一般的なデフォルト
DaphnePythonDjango Channels、WebSocket、HTTP/2
GranianRust最高の生スループット、HTTP/1 と HTTP/2
HypercornPythonHTTP/2 と HTTP/3 (QUIC)

2026 年のほとんどのデプロイでは、Gunicorn の worker class として動かす Uvicorn が引き続き信頼できる選択肢です。Gunicorn はプロセスのライフサイクルを監督し、クラッシュした worker を再起動し、シグナルを処理します。一方で Uvicorn は各 worker の内部で event loop を駆動します。アップグレード時につまずきやすい移行上の注意点が 1 つあります。Gunicorn の worker class は Uvicorn 0.30 の時点で Uvicorn 本体から独立した uvicorn-worker パッケージに移動したため、import パスが変わりました。

bash
# 先に worker パッケージをインストールする: pip install uvicorn-worker
gunicorn myproject.asgi:application \
    --workers 4 \
    --worker-class uvicorn_worker.UvicornWorker \
    --bind 0.0.0.0:8000

最大限のスループットを追求するチームは、Python 側の HTTP パースのオーバーヘッドを取り除く Rust ベースの server である Granian を選ぶことが増えています。どれを選ぶにせよ、async が本番環境に現れるのは意図的に設定した場合に限られる理由は、まさにこのデプロイ事情にあります。ASGI 固有のセットアップには、Django のデプロイに関する面接質問モジュールで扱う運用上の注意点がそのまま当てはまります。

async ORM と SynchronousOnlyOperation の落とし穴

Django 4.1 は ORM 全体にわたって非同期のクエリメソッドを追加しました。agetacreateasaveadeleteacountaexistsaaggregate、そして async for によるイテレーションです。これらは async に適したインターフェースを提供しますが、2026 年に至っても残る重要な注意点があります。その下にあるデータベース層はネイティブに非同期ではない、という点です。psycopg3 を使っていても、Django はクエリを threadpool 内で実行し、それを async ラッパーを通して公開します。したがって async ORM は使い勝手を改善し loop のブロックを回避しますが、真のエンドツーエンドな非同期データベース I/O を提供するわけではありません。

最も一般的な失敗パターンは、async view の中で同期の ORM メソッドを直接呼び出すことです。Django は async コンテキストを検知して拒否し、SynchronousOnlyOperation を送出します。これは、ブロッキング呼び出しが、その worker を共有する他のすべてのリクエストの event loop を凍結させるのを防ぐためです。

SynchronousOnlyOperation は仕様である

この例外はバグではなくガードレールです。これを黙らせるために DJANGO_ALLOW_ASYNC_UNSAFE=true に手を出すと、async が排除しようとしていたブロッキング動作をそのまま復活させてしまいます。正しい対処は、async の ORM メソッド(get の代わりに aget)を使うか、async の代替が存在しないコードに対して明示的に sync_to_async でラップすることです。

同期版のメソッドを async のクエリメソッドに置き換えれば、async view は素直に読めるようになります。以下のパターンは、event loop を一度も離れることなく行数を数え、先頭 10 件のレコードを取り出します。

python
# views.py
from django.http import JsonResponse
from .models import Article

async def latest_articles(request):
    # acount() と async イテレーションは ORM をラップした async セーフなラッパーです。
    count = await Article.objects.acount()
    titles = []
    async for article in Article.objects.order_by("-created_at")[:10]:
        titles.append(article.title)
    return JsonResponse({"count": count, "titles": titles})

コードの中には async の対応版が存在しないものもあります。同期呼び出ししか提供しないサードパーティ SDK や、書き換える価値のない密に連なった ORM の traversal などです。それらを asgiref ライブラリsync_to_async でラップすると、処理を thread に退避させ、loop の応答性を保てます。thread_sensitive=True 引数が安全なデフォルトなのは、ラップされたすべての呼び出しを 1 つの共有 executor thread に振り向けるからです。これにより、呼び出しが任意の thread に分散していたら壊れてしまうデータベース接続とトランザクションの状態が保たれます。

python
# views.py
from asgiref.sync import sync_to_async
from .services import build_report  # 同期関数

async def report_view(request):
    # thread_sensitive=True はラップされた呼び出しを 1 つの共有 thread 上に保ち、
    # 境界をまたいでも接続とトランザクションの状態を維持します。
    report = await sync_to_async(build_report, thread_sensitive=True)(request.user.id)
    return JsonResponse(report)

Django の async 性能: 並行性が勝つとき

Django の async 性能は、生の速度ではなく I/O のオーバーラップに関する話です。最も分かりやすい効果は、独立したネットワーク呼び出しを asyncio.gather で並列に展開する場合に得られます。各 200ms かかる 3 つの外部リクエストは、逐次実行するとおよそ 600ms かかりますが、並行実行すると約 200ms で完了します。await が同じ loop 上でオーバーラップするためです。

python
# views.py
import asyncio
import httpx
from django.http import JsonResponse

async def aggregate(request):
    async with httpx.AsyncClient(timeout=5.0) as client:
        # 3 つの独立した呼び出しを、逐次ではなく並行に実行します。
        weather, prices, news = await asyncio.gather(
            client.get("https://api.example.com/weather"),
            client.get("https://api.example.com/prices"),
            client.get("https://api.example.com/news"),
        )
    return JsonResponse({
        "weather": weather.json(),
        "prices": prices.json(),
        "news": news.json(),
    })

逆のケースも同じくらい重要です。単一の高速なローカルクエリを実行する view は async から何も得られず、むしろ損をすることが多くあります。すべてのリクエストが、event loop のスケジューリングに加えて、同期データベースドライバへの threadpool ホップのコストを支払うことになるからです。async はさらに、あらゆる sync/async の境界で、より見えにくいコストも課します。同期と非同期の middleware を混在させると、Django は遷移のたびに loop と thread の間を切り替えざるを得ず、1 レスポンスあたり何度もその境界をまたぐリクエストは実際のレイテンシを積み重ねていきます。middleware スタックを一様に非同期、あるいは一様に同期に保てば、こうした切り替えはなくなります。

この判断は、モダンな構文への好みではなく、ワークロードの形状に帰着します。

ワークロード推奨されるデフォルト
リクエストごとに複数の遅い外部 API 呼び出しasyncio.gather を使った async view
長時間維持されるストリーミングや Server-Sent Eventsasync generator を使った async view
単一の高速なデータベースクエリ、CPU 負荷が軽い同期 view、Gunicorn の worker を増やす
CPU バウンドなレンダリングや計算同期 view、タスクキューに退避する

本当に長時間実行されるジョブに対しては、async view はまったく適さないツールです。数分におよぶ処理のためにリクエストを開いたままにすることは、並行性モデルに関係なく接続を浪費します。そうした処理は background worker に任せるべきであり、このトレードオフは Django における Celery の非同期タスクのガイドで掘り下げています。

採用は直感ではなく計測に従うべきです。async を正当化する誠実な方法は、Locust や k6 のようなツールで対象のエンドポイントを現実的な並行度のもとで負荷試験し、N 個の worker 上の同期版と、同じハードウェア上の async 版を比較することです。高レイテンシな単一の外部呼び出しが支配的なエンドポイントは、async に有利な大きな差を示します。高速なクエリが支配的なエンドポイントは、threadpool ホップを勘定に入れると async がわずかに劣る結果になることがしばしばあります。まずリクエストをプロファイルすれば、そもそもボトルネックが I/O なのかどうかも分かります。というのも、インデックスの無いクエリのせいで遅くなっている view に対して async は何の効果もないからです。この問題は Django ORM クエリの最適化の解説で扱っています。

Djangoの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

2026 年に向けた Django async の面接質問

Django async の面接は、候補者がこのモデルを理解しているのか、それとも単にキーワードを知っているだけなのかを見極めます。以下の質問は、2026 年のシニア面接で実際に問われる内容を反映しており、それぞれに経験豊富なエンジニアが返す回答を添えてあります。

async view が ASGI ではなく WSGI 上で動くと何が変わりますか。 WSGI 上では、Django は coroutine を async_to_sync でラップし、リクエスト thread 上で完了まで実行します。そのため並行性の恩恵はゼロで、より遅い同期 view として振る舞います。worker が await の間に他のリクエストを処理できるのは、event loop を動かす ASGI server 上だけです。

なぜ async view の中で Article.objects.get(pk=1)SynchronousOnlyOperation を送出するのですか。 同期の ORM 呼び出しは event loop をブロックし、その worker 上の他のすべてのリクエストを停滞させてしまうためです。Django は async コンテキストを検知して拒否します。対処法は async メソッドの await Article.objects.aget(pk=1) を使うか、async の代替が無いコードには sync_to_async を使うことです。

Django の async ORM は本当にノンブロッキングなのですか。 いいえ。async メソッドは使い勝手を高めるラッパーにすぎません。データベースドライバがエンドツーエンドのネイティブな非同期実行に統合されていないため、根底にあるクエリは依然として threadpool 内で実行されます。その利点は loop を空けておくことであり、ドライバレベルでの並列なデータベース I/O ではありません。

Celery のようなタスクキューではなく async view を選ぶべきなのはどんなときですか。 async view が適しているのは、レスポンスの前に完了しなければならないリクエストスコープの I/O 並行処理です。たとえば複数の API を集約する場合などです。リクエストより長く生き延びる処理、リトライを必要とする処理、あるいは数分間実行される処理は、Celery や Django の background tasks に任せるべきです。そのために HTTP 接続を開いたままにすると server のリソースを浪費するからです。

同期と非同期の middleware を混在させるコストは何ですか。 同期コンポーネントと非同期コンポーネントの間の各遷移で、Django は sync_to_async または async_to_sync を介して event loop と thread の間をホップせざるを得ません。その境界を繰り返しまたぐリクエストは累積的なオーバーヘッドを支払うため、一様な middleware スタックの方が、混在したものより高いパフォーマンスを発揮します。

まとめ

  • async view が並行性をもたらすのは ASGI server 上に限られます。同じ view でも WSGI 上では async_to_sync を通して同期的に実行されます。
  • Uvicorn は Gunicorn 配下で uvicorn-worker パッケージを介してデプロイし、最大限のスループットが必要なら Granian を選びます。そして ASGI のセットアップは明示的なデプロイ工程として扱います。
  • リクエストが複数の遅い外部 I/O 呼び出しを待つ場合は async view を選びます。高速なクエリや CPU バウンドなエンドポイントは、worker を増やして同期のままにします。
  • async view の中では agetacreateasync for を使います。ただし ORM がネイティブな非同期ではなく、依然として threadpool でクエリを実行することは忘れないようにします。
  • SynchronousOnlyOperation はガードレールとして扱います。async メソッドや sync_to_async(thread_sensitive=True) で対処し、チェックを無効化してはいけません。
  • middleware スタックは一様に同期または非同期に保ち、境界をまたぐたびに thread 切り替えのペナルティを支払うことを避けます。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

今日のチャレンジ

Django のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年7月7日 更新

タグ

#django
#async
#asgi
#python
#performance

共有

関連記事