Django在多租户数据库上运行迁移

时间:2012-11-22 11:55:24

标签: django django-south multi-tenant database-migration

在一个带有一些自制(但接近可用的插件方法)多租户实现的Django应用程序中,我想用South运行一个迁移(这次是一个简单的add_column),可以应用于所有模式。我的配置非常接近this one

如果可能的话,我想跳过任何纯SQL查询。我可以正确地从ORM获取模式名称列表,但后来我想知道我是否有可能以某种方式从各种模式中访问表。

我有一个钩子能够在某个级别通过参数更改DB_HOST和DB_SCHEMA,但我认为不能在南方的前向迁移方法中以这种方式干净地循环。

这个问题相当高级,但我主要想知道是否有人不得不面对同样的问题,我很想知道是否有一些聪明的方法来处理它!

此致 马特

2 个答案:

答案 0 :(得分:1)

这是南方邮件列表上公布的解决方案概要。措辞的问题与列表中的问题略有不同:在那里,还提到了在所有租户之间共享的“共同”表,在单独的模式中。 Rmatt自己的回答将此称为 public 架构。​​

我的解决方案的基本思想:保存架构中每个数据库(架构)的迁移历史记录。为此,我们需要使用一些数据库和Django技巧。

这意味着公共模式上的应用程序迁移的历史记录保存在公共模式中,而租户应用程序的迁移历史记录保存在租户模式中 - 有效地分割迁移历史记录表。 Django并不真正支持这种分片;通过实例内容设置写入很容易,但是没有办法设置读数。

所以我建议每个租户创建一个“租户助手”模式,其中包含一个名为south_migrationhistory的视图,它是来自公共和租户模式的south_migrationhistory表的联合。然后,为South MigrationHistory模型设置数据库路由器,指示它:

  • syncdb同时适用于公共和租户架构
  • 始终从tenant-helper schema
  • 读取
  • 根据迁移所属的应用程序写入公共或租户架构

结果可以正确处理从租户应用迁移到公共应用迁移的依赖关系;这意味着你需要做的就是向前迁移,在migrate --all(或syncdb --migrate)命令上循环 - 不需要伪造向后迁移。公共模式的迁移将与循环中第一个租户的迁移一起运行,所有其他租户将“看到”它们。

作为事后的想法,通过重命名租户架构中的south_migrationhistory表,并在返回上述方案的架构中安装具有该名称的视图,也可以在没有辅助架构的情况下执行此操作。查询时联合,并具有“替代插入”触发器以写入重命名的表。

答案 1 :(得分:0)

很好,没有那么多人似乎有经验或关心这个非常具体的问题。我在这里和那里尝试了一些东西,我也得到了南方邮件列表的一些支持,这有助于我理解一些观点。

基本上,我实施的解决方案如下:

我有一个非常正常的迁移文件,通过South的 schemamigration 自动生成。但我已将 add_column delete_column 的表名更改为schema.table_name。通过导入多租户中间件提供架构。

只有在未针对 public 架构运行架构时,才会应用迁移。它实际上并不是独立运行,也不仅仅是数据库和模式kwargs运行,而是来自作为新django命令的迁移运行器。

不幸的是,跑步者在外部调用迁移,以便每次都通过中间件。另一个诀窍是我们必须获得以前的迁移状态,以便在每次租户迁移后伪造将其恢复到南方的先前状态。

这是我的代码:

from subprocess import call
import os
from django.core.management.base import BaseCommand    
from south.models import MigrationHistory
from myapp.models import MyModel

class Command(BaseCommand):

    def handle(self, *args, **options):
        #the only allowed arg is the prefix version and it should have a length of 4 (i.e. 0002)
        applied = MigrationHistory.objects.filter(app_name='myapp').latest('applied')
        current_version = applied.migration[:4]
        call_args = ['python', os.path.join('bin', 'manage.py'), 'migrate', 'myorderbird.app.backups']
        if len(args) == 1 and len(args[0]) == 4:
            call_args.append(args[0])

        obje_call_args = None
        for obje in MyModel.objects.all():
            if obje.schema_exists:
                # fake the migration of the previous venue back to the current version
                if obje_call_args:
                    obje_call_args = obje_call_args[:4] + [current_version, '--fake'] + obje_call_args[len(obje_call_args)-3:]
                    call(obje_call_args)
                # migrate the venue in the loop
                obje_call_args = list(call_args)
                obje_call_args.extend(['--database={}'.format(obje.db), '--schema={}'.format(obje.schema)])
                call(venue_call_args)