Django under the Hood - Channels

Speaker: Andrew Godwin is known mostly for Django migrations. He works for eventbrite, and started Channels.

Links: Channels docs and Channels examples

The Problem

  • There are now WebSockets, and other things that are not just Request/Response. Channels tries to solve that, since Django is (as is) not exactly suited to asynchronous work.
  • Since Python is synchronous (traditionally), Django is, too.
  • Synchronous code is much easier to write and debug and understand and reason about.
  • Single-process async is not enough, distributed systems rock!
  • To offset the problems with reasoning about async needs to be solved by proven design patterns.
  • Needs to be easy to reason about, easy to understand, and easy to explain

Design Pattern: Loose Coupling via Message Bus

  • Do not be tied too much to other elements: to Django, to WebSockets.
    • Implemented: HTTP, WebSocket
    • Drafted: IRC, Email, Slack
    • Do not do: Minecraft, Mainframe Terminal
  • Well-defined, minimal interfaces that can be used in all places
  • Very proven: Message Bus
  • ASGI: specification of how to talk to the message bus, defining five interface methods
    • nonblocking send
    • blocking receive
    • add to group
    • discard from group
    • send to group
  • Messages are: JSON-compatible, dictionary-based messages onto named channels

Implementation overview

  • WebSocket: upgrade from HTTP
    • Connect and ask for upgrade
    • Server either accepts or rejects
    • Send and receive whenever you want, no ordering
    • Disconnect
  • Server handles sockets separately from Django project
  • Structure: Shared bus layer, receiving incoming, and forwarding to Django or Channels
  • Mark channels that need to go to a specific group
  • Works as shared-nothing: workers don't know about each other, hence the reply_channel
  • Message Bus tradeoffs
    • At-most-once: do not guarantee delivery
    • FIFO: messages are handled ordered
    • Backpressure via capacity: reject connections over capacity
    • Not sticky: you might go to different servers on every message
  • Ordering via an order key on receive messages, per-group
  • Changed to explicit connecting for a clean start for ordering

Parts

  • Channels: Django integration of ASGI
  • asgi-redis: for shared backends
  • asgi-ipc: local memory backend
  • asgiref: common code
  • Daphne: using twisted (etc), handle connections and negotiations, can handle either everything or only WebSockets

Implementation details

  • Consumers
    • Consumers look like views
    • Interfaces againt message instead of request
    • Responses are not a return; responses can be sent at any time in the consumer
    • Also class-based consumers available
  • Routing
    • Like Django: regex-based matches
  • Sessions: Channels Session:
    • Contain all the relevant meta information (eg ordering)
    • Generate sessions from a message's reply channel name
    • uses ConsumeLater exception to postpone delivering a message
  • No middleware
    • Middleware is very request/response
    • New middleware half works
    • Decorators are more discoverable and explicit than middleware
  • View/HTTP Django is still there
    • Django as is: is now a single consumer, the ViewConsumer, inside Channels
    • Send a message, receive response, send response
    • Means you can drop down to Django views
  • Signals and commands
    • Runserver works (overridden to include Daphne)
    • New signals for lifecycle handling
    • Staticfiles configured for development are injected

Future of Channels

  • Generalised async communication (think CSP)
  • Might work on a diverse set of async stuff
    • MQTT
    • Microservice communication
    • Separate things by CPU
    • Mix Python versions
    • Mix async and sync
  • Diversity of implementations
    • More web servers (at least a second one to Daphne)
    • More channel layers
  • More optimization
    • Better bulk sends
    • Less network traffic on receive
    • Channels was right first, will be fast second
  • More maintainers
    • More viewpoints
    • More time
  • 1.0 is coming really soon

Q&A

  • Use cases
    • Long polling
    • Live data
    • Might implement sock.js in the future.
  • Getting involved
    • A getting started
    • Concepts documentation
    • Channels examples
    • Further docs
  • Why do URLs start with a slash?
    • This is not URL routing, it's message routing.
  • Is this applicable to the current Django DEP on routing?
    • Yup. There ought to be a "start with a slash" DEP.
  • Can you make sure that some requests are only handled by some systems?
    • Yup, you do that in the runworker command. You specify which requests should be handled.
  • Has HTTP over channels been load tested?
    • Came out a bit slower than gunicorn, doesn't handle many (>1k) connections well yet.
  • What are our profiling tools here?
    • HTTP benchmarking tools, there is one for WebSockets … other than that, nothing.
  • Within the spec, could there be an "at least once" mode?
    • You could do it, but all consumers and decorators would need to be rewritten, dealing with duplicates - where would we dedup? It's certainly possible, but it's a lot of work, not exactly feasible.
  • So many choices for transport layers, and frontend stuff - what do you recommend as default stack?
    • Personally, Daphne for WebSockets, WSGI for HTTP, route via nginx. Once we reach 1.0, we should be able to use Daphne for everything.