【问题标题】:Django running a migration on multi-tenant DBDjango 在多租户数据库上运行迁移
【发布时间】:2012-11-26 20:29:54
【问题描述】:

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

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

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

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

问候, 马特

【问题讨论】:

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


    【解决方案1】:

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

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

    这意味着公共架构上的应用迁移历史记录保存在公共架构中,而租户应用迁移的历史记录保存在租户架构中——有效地对迁移历史表进行分片。 Django 并不真正支持这种分片;按实例内容设置写入很容易,但无法设置读取。

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

    • syncdb 到公共和租户模式
    • 始终从租户助手架构中读取
    • 根据迁移所属的应用写入公共或租户架构

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

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

    【讨论】:

      【解决方案2】:

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

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

      我有一个通过 South 的 schemamigration 自动生成的非常正常的迁移文件。但是我把add_columndelete_column的表名改成了schema.table_name。通过导入多租户中间件来提供模式。

      仅当架构未针对 public 架构运行时,才会应用迁移。它实际上并不意味着独立运行,或者仅与数据库和模式 kwargs 一起运行,而是来自一个新的 django 命令的迁移运行程序。

      不幸的是,跑步者不得不在外部调用迁移,以便每次都再次通过中间件。另一个技巧是,我们必须获取之前的迁移状态,以便在每次租户迁移后伪造回到之前的南方状态。

      这是我的 sn-p:

      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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-07-13
        • 1970-01-01
        • 1970-01-01
        • 2021-10-15
        • 2014-01-13
        • 2015-07-19
        • 1970-01-01
        • 2023-03-28
        相关资源
        最近更新 更多