Skip to content

Conversation

andrewgodwin
Copy link
Member

@andrewgodwin andrewgodwin commented Apr 6, 2016

Adds Channels into Django as django.channels, including tests and documentation ported from the standalone channels repository.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a select_for_update() should be added here? As is, multiple consumers are likely to fetch the same message. I'm not sure what guarantees that the same message isn't fetched multiple times.

We could make the db channel implementation better (skip locked in PostgreSQL for example), but it is probably enough to just make it slow but correct.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My impression was that transaction.atomic() would be enough to provide the isolation here (without it, I was definitely seeing multiple-fetch, and with it, that's never happened).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, that isn't enough. There is nothing blocking selecting the same message multiple times. On PostgreSQL you can test with begin; select id from test_table order by id limit 1; delete from test_table where id = <fetched_id>; in multiple psql sessions. When the first session commits, the second session unblocks and deletes nothing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmh, if you are using REPEATABLE READ or SERIALIZABLE isolation, then the second session is likely going to fail instead of returning nothing. I believe select_for_update is still the right thing to do.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed; added it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this, and on PostgreSQL if using REPEATABLE READ with select_for_update() you'll get serialization error on the select instead of delete (which is slightly better). If using READ COMMITTED, then you'll get the next row in the second session which is actually the wanted behavior.

It is possible (though unlikely) that even MySQL behaves sanely with select_for_update().

@akaariai
Copy link
Member

akaariai commented Apr 6, 2016

I quickly checked the db layer, and I think it is good enough for development after a couple of changes.

I've been wondering if it would be possible to have a bypass of the channel layer for huge responses. Using the channels when the reader of the message is fixed seems like wasted resources. Some ideas are:

  • Directly connect to Daphne and stream the response over HTTP.
  • Send back a message stating "you can read the response from this URL".
    This is premature optimization without benchmark results... Any ETA for those?

@andrewgodwin
Copy link
Member Author

How would you bypass, exactly? As the Django worker, you have no idea where the protocol server is on the network (and if your opsec is done right, you can't talk to it unless you explicitly open up the ports), and the two programs don't share a filesystem or local shared memory necessarily.

In my limited testing, the in-memory channel actually outperformed WSGI (mostly due to larger chunking and because it interleaved the processes better). I haven't done any benchmarks of the database layer, as it's not intended for any serious use, and the redis layer I've only done latency benchmarks of (where it tops out at around 150 req/sec with just one Daphne and one Django process)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this version check.

@akaariai
Copy link
Member

akaariai commented Apr 6, 2016

Yes, if the protocol server can't talk to the worker server directly, then there can be no bypass.

The interesting case is how will redis perform with large responses.

@andrewgodwin
Copy link
Member Author

Yes, this approach will never be as good as WSGI for large responses, but then neither of them are as good as raw C code either. My initial tests showed throughput to be pretty good, but I haven't get tried shoving a few gigabytes down several responses at once and seeing what happens (I suspect Redis might run out of memory and just dump messages depending on how it's configured)

@akaariai
Copy link
Member

akaariai commented Apr 6, 2016

It would be really nice to have some docs about what to do when you need large responses.

It might be that a message containing "read response from this URL" approach might actually work. For instance, you could stream large static files that way if the protocol server has access to the static files. Or, you could have a proxy server in between the worker and the protocol server dedicated to streaming data between the servers.

@andrewgodwin
Copy link
Member Author

It's certainly possible you could have nginx do the thing where you send a header in the response and it uses that as a path to the file to actually stream.

I don't see a lot changing around our general advice though - don't stream huge files from Python, do it from a proxy server in front of gunicorn/uwsgi/daphne/etc. Small static files from things like Whitenoise are fine, we just don't want (and never have wanted) people reading through several hundred megabytes of video when other software/CDNs are much better at that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

]},
install_requires=[
'asgiref>=0.10',
'daphne>=0.11',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of these packages have little to no test coverage. It concerns me that they are going to be core dependencies to Django.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asgiref has extensive test coverage on the parts we are using (in fact, one of its main jobs is to provide the test confirmance suite). Daphne is less so - see my post to django-developers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this discussion is better held on django-developers.


Default: Value of :setting:`SESSION_ENGINE`

Allows you to specify an alternative session backend to use for per-channel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should note that django.contrib.sessions.backends.signed_cookies cannot be used. Related that if SESSION_ENGINE is using django.contrib.sessions.backends.signed_cookies then this must be set to use a different engine. Not sure if this is the right place for that note.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted.

raise CommandError('You must set settings.ALLOWED_HOSTS if DEBUG is False.')

self.use_ipv6 = options['use_ipv6']
self.verbosity = options.get("verbosity", 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please cleanup options usage as done in 1845bc1 throughout the new management commands and in the edits.

@andrewgodwin
Copy link
Member Author

As per my post to django-developers (https://groups.google.com/forum/#!topic/django-developers/QRd4v7OErT8), this patch is withdrawn for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants