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.