如何在Django中为每个“应用程序实例”使用不同的数据库?

时间:2022-10-02 21:36:24

The scenario

We have two applications.

我们有两个应用程序。

TheApp

TheApp is an incredible app which customers love. Each customer gets his own instance of the application, which means each customer will use a different database (name,user,password). The database connection should be decided on the domain from which the request comes in.

TheApp是一款令客户喜爱的令人难以置信的应用。每个客户都获得自己的应用程序实例,这意味着每个客户将使用不同的数据库(名称,用户,密码)。应根据请求所在的域确定数据库连接。

req: customerA.foo.tld -> db:(app_cust1, cust1, hunter2)
req: customerB.foo.tld -> db:(app_cust2, cust2, hunter3)

Administration application

Should be able to create/delete TheApp instances for the customers. Therefore it has to setup the new database and write the config to somewhere. The way which decides which db is used for the incoming request should perform well and be easy manageable.

应该能够为客户创建/删除TheApp实例。因此,它必须设置新数据库并将配置写入某处。决定将哪个数据库用于传入请求的方法应该运行良好且易于管理。

The Question

Which is the best way to decide which database connection should be used for an instance? What performs the best? What scales best?

哪个是决定应该为实例使用哪个数据库连接的最佳方法?什么表现最好?什么尺度最好?

Answers I came up with™

I read stuff and those are the ways I came up with:

我读了一些东西,这些是我提出的方法:

(wsgi daemon + settings.py) per instance

Each customer will get his own settings.py with the database credentials. The settings may inherit some common stuff from a shared settings file.

每个客户都将获得自己的settings.py和数据库凭据。这些设置可能会从共享设置文件中继承一些常见内容。

For each new setting file a new wsgi instance of the application has to be started. This may scale badly if we have many customers? Also creating the apache vhost files is ugly.

对于每个新设置文件,必须启动应用程序的新wsgi实例。如果我们有很多客户,这可能会严重缩小?另外创建apache vhost文件很难看。

Using 'using' & one settings.py

I could do it like

我可以这样做

MyModel.objects.using(THE_CURRENT_DB).all()

and set THE_CURRENT_DB somewhere (middleware thingy?) per request. But it seems ugly to have to do this everywhere. Also the settings.py/app has to be rewritten everytime a customer gets his instance.

并根据请求在某处设置THE_CURRENT_DB(中间件东西?)。但到处都要做到这一点似乎很难看。每次客户获取实例时,都必须重写settings.py/app。

One settings.py + Application Router

I didn't yet have a look if I can access any information about the request in the router, but if so, I maybe could decide which of the dbs in settings.py should be used. Kind of like https://docs.djangoproject.com/en/1.3/topics/db/multi-db/#an-example but not per model but per request.

我还没有看看我是否可以在路由器中访问有关请求的任何信息,但如果是这样,我可能会决定应该使用settings.py中的哪个dbs。有点像https://docs.djangoproject.com/en/1.3/topics/db/multi-db/#an-example,但不是每个型号,而是每个请求。

Modify settings in a middleware

Just had the idea that maybe the db setting could be altered in a middleware. Didn't yet have a look how middleware works in Django and what's possible there.

只是想到可能在中间件中更改数据库设置。还没看看Django中间件是如何工作的,那里有什么可能。

Some obscure other way

As I'm pretty new with Django I may have missed some points or some of them are just totally silly and bad. What would jes^wyou do?

由于我对Django很新,我可能错过了一些点,或者其中一些只是完全愚蠢和糟糕。你会做什么?

Why not everything in one db?

Well. Because I think separation of stuff is good. And if bad things happen not everybody is suddenly affected.

好。因为我认为东西的分离是好的。如果发生不好的事情,并不是每个人都会受到影响。

2 个解决方案

#1


2  

This is easily done with middleware and Postgres namespaces. Here is a quick and dirty example with absolutely no error handling:

这可以通过中间件和Postgres命名空间轻松完成。这是一个快速而肮脏的例子,完全没有错误处理:

class NamespaceMiddleware:
    def process_request(self, request):

        # Get the subdomain. You could also use the domain name, but you'll have to remove special characters.
        host = request.get_host()
        parts = host.split('.')
        if len(parts) >= 3:
            subdomain = parts[0]

        # Set the namespace (aka "schema"). This will throw a DatabaseError if the namespace does not exist.
        from django.db import connection
        cursor = connection.cursor()
        cursor.execute("SET search_path TO ", subdomain)

With this middleware enabled, each customer can have completely separate data, and no mopery is required to make it work. There are a few things to know, though:

启用此中间件后,每个客户都可以拥有完全独立的数据,并且不需要使用它就可以运行。但有几点需要了解:

  1. One problem is dealing with this in development. I usually add an if statement to ignore the above procedure if settings.DEBUG is True, but you could also set up virtual hosts and edit your hosts file to test this in development.
  2. 一个问题是在开发中处理这个问题。如果settings.DEBUG为True,我通常会添加一个if语句来忽略上述过程,但您也可以设置虚拟主机并编辑hosts文件以在开发中测试它。
  3. Another consideration is that you must run a separate virtual host for each instance. Otherwise you may run into problems where instance data can cross over. I assume this is some sort of threading issue, but someone smarter than I can probably explain that in more detail.
  4. 另一个考虑因素是您必须为每个实例运行单独的虚拟主机。否则,您可能会遇到实例数据可以跨越的问题。我认为这是某种线程问题,但是比我聪明的人可以更详细地解释一下。
  5. Finally, you need to think about how to deal with new installs and schema updates. Django will put everything in the public schema. You'll need to learn how to copy this schema to create a new one, and also get used to scripting database updates.
  6. 最后,您需要考虑如何处理新安装和架构更新。 Django将把所有内容都放在公共模式中。您需要了解如何复制此架构以创建新架构,并且还习惯于编写数据库更新脚本。

#2


1  

That is one of those scenarios that show the weakness of django configuration module (settings). There is no "django supported" way to do that.

这是显示django配置模块(设置)弱点的场景之一。没有“django支持”的方式来做到这一点。

I think you could choose to go with an option that has minimal impact to code maintenance and portability. So I suggest to:

我认为您可以选择对代码维护和可移植性影响最小的选项。所以我建议:

  • use an middleware to keep the users configuration in some data structure (django supported)
  • 使用中间件将用户配置保留在某些数据结构中(支持django)
  • make settings.DATABASE a callable to fetch users configuration from above (django hack)
  • make settings.DATABASE a callable从上面获取用户配置(django hack)
  • use django multiple database feature to access models (django supported)
  • 使用django多数据库功能访问模型(支持django)

#1


2  

This is easily done with middleware and Postgres namespaces. Here is a quick and dirty example with absolutely no error handling:

这可以通过中间件和Postgres命名空间轻松完成。这是一个快速而肮脏的例子,完全没有错误处理:

class NamespaceMiddleware:
    def process_request(self, request):

        # Get the subdomain. You could also use the domain name, but you'll have to remove special characters.
        host = request.get_host()
        parts = host.split('.')
        if len(parts) >= 3:
            subdomain = parts[0]

        # Set the namespace (aka "schema"). This will throw a DatabaseError if the namespace does not exist.
        from django.db import connection
        cursor = connection.cursor()
        cursor.execute("SET search_path TO ", subdomain)

With this middleware enabled, each customer can have completely separate data, and no mopery is required to make it work. There are a few things to know, though:

启用此中间件后,每个客户都可以拥有完全独立的数据,并且不需要使用它就可以运行。但有几点需要了解:

  1. One problem is dealing with this in development. I usually add an if statement to ignore the above procedure if settings.DEBUG is True, but you could also set up virtual hosts and edit your hosts file to test this in development.
  2. 一个问题是在开发中处理这个问题。如果settings.DEBUG为True,我通常会添加一个if语句来忽略上述过程,但您也可以设置虚拟主机并编辑hosts文件以在开发中测试它。
  3. Another consideration is that you must run a separate virtual host for each instance. Otherwise you may run into problems where instance data can cross over. I assume this is some sort of threading issue, but someone smarter than I can probably explain that in more detail.
  4. 另一个考虑因素是您必须为每个实例运行单独的虚拟主机。否则,您可能会遇到实例数据可以跨越的问题。我认为这是某种线程问题,但是比我聪明的人可以更详细地解释一下。
  5. Finally, you need to think about how to deal with new installs and schema updates. Django will put everything in the public schema. You'll need to learn how to copy this schema to create a new one, and also get used to scripting database updates.
  6. 最后,您需要考虑如何处理新安装和架构更新。 Django将把所有内容都放在公共模式中。您需要了解如何复制此架构以创建新架构,并且还习惯于编写数据库更新脚本。

#2


1  

That is one of those scenarios that show the weakness of django configuration module (settings). There is no "django supported" way to do that.

这是显示django配置模块(设置)弱点的场景之一。没有“django支持”的方式来做到这一点。

I think you could choose to go with an option that has minimal impact to code maintenance and portability. So I suggest to:

我认为您可以选择对代码维护和可移植性影响最小的选项。所以我建议:

  • use an middleware to keep the users configuration in some data structure (django supported)
  • 使用中间件将用户配置保留在某些数据结构中(支持django)
  • make settings.DATABASE a callable to fetch users configuration from above (django hack)
  • make settings.DATABASE a callable从上面获取用户配置(django hack)
  • use django multiple database feature to access models (django supported)
  • 使用django多数据库功能访问模型(支持django)