Hello Donald, all,

Some thoughts inline below.

> On 06 May 2016, at 18:11, Donald Stufft <don...@stufft.io> wrote:
> 
> For an example, in traditional HTTP servers where you have an open connection
> associated with whatever view code you're running whenever the client
> disconnects you're given a few options of what you can do, but the most common
> option in my experience is that once the connection has been lost the HTTP
> server cancels the execution of whatever view code it had been running [1].
> This allows a single process to serve more by shedding the load of connections
> that have since been disconnected for some reason, however in ASGI since
> there's no way to remove an item from the queue or cancel it once it has begun
> to be processed by a worker proccess you lose out on this ability to shed the
> load of processing a request once it has already been scheduled.

In theory this effect is possible. However I don't think it will make a
measurable difference in practice. A Python server will usually process
requests quickly and push the response to a reverse-proxy. It should have
finished to process the request by the time it's reasonable to assume the
client has timed-out.

This would only be a problem when serving extremely large responses in Python,
which is widely documented as a performance anti-pattern that must be avoided
at all costs. So if this effect happens, you have far worse problems :-)


> This additional complexity incurred by the message bus also ends up requiring
> additional complexity layered onto ASGI to try and re-invent some of the
> "natural" features of TCP and/or HTTP (or whatever the underlying protocol 
> is).
> An example of this would be the ``order`` keyword in the WebSocket spec,
> something that isn't required and just naturally happens whenever you're
> directly connected to a websocket because the ``order`` is just whatever bytes
> come in off the wire.

I'm somewhat concerned by this risk. Out-of-order processing of messages
coming from a single connection could cause surprising bugs. This is likely one
of the big tradeoffs of the async-to-sync conversion channels operates. I
assume it will have to be documented.

Could someone confirm that this doesn't happen for regular HTTP/1.1 requests?
I suppose channels encodes each HTTP/1.1 request as a single message.

Note that out of order processing is already possible without channels e.g.
due to network latency or high load on a worker.

The design of channels seems similar to HTTP/2 — a bunch of messages sent in
either direction with no pretense to synchronize communications. This is a
scary model but I guess we'll have to live with it anyway...


> Anytime you add a message bus you need to make a few trade offs, the 
> particular
> trade off that ASGI made is that it should prefer "at most once" delivery of
> messages and low latency to guaranteed delivery.

That’s already what happens today, especially on mobile connections. Many
requests or responses don’t get delivered. And it isn’t even a trade-off
against speed.


> This choice is likely one of
> the sanest ones you can make in regards to which trade offs you make for the
> design of ASGI, but in that trade off you end up with new problems that don't
> exist otherwise. For example, HTTP/1 has the concept of pipelining which 
> allows
> you to make several HTTP requests on a single HTTP connection without waiting
> for the responses before sending each one. Given the nature of ASGI it would 
> be
> very difficult to actually support this feature without either violating the
> RFC or forcing either Daphne or the queue to buffer potentially huge responses
> while it waits for another request that came before it to be finished whereas
> again you get this for free using either async IO (you just don't await the
> result of that second request until the first request has been processed) or
> with WSGI if you're using generators (you just don't iterate over the result
> until you're ready for it).

In this case, daphne forwarding to channels seems to be exactly in the same
position than, say, nginx forwarding to gunicorn. At worst, daphne can just
wait until a response is sent before passing the next request in the pipeline
to channels. At best, it can be smarter.

Besides I think pipelining is primarily targeted at static content which
shouldn't be served through Django in general.

Does anyone know if HTTP/2 allows sending responses out of order? This would
make sub-optimal handling of HTTP/1.1 pipelining less of a concern going
forwards. We could live with a less efficient implementation.

Virtually nothing done with Django returns a generator, except pathological
cases that should really be implemented differently (says the guy who wrote
StreamingHttpResponse and never actually used it). So I’m not exceedingly
concerned about this use case. It should work, though, even if it’s slow.


> I believe the introduction of a message bus here makes things inherently more
> fragile. In order to reasonable serve web sockets you're now talking about a
> total of 3 different processes that need to be run (Daphne, Redis, and Django)
> each that will exhibit it's own failure conditions and introduces additional
> points of failure. Now this in itself isn't the worst thing because that's
> often times unavoidable anytime you scale beyond a single process, but ASGI
> adds that complication much sooner than more traditional solutions do.

Yes, that’s my biggest concern with channels. However I haven’t seen anyone
suggesting fewer than three systems:

- frontend + queue + worker (e.g. channels)
- regular HTTP + websockets + pub/sub (e.g. what Mark Lavin described)

I share Mark’s concerns about handling short- and long-lived connections in
the same process. Channels solves this elegantly by converting long-lived
connections to a series of events to handle.


> So what sort of solution would I personally advocate had I the time or energy
> to do so? I would look towards what sort of pure Python API (like WSGI itself)
> could be added to allow a web server to pass websockets down into Django.

This sounds a lot like the proof-of-concept I demonstrated at DjangoCon US
2013, eventually reaching the conclusion that this wasn't a workable model,
mainly due to:

- the impossibility of mixing async and sync code in Python, because of the
  explicit nature of async code written on top of asyncio (which I still
  believe is the right choice even though it's a problem for Django).

- the great difficulty of implementing the ORM's APIs on top of an async
  solution (although I came up with new ideas since then; also Amber Brown
  showed an interesting proof-of-concept on top of Twisted at Django under
  the Hood 2015).

I think it's important to keep a straightforward WSGI backend in case we crack
this problem and build an async story that depends on asyncio after dropping
support for Python 2.

I don't think merging channels as it currently stands hinders this possibility
in any way, on the contrary. The more Django is used for serving HTTP/2 and
websockets, the more we can learn.


Sorry Andrew, that was yet another novel to read… I hope it helps anyway…

-- 
Aymeric.

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/E1544618-4CC7-4392-9798-6BDB7192A418%40polytechnique.org.
For more options, visit https://groups.google.com/d/optout.

Reply via email to