22

对于在非平凡生产环境中拥有 django 应用程序的人,您如何处理数据库迁移?我知道有south,但如果涉及任何实质性内容,似乎会错过很多。

其他两个选项(我能想到或已经使用)是在测试数据库上进行更改,然后(使用应用程序脱机)并导入该 sql 导出。或者,也许是一个风险更大的选择,实时对生产数据库进行必要的更改,如果出现任何问题,则恢复到备份。

您通常如何处理数据库迁移和架构更改?

4

6 回答 6

23

我认为这个问题有两个部分。

首先是管理数据库模式及其变化。我们使用 South 执行此操作,将工作模型和迁移文件都保存在我们的 SCM 存储库中。为了安全(或偏执),我们在运行任何迁移之前(如果我们真的很害怕,那么之后)会转储数据库。到目前为止,South 已经足以满足我们的所有要求。

其次是部署架构更改,这不仅仅是运行 South 生成的迁移文件。根据我的经验,更改数据库通常需要更改已部署的代码。如果您甚至有一个小型网络农场,保持部署的代码与您的数据库模式的当前版本同步可能并非易事 - 如果您考虑不同的缓存层和对已经活跃的站点用户的影响,这会变得更糟。不同的网站处理这个问题的方式不同,我认为没有一个万能的答案。


解决这个问题的第二部分不一定是直截了当的。我不相信有一种万能的方法,并且没有足够的关于您的网站和环境的信息来建议最适合您的情况的解决方案。但是,我认为可以牢记一些注意事项,以帮助指导大多数情况下的部署。

在某些情况下,使整个站点(Web 服务器和数据库)脱机是一种选择。这无疑是管理更新最直接的方式。但是频繁的停机(即使是计划好的)可能是快速开展业务的好方法,即使是很小的代码更改也会让人厌烦,如果您有大型数据集和/或复杂的迁移,可能需要很多小时。也就是说,对于我帮助管理的网站(这些网站都是内部的,通常只在工作日的工作时间使用),这种方法效果很好。

如果您在主数据库的副本上进行更改,请小心。这里的主要问题是您的站点仍然存在,并且可能正在接受对数据库的写入。当您忙于迁移克隆以供以后使用时,写入主数据库的数据会发生什么情况?您的网站要么一直处于关闭状态,要么暂时处于只读状态,否则您将丢失它们。

如果您的更改是向后兼容的,并且您有一个网络农场,有时您可以通过更新实时生产数据库服务器(我认为在大多数情况下是不可避免的)然后通过将它们从农场中取出来逐步更新农场中的节点短期负载均衡器。这可以正常工作 - 但是这里的主要问题是,如果一个已经更新的节点发送一个旧节点不支持的 url 请求,您将失败,因为您无法在负载均衡器级别进行管理。

我已经看到/听到了其他几种效果很好的方法。

第一个是将所有代码更改包装在一个功能锁中,然后可以在运行时通过一些站点范围的配置选项进行配置。这实质上意味着您可以在关闭所有更改的情况下发布代码,然后在对服务器进行所有必要的更新后,您可以更改配置选项以启用该功能。但这会产生相当繁重的代码......

第二个是让代码管理迁移。我听说过一些网站,其中对代码的更改是以在运行时处理迁移的方式编写的。它能够检测正在使用的模式的版本,以及它返回的数据的格式——如果数据来自旧模式,它会进行适当的迁移,如果数据已经来自新模式,它什么也不做. 从自然站点使用情况来看,您的大部分数据将由使用该站点的人迁移,其余的您可以随时使用迁移脚本来完成。

但我认为此时谷歌成为你的朋友,因为正如我所说,解决方案是非常具体的,我担心这个答案会开始变得毫无意义......搜索“零停机时间部署”之类的东西,你会有很多想法会得到这样的结果......

于 2012-06-02T06:45:14.993 回答
4

我将 South 用于具有约 40K 行代码库的生产服务器,到目前为止我们没有遇到任何问题。我们还对我们的一些模型进行了几次重大重构,并且我们的问题为零。

我们还拥有的一件事是对模型的版本控制,这有助于我们恢复对软件方面的模型所做的任何更改,而 South 更多地用于实际数据。我们使用Django Reversion

于 2012-05-31T05:35:48.500 回答
3

我有时对这个问题采取了一种非常规的方法(阅读其他答案,也许它不是那么非常规)。我从来没有用 django 尝试过,所以我只是用它做了一些实验。

简而言之,我让代码捕获旧模式导致的异常并应用适当的模式升级。我不希望这是公认的答案——它只在某些情况下是合适的(有些人可能永远不会争辩)。但我认为它有一种丑小鸭的优雅。

当然,我有一个测试环境,我可以随时重置回生产状态。使用该测试环境,我像往常一样更新我的模式并针对它编写代码。

然后我恢复架构更改并再次测试新代码。我捕获了由此产生的错误,执行架构升级,然后重试错误查询。

必须编写升级函数,使其“无害”,这样如果它被多次调用(就像投入生产时可能发生的那样),它只会执行一次。

实际的 python 代码——我把它放在我的 settings.py 的末尾来测试这个概念,但你可能想把它放在一个单独的模块中:

from django.db.models.sql.compiler import SQLCompiler
from MySQLdb import OperationalError

orig_exec = SQLCompiler.execute_sql
def new_exec(self, *args, **kw):
    try:
        return orig_exec(self, *args, **kw)
    except OperationalError, e:
        if e[0] != 1054: # unknown column
            raise
        upgradeSchema(self.connection)
        return orig_exec(self, *args, **kw)
SQLCompiler.execute_sql = new_exec

def upgradeSchema(conn):
    cursor = conn.cursor()
    try:
        cursor.execute("alter table users add phone varchar(255)")
    except OperationalError, e:
        if e[0] != 1060: # duplicate column name
            raise

一旦您的生产环境是最新的,您就可以自由地从您的代码库中删除此自我升级代码。但即使你不这样做,代码也不会做任何重要的不必要的工作。

您需要针对您的数据库引擎和架构更改定制异常类(在我的情况下为 MySQLdb.OperationalError)和数字(在我的情况下为 1054“未知列”/1060“重复列”),但这应该很容易。

您可能需要添加一些额外的检查以确保正在执行的 sql 实际上是由于有问题的架构更改而不是其他问题而出错,但即使您不这样做,这也应该重新引发不相关的异常。唯一的惩罚是,在这种情况下,您将在引发异常之前尝试两次升级和错误查询。

关于 python,我最喜欢的一件事是能够像这样在运行时轻松覆盖系统方法。它提供了很大的灵活性。

于 2012-06-08T16:09:28.263 回答
2

如果你的数据库是非平凡的并且 Postgresql 你有一大堆优秀的 SQL 选项,包括:

  • 快照和回滚
  • 实时复制到备份服务器
  • 试用升级然后上线

试用升级选项很好(但最好与快照合作完成)

su postgres
pg_dump <db> > $(date "+%Y%m%d_%H%M").sql
psql template1
# create database upgrade_test template current_db
# \c upgradetest
# \i upgrade_file.sql
...assuming all well...
# \q
pg_dump <db> > $(date "+%Y%m%d_%H%M").sql # we're paranoid
psql <db>
# \i upgrade_file.sql

如果你喜欢上面的安排,但你担心运行两次升级所花费的时间,你可以锁定db进行写入,然后如果升级到upgradetest顺利,你可以将db重命名为dbold并将upgradetest重命名为db。有很多选择。

如果您有一个 SQL 文件列出了您想要进行的所有更改,一个非常方便的 psql 命令\set ON_ERROR_STOP 1。这会在出现问题时停止升级脚本。而且,通过大量测试,您可以确保没有任何问题。

有很多可用的数据库模式差异工具,在这个StackOverflow 答案中注明了一些。但这基本上是很容易手工完成的......

pg_dump --schema-only production_db > production_schema.sql
pg_dump --schema-only upgraded_db > upgrade_schema.sql
vimdiff production_schema.sql upgrade_schema.sql
or
diff -Naur production_schema.sql upgrade_schema.sql > changes.patch
vim changes.patch (to check/edit)
于 2012-06-07T22:41:25.533 回答
1

南方不是到处使用。就像在我的组织中一样,我们有 3 个级别的代码测试。一是本地开发环境,一是暂存开发环境,三是生产环境。

Local Dev 掌握在开发人员手中,他可以根据自己的需要进行游戏。然后是 staging dev,它与生产环境保持一致,当然,直到必须在实时站点上完成数据库更改,我们首先在 staging 上进行数据库更改,并检查一切是否正常,然后我们手动更改生产环境db 使其再次与 staging 相同。

于 2012-06-03T17:37:57.920 回答
0

如果它不是微不足道的,那么您应该拥有模仿生产数据库/应用程序的预生产数据库/应用程序。避免生产停机。

于 2022-01-02T20:47:48.563 回答