Django ChannelsはWebSocketを扱うライブラリだと説明されがちですが、実際は受け口・処理・配信という3つの層をDjangoに足す拡張です。channels 4.3系の動作条件、Consumerの選び方、Channel Layerの構成、本番で何が壊れるかを公式の記述に沿って整理します。
まとめ:Django Channelsを入れる判断とASGI構成の結論
先に結論を置きます。Channelsが要るのは、サーバーとブラウザが双方向にやり取りし、かつ接続を張りっぱなしにする必要があるときだけ。非同期のコードを書きたいだけなら不要です。Django本体の非同期ビューはWSGI下でも動くと公式が明記しています。
構成は3層で捉えると迷いません。ASGIの受け口(asgi.pyのProtocolTypeRouter)でHTTPとWebSocketを分岐させ、接続ごとの処理をConsumerが持ち、プロセスを跨ぐ配信をChannel Layerが担う。3つ目を軽視した構成が、開発機では動いて本番で無言のうちに壊れます。InMemoryChannelLayerは複数インスタンスでメッセージが落ちると公式が警告しているからです。
Consumerの選択は、公式が既定として同期版を勧めています。Django自体の全体像はDjangoとMVTパターンの入門解説を前提とし、本記事はHTTPの外側だけを扱います。
Django Channelsとは何か|WSGIの外側をASGIで受け直す拡張
Channelsの位置づけを、Djangoのどこに何が挿さるのかという形で先に固定します。
ChannelsがDjangoに足す層とProtocolTypeRouterの位置
公式の定義は「Channels is a project that takes Django and extends its abilities beyond HTTP – to handle WebSockets, chat protocols, IoT protocols, and more.」というもので、HTTPの向こう側へ広げるという表現が構造をそのまま表しています。
通常のDjangoはwsgi.pyのapplicationを入口に、リクエストを受けてレスポンスを返したら終わります。1往復で完結する前提。Channelsを入れると入口がasgi.pyへ移り、ProtocolTypeRouterがプロトコル種別を見て振り分けます。HTTPはDjangoのビュー層へ、WebSocketはURLRouterを経てConsumerへ。WSGIとASGIの差は、接続を保持したまま双方向に流せるか、HTTP以外のプロトコル型を扱えるかの2点で捉えると実装に落ちます。前提となる考え方は同期処理との違いから見た非同期処理の解説を参照してください。
動作条件はchannels 4.3系とDjango 4.2以上の組み合わせ
2026年8月時点で、channels の最新は 4.3.2(2025年11月リリース)。PyPIのメタデータ上の要件は Python 3.9以上、Django 4.2以上、asgiref 3.9.0以上です。4.3.0のリリースノートには「Python 3.9 is now the minimum supported version.」と書かれ、EOLを迎えた版のサポートを落としたことが記されています。周辺パッケージは channels_redis が 4.3.0(2025年7月)、daphne が 4.2.3(2026年7月)。いずれも下限の宣言でしかないため、Djangoの側を上げる運用なら、Channelsの対応リリースが出てから追随する順序になります。
導入の最小構成|インストールからasgi.pyのルーティングまで
導入手順は短いのですが、順序を間違えると起動しません。
channelsのdaphne付き導入とINSTALLED_APPSの並び順
公式が案内するインストールは次の形です。
python -m pip install -U "channels[daphne]"
角括弧の指定はextrasで、ASGIサーバーであるDaphne(4.0.0以上)を一緒に入れます。別のASGIサーバーを使う予定ならchannels単体でも構いません。
設定側の作業は2つ。INSTALLED_APPSの先頭に"daphne"を置くことと、ASGI_APPLICATIONを指定することです。先頭という指定には理由があり、公式は「Daphne will integrate itself into Django and take control of the runserver」と書いています。同じくrunserverを差し替える他のアプリと並ぶと、後ろに置いた側が負けます。
asgi.pyでProtocolTypeRouterとURLRouterを組む書き方
プロジェクト直下のasgi.pyを、HTTPとWebSocketの分岐点として書き換えます。
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
django_asgi_app = get_asgi_application()
import chat.routing
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AuthMiddlewareStack(
URLRouter(chat.routing.websocket_urlpatterns)
),
})
get_asgi_application()を先に呼んでからアプリ側のモジュールを取り込む順序に意味があります。Djangoのアプリ登録が済む前にモデルを参照すると、起動時に例外が出るためです。AuthMiddlewareStackを外すとscopeから認証情報が取れません。ルーティングと設定は次のとおり。
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),
]
# settings.py
ASGI_APPLICATION = "config.asgi.application"
runserverがDaphneに置き換わることで起きる副作用
ここが最初の落とし穴です。"daphne"を入れた時点で、開発サーバーはDjangoのものではなくDaphneになります。ローカルでWebSocketが疎通しても、本番の構成が同じとは限らない。本番がWSGIの入口を読んだままなら、接続はハンドシェイクの段階で失敗します。自動リロードやログ出力の挙動もDaphne側のものに変わる点にも注意してください。
本番配置の基礎はgunicornとnginxを使うDjangoのデプロイ手順で整理しているので、ASGIへ寄せる前後の差分として読み比べてください。
Consumerの設計|同期と非同期の選び方とORM呼び出しの境界
Consumerは接続1本ごとに割り当てられる処理系です。設計上の判断はほぼ1つ、同期で書くか非同期で書くかに集約されます。
公式が既定で勧めるのはSyncConsumerであるという事実
非同期のライブラリだから非同期で書くべき、と考えると足をすくわれます。公式の推奨はこうです。「We recommend that you write SyncConsumers by default, and only use AsyncConsumers in cases where you know you are doing something that would be improved by async handling (long-running tasks that could be done in parallel) and you are only using async-native libraries.」
SyncConsumerは「run your code synchronously in a threadpool」と説明されているとおり、スレッドプール上で同期的に動き、Django ORMもそのまま呼べます。対してAsyncConsumerはハンドラがすべてコルーチンでなければならず、同期関数を呼ぶとイベントループ全体を止めてしまう。並列にできる長時間処理があり、使うライブラリが非同期ネイティブで揃っている場合に限って非同期版を選びます。
WebsocketConsumer系4種の違いとJSONの扱い分け
継承するクラスは、生フレームを扱う系統とJSONを自動変換する系統が、それぞれ同期版と非同期版を持つ形で4つあります。
| クラス系統 | 入出力の扱い | 非同期版の名前 |
|---|---|---|
| WebsocketConsumer | テキストとバイナリの生フレーム | 先頭にAsyncを付けた同名 |
| JsonWebsocketConsumer | JSONへ自動で変換する | 先頭にAsyncを付けた同名 |
公式はAsyncWebsocketConsumerを「exact same methods and signature as WebsocketConsumer but everything is async」と説明しています。メソッド名が揃っているため、同期で書いてからAsyncJsonWebsocketConsumerなどへ移す判断も取れる。JSON版はreceive_jsonとsend_jsonでPythonのオブジェクトを直接やり取りできます。
connectとreceiveとdisconnectの流れとscopeの中身
ハンドラは3つ。connect()で接続を受理するか閉じるかを決め、receive()がフレームごとに呼ばれ、disconnect()で後始末をします。
from channels.generic.websocket import JsonWebsocketConsumer
from asgiref.sync import async_to_sync
class ChatConsumer(JsonWebsocketConsumer):
def connect(self):
self.room = self.scope["url_route"]["kwargs"]["room_name"]
if not self.scope["user"].is_authenticated:
self.close()
return
async_to_sync(self.channel_layer.group_add)(self.room, self.channel_name)
self.accept()
def receive_json(self, content):
async_to_sync(self.channel_layer.group_send)(
self.room, {"type": "chat.message", "text": content["text"]}
)
def chat_message(self, event):
self.send_json({"text": event["text"]})
def disconnect(self, code):
async_to_sync(self.channel_layer.group_discard)(self.room, self.channel_name)
公式はscopeを「contains a lot of the information you’d find on the request object in a Django view」と説明しています。パスやヘッダ、認証ミドルウェアを通した後の利用者オブジェクトがここに入る。認可の判定はconnect()で済ませます。group_sendで渡したtypeの値が、ドットをアンダースコアに置き換えたメソッド名に対応する規約も押さえてください。
ORMを呼ぶ位置とdatabase_sync_to_asyncの使いどころ
同期Consumerならモデルの呼び出しはそのまま書けます。非同期Consumerでは扱いが変わり、公式はDjango ORMを呼ぶ場合にdatabase_sync_to_asyncアダプタを使うよう指示しています。
Django本体の側にも非同期の口はあり、SQLを発行するQuerySetのメソッドにはa接頭辞の非同期版が用意されました。ただし公式は「Transactions do not yet work in async mode.」とも書いています。トランザクションでまとめたい処理は同期の関数として書き、sync_to_asyncで呼ぶ形が案内されている。複数テーブルの更新を非同期Consumerに直接書くのは避けてください。
Channel Layerの構成|InMemoryとRedisの違いと配信の仕組み
Channel Layerは、Consumer同士がメッセージを渡すための共有経路です。ここの選択が本番の可否を分けます。
InMemoryChannelLayerが本番で欠落を起こす理由
公式は用途を「テストまたはローカル開発目的」と限定したうえで警告しています。各プロセスが独立したレイヤーを持つためプロセス間通信ができず、マルチインスタンス環境ではデータ損失が発生する、と。
この壊れ方は例外を伴いません。ワーカーAに繋がっている利用者へ、ワーカーBが受け取ったメッセージを送ろうとしても、Bのメモリ上のグループにAの接続は存在しない。送信は成功したように見えて相手には届かず、単一プロセスでは完璧に動くため、本番で初めて表面化します。
RedisChannelLayerの設定とpubsub実装の位置づけ
設定はsettings.pyに辞書を1つ置くだけです。
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
channels_redis には実装が2つあります。READMEはRedisChannelLayerを「the original layer, and implements channel and group handling itself」、pubsubモジュール側のRedisPubSubChannelLayerについて「currently at Beta status, meaning it may be subject to breaking changes」と明記している。破壊的変更を許容できない案件では既定のcore側を選ぶのが妥当です。
既定値も押さえておきます。expiryが60秒、group_expiryが86400秒、capacityが100。メッセージが60秒で消える設計は、ここが永続キューではないことを示しています。取りこぼしてはいけないデータは、DBに書いてから通知だけを流す構造にしてください。
group_addとgroup_sendで送る単位とグループ名の制約
配信の単位はグループです。group_addでチャネル名を加え、group_sendでグループ全体へ送り、group_discardで外す。チャネル名は接続ごとに自動採番される識別子で、self.channel_nameで参照します。
名前には制約があり、公式は「ASCII英数字、ハイフン、ピリオドのみ許可。最大長100文字」と定めています。日本語の部屋名をそのままグループ名にすると弾かれるため、主キーを文字列化した形など規約に収まる命名を先に決めてください。
WSGIのままでよい場面とChannelsが必要になる場面の線引き
Channelsを入れる前に、入れずに済むかどうかを2つの観点で潰します。
Django本体の非同期ビューはWSGIの下でも動くという事実
非同期で書きたいからASGIへ移行する、という理由づけは公式の記述と噛み合いません。「Async views will still work under WSGI, but with a small per-request adaptation cost, and without the ability to have efficient long-running requests.」
続けて「Under a WSGI server, async views will run in their own, one-off event loop.」とあり、外部APIへの並行リクエストのような使い方はWSGIのままでも動くと明言されています。ASGIへ移す動機として挙げられているのは、遅いストリーミングやロングポーリングを使いたい場合。ASGIサーバー単体の役割はUvicornとASGIサーバーの解説で確認できます。
サーバーからブラウザへの片方向通信ならSSEで足りるという判断
2つ目の観点は通信の向きです。進捗表示、通知、ダッシュボードの数値更新といった用途は、サーバーからブラウザへ一方向に流れるだけの場合が大半。この形ならServer-Sent Eventsで済み、Channelsは要りません。
SSEは素のHTTP上で動くため、プロキシの設定変更も小さく収まります。ブラウザ側は再接続を自前で書かなくてもEventSourceが面倒を見てくれる。双方向が要るのか片方向で足りるのかは、SSEとWebSocketの違いと使い分けで切り分けてください。
Channelsが必要になるのは双方向と接続の保持が要るとき
逆に、Channelsが要ると判断できる形は明快です。ブラウザ側からも随時メッセージを送る必要があり(チャット、共同編集、対戦、遠隔操作)、かつ接続を張り続けたまま双方向にやり取りする。この2条件が揃ったときだけ。WebSocket自体の仕様はWebSocketの仕組みと運用判断の解説へ寄せてください。
本番でのスケール|プロセス分散と接続数とRedis依存の注意点
台数を増やした瞬間に効いてくる論点を3つ挙げます。
プロセスを跨ぐ通知はChannel Layerだけを経路にする
ワーカーを複数立てた時点で、ある接続と別の接続は同じプロセスにいるとは限りません。HTTPを処理するプロセスと、WebSocketを保持するプロセスも別物です。ビューやタスクから通知を出す実装は、必ずChannel Layer経由になります。
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
layer = get_channel_layer()
async_to_sync(layer.group_send)(
"room.42", {"type": "chat.message", "text": "更新がありました"}
)
裏を返せば、Channel Layerが止まると通知系がすべて止まります。channels_redis のREADMEは両レイヤーとも単一サーバー構成と分割構成に対応すると書いており、Sentinelを使う接続も受け付ける。可用性はこの段で決めます。キャッシュ用のRedisと同居させるかも判断が要り、負荷の性質が違うため規模が読めないうちは分けておくほうが楽です。
nginxやロードバランサでUpgradeとタイムアウトを通す
前段のプロキシがハンドシェイクを通さない設定だと、アプリ側が正しくても接続できません。nginxならproxy_http_version 1.1と、UpgradeおよびConnectionヘッダの引き回しが要ります。
もう1つがアイドルタイムアウトです。クラウドのロードバランサは既定で数十秒から数分の無通信で接続を切ります。ハートビートを流すか、タイムアウト値を延ばすかの手当てが必要。この症状は「ローカルでは切れないのに本番だけ切れる」という形で出ます。
HTTPとWebSocketをプロセス単位で分ける構成の考え方
公式のデプロイ文書は、Daphneで全リクエストを受ける構成と、安定性を優先してHTTPをWSGIサーバーに残しWSGIで扱えないものだけをDaphneに任せる構成の2つを示しています。
後者は既存のWSGI構成をそのまま残し、WebSocket用のパス配下だけをASGI側へ振り分ける形です。既存アプリへ後付けするならこちらが現実的で、HTTP側の挙動を変えずに済みます。接続を保持するプロセスはスケールの単位も監視の指標も違うため、分けておくと台数の増減も別々に判断できます。
Django Channelsを採用してよい条件と見送る3つの場面
判断を条件付きで言い切ります。
Channelsを採用してよい3条件を実装コストから言い切る
1つ目、ブラウザ側からの送信が随時発生し、双方向の即時性が機能要件に入っていること。2つ目、既存資産がDjangoにあり、認証・モデル・権限を流用したいこと。別言語でWebSocketサーバーを立てて認証を二重に持つより、Channelsで同居させるほうが総量は小さくなります。3つ目、Redisを含む常駐基盤を運用に載せられること。Channel Layerの運用を引き受けられない体制なら、外部のリアルタイム基盤を使う判断のほうが筋が通ります。
Channelsを見送るべき3つの場面と代わりに選ぶ手段の指針
1つ目、通信がサーバーからの片方向で足りる場面。SSEかポーリングで済ませ、ASGIへの移行そのものを見送ります。2つ目、非同期で書きたいだけの場面。Django本体の非同期ビューはWSGI下でも動くため不要です。
3つ目、常時接続の数が読めず、まずは検証したい段階。接続数に比例して負荷が積み上がるため、規模の見積もりが立たないうちに自前で持つと運用が重くなります。マネージドの基盤で機能を検証し、内製へ寄せる判断を後段に回してください。
Channelsの開発を外注するとき仕様書で決めておく4項目
外部へ委ねる場合、仕様書に落としておく項目は4つです。(1)Channel Layerのバックエンドと冗長化の方針(InMemoryでの納品を許さない旨を明記)、(2)想定同時接続数とワーカー構成の根拠、(3)切断と再接続の扱い、(4)前段プロキシのタイムアウトとハートビートの間隔。
この4点が曖昧なまま進むと、受け入れ試験は通るのに本番で「たまに届かない」という報告が続きます。既存システムへリアルタイム通信を後付けするなら、API開発・システム連携で構成の妥当性から詰めてください。
よくある質問
Django ChannelsとWebSocketは何が違いますか?
WebSocketは通信プロトコルの名前で、Django ChannelsはそれをDjangoから扱えるようにする実装です。公式が「chat protocols, IoT protocols, and more」と書いているとおり、ASGIで表現できるプロトコル全般の受け口を提供します。
Channelsを入れると既存のHTTPリクエストは遅くなりますか?
ProtocolTypeRouterのHTTP側にget_asgi_application()を割り当てている限り、HTTPは従来のDjangoの処理系を通ります。ただし実行環境がWSGIからASGIへ変わるため性能特性は同一ではなく、安定性を優先するならHTTPをWSGIサーバーに残す構成も取れます。
Channel LayerにRedis以外は使えますか?
公式が本番向けとして案内しているのは channels_redis です。InMemoryChannelLayerはテストとローカル開発に限ると明記され、マルチインスタンスではデータ損失が起きると警告されています。単一プロセスで動かすと決め切れる用途以外では選べません。
runserverでWebSocketが動くのに本番で動かないのはなぜですか?
INSTALLED_APPSの先頭に"daphne"を置くと、開発サーバーがDaphneに置き換わるためです。本番がWSGIの入口を読んだままなら、WebSocketの経路は存在せずハンドシェイクが失敗します。前段プロキシでUpgradeヘッダが落ちている場合も同じ症状です。
DaphneとUvicornはどちらを使うべきですか?
Channelsの公式インストール手順はdaphne付きのextrasを案内しており、既定はDaphneです。デプロイ文書には代替のASGIサーバーへの言及もあり、Uvicornを選ぶ構成も取れます。まず動かす段階ではDaphneに寄せるほうが情報量の面で楽です。