Django Channels and multi-tenant will this be a world of hurt?

1,172 views
Skip to first unread message

Filbert

unread,
Nov 24, 2017, 6:09:42 PM11/24/17
to Django users
Running multi-tenant site using a fork of Django tenant schemas with tens of web servers and thousands of tenants....

Piloting a project to implement Channels for real-time notifications, etc.

I want to confirm these assumptions:

1. Channels really has no support for multi-tenant, I will have to roll my own.
2. Since uWSGI is serving us well and (at this point) I wouldn't trust Daphne to serve HTTP, I've got split paths in NGinx for uWSGI and ASGI.
3. We are running RabbitMQ, so we have to cluster it and implement channels using asgi_rabbitmq (Redis would just add yet another moving part)
4. Plan on significant additional resource requirements on the web server and serious scaling challenges.

Are their any other non-Channels options, or is it the really the only game in town?

Thanks.



Andrew Godwin

unread,
Nov 24, 2017, 7:10:29 PM11/24/17
to django...@googlegroups.com
Your assumptions all seem correct. Also consider that the security model of WebSockets is... interesting, so securing a multi-tenant setup is going to require a bit more work than you would merely doing the same for HTTP requests.

There are other non-Channels options around if you want to look into them, but I suspect they'll all have similar architectural challenges.

Andrew

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/aae2725b-d873-40fd-ae09-d1668ab9e727%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Etienne Robillard

unread,
Nov 25, 2017, 4:35:28 AM11/25/17
to Andrew Godwin, django-...@googlegroups.com, django...@googlegroups.com

Hi,

What is a multi-tenant site?

how can i build a multi-tenant django app for django-hotsauce and asyncio without django-channels?

Thank you,

Etienne
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.

To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.

For more options, visit https://groups.google.com/d/optout.

Filbert

unread,
Nov 25, 2017, 8:17:54 PM11/25/17
to Django users
Andrew,
Thanks for the response. Seeing that I am keeping uWSGI for the non-websocket paths, any heartache with me starting daphne and the consumers using uWSGI's attach-daemon? I do this with celery and it is a convenient way to have everything for the App managed as a unit via a single /etc/init (upstart) conf file.
Thanks. 


On Friday, November 24, 2017 at 7:10:29 PM UTC-5, Andrew Godwin wrote:
Your assumptions all seem correct. Also consider that the security model of WebSockets is... interesting, so securing a multi-tenant setup is going to require a bit more work than you would merely doing the same for HTTP requests.

There are other non-Channels options around if you want to look into them, but I suspect they'll all have similar architectural challenges.

Andrew
On Fri, Nov 24, 2017 at 3:09 PM, Filbert <tim...@gmail.com> wrote:
Running multi-tenant site using a fork of Django tenant schemas with tens of web servers and thousands of tenants....

Piloting a project to implement Channels for real-time notifications, etc.

I want to confirm these assumptions:

1. Channels really has no support for multi-tenant, I will have to roll my own.
2. Since uWSGI is serving us well and (at this point) I wouldn't trust Daphne to serve HTTP, I've got split paths in NGinx for uWSGI and ASGI.
3. We are running RabbitMQ, so we have to cluster it and implement channels using asgi_rabbitmq (Redis would just add yet another moving part)
4. Plan on significant additional resource requirements on the web server and serious scaling challenges.

Are their any other non-Channels options, or is it the really the only game in town?

Thanks.



--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.

Andrew Godwin

unread,
Nov 26, 2017, 9:58:58 AM11/26/17
to django...@googlegroups.com
I've never used attach-daemon so I can't help you there I'm afraid. As long as it does sensible process-management things it's probably alright?

Andrew

To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.

To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.

Filbert

unread,
Nov 27, 2017, 5:58:51 PM11/27/17
to Django users
Thanks again. One last question, in order to "multi-tenant-ify" Channels I think the challenge will be trickle the websocket "orgin" into the consumer from Daphne somehow as it seems there is no way to know in a consumer which tenant (unique domain) the message originated from. Without the domain you can't establish the Postgres schema, without the schema you have no tenant :-)

Please confirm this would be the approach or whether I am wrong-headed here.
Thanks.

Andrew Godwin

unread,
Nov 27, 2017, 6:04:48 PM11/27/17
to django...@googlegroups.com
That would be correct (though please be aware Origin can be faked like host headers, so don't use it as the sole point of security).

Is it not available via the headers list in the connect message?

Andrew

To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.

To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.

Filbert

unread,
Nov 27, 2017, 8:22:10 PM11/27/17
to Django users
Sorry, yeah dig before I post :-|  Can create a class-based consumer and use the message->headers->host. I'll get chest deep before ask another question.

N Muchai

unread,
Jan 10, 2019, 7:22:40 AM1/10/19
to Django users
@Filbert,

Am in the same exact spot using Django Channels with django-tenants. Did you find a way to identify the tenant?

Thanks.

Lorenzo Peña

unread,
Jan 15, 2019, 5:42:50 PM1/15/19
to Django users


Hi Filbert, everyone!

django-tenant-schemas has a way to infer the tenant from the request, and you could apply the same logic to extend Channel's scope with the tenant. With the tenant on the scope you can incrementally add in-channels multi-tenant logic. Not sure if this helps as you mention other multiple elements in your setup.
 

 

Ahmed Ishtiaque

unread,
Feb 24, 2019, 7:13:30 PM2/24/19
to Django users
@N Muchai, 

I don't know if you've found a workaround to this, but here's mine if people are interested in the future. 

I just made a custom ASGI application that adds the tenant schema name (provided your URLs are in the form *.domain.com, where * represents the schema name) and whether the project is multitenant or not. I haven't thought much about whether the boolean for multitenancy identifier helps, so feel free to remove it in your implementation. I just stacked this application in my project's `routing.py` file. Here's the code I have at the moment: 

My Middleware:
class MTSchemaMiddleware:
    def __init__(self, inner):
        self.inner = inner

    def __call__(self, scope):
        if "headers" not in scope:
            raise ValueError(
                "MTSchemaMiddleware was passed a scope that did not have a headers key "
                + "(make sure it is only passed HTTP or WebSocket connections)"
            )

        for key, value in scope.get('headers', []):
            if key == b'host':
                schema_name = value.decode('ascii').split('.')[0]
                break
        else:
            raise ValueError(
                "The headers key in the scope is invalid. "
                + "(make sure it is passed valid HTTP or WebSocket connections)"
            )
        return self.inner(
            dict(scope, schema_name=schema_name, multitenant=True)
        )

My `routing.py`:

import MTSchemaMiddleware # from wherever your Middleware class resides
application = ProtocolTypeRouter({
'websocket': <your stack>(
            MTSchemaMiddleware(
URLRouter(
chat.routing.websocket_urlpatterns
)
)
            )
})

After you do this, you will be able to access the `schema_name` and `multitenant` variables inside your consumer's `scope` like this: `schema_name = scope['schema_name']` or `multitenant = scope[`multitenant`]`. 

Hope this helps someone! 

Best,
Ahmed

Sebastián García

unread,
Jan 27, 2024, 6:10:53 AMJan 27
to Django users
Hi, I'm reviving this old thread... do you know of any other solution to the one proposed by @Ahmed Ishtiaque

Thanks

Nagaraja

unread,
Jan 27, 2024, 8:28:26 AMJan 27
to django...@googlegroups.com
Y cnt u use pusher instead of channels 

David Emanuel Sandoval

unread,
Jan 27, 2024, 12:16:05 PMJan 27
to django...@googlegroups.com
To identify the tenant i used a middleware that extract the tenant from the domain.

David Emanuel Sandoval

unread,
Jan 28, 2024, 2:05:20 AMJan 28
to django...@googlegroups.com
Just like the one they posted, but with a few minor changes:

 async def __call__(self, scope, receive, send):
and:
return await self.inner(
dict(scope, schema_name=schema_name, multitenant=True), receive, send
)


Also, check if AuthMiddleware is using the channels.auth.get_user() function within the correct schema.

Reply all
Reply to author
Forward
0 new messages