在非平凡的生产环境中拥有django应用程序的人,您如何处理数据库迁移?我知道有south
,但如果涉及到任何重大内容,似乎会错过很多。
另外两个选项(我能想到或已经使用过)是在测试数据库上进行更改然后(与应用程序脱机)并导入该sql导出。或者,也许是一个风险更高的选择,实时对生产数据库进行必要的更改,如果出现任何问题,还原为备份。
您通常如何处理数据库迁移和架构更改?
答案 0 :(得分:15)
我认为这个问题有两部分。
首先是管理数据库架构及其变化。我们使用South执行此操作,将工作模型和迁移文件保留在SCM存储库中。为了安全(或偏执),我们在运行任何迁移之前(如果我们真的害怕,之后)进行数据库转储。到目前为止,南方已满足我们的所有要求。
其次是部署模式更改,这不仅仅是运行South生成的迁移文件。根据我的经验,对数据库的更改通常需要更改已部署的代码。如果您有一个小型Web场,那么将部署的代码与当前版本的数据库架构保持同步可能并不简单 - 如果考虑不同的缓存层并对已经活跃的站点用户产生影响,这会变得更糟。不同的网站以不同的方式处理这个问题,我认为没有一个通用的答案。
解决这个问题的第二部分并不一定是直截了当的。我不相信有一种通用的方法,并且没有足够的关于您的网站和环境的信息来建议最适合您情况的解决方案。但是,我认为可以记住一些注意事项,以帮助指导大多数情况下的部署。
在某些情况下,将整个站点(Web服务器和数据库)脱机是一种选择。它当然是管理更新的最直接方式。但是频繁的停机时间(即使在计划中)也是快速开展业务的好方法,使得即使是很小的代码更改也很烦人,如果您拥有大型数据集和/或复杂的迁移,可能需要花费数小时。也就是说,对于我帮助管理的网站(这些网站都是内部网站,通常仅在工作日的工作时间使用),这种方法可以实现奇迹。
如果对master数据库的副本进行更改,请务必小心。这里的主要问题是您的网站仍然存在,并且可能接受对数据库的写入。当您忙于迁移克隆以供以后使用时,写入主数据库的数据会发生什么变化?您的网站必须暂时停止或暂时处于某种只读状态,否则您将丢失它们。
如果您的更改是向后兼容的,并且您有一个Web场,有时您可以更新实时生产数据库服务器(我认为在大多数情况下这是不可避免的),然后通过获取它们来逐步更新服务器场中的节点在短时间内从负载平衡器中取出。这可以正常工作 - 但是这里的主要问题是,如果已经更新的节点发送了对旧节点不支持的URL请求,您将无法在负载均衡器级别管理该节点。 / p>
我已经看到/听到过其他几种方法。
第一个是将所有代码更改包装在功能锁中,然后通过某些站点范围的配置选项在运行时进行配置。这实际上意味着您可以释放所有更改都已关闭的代码,然后在对服务器进行所有必要的更新后,更改配置选项以启用该功能。但这会产生相当沉重的代码......
第二个是让代码管理迁移。我听说过这样的站点,其中代码的更改以这样的方式编写,即它在运行时处理迁移。它能够检测正在使用的模式的版本,以及它获取的数据的格式 - 如果数据来自旧模式,它就会进行迁移,如果数据已经来自新模式,它什么也不做。从自然站点使用情况来看,大部分数据将由使用该站点的人员迁移,您可以随时使用迁移脚本进行迁移。
但我认为此时谷歌会成为你的朋友,因为正如我所说,解决方案是针对特定情况的,我担心这个答案会开始变得毫无意义......搜索“零停机时间部署”之类的内容并且你会得到诸如this之类的结果并且有很多想法...
答案 1 :(得分:4)
我使用South作为代码库为~40K行的生产服务器,到目前为止我们没有遇到任何问题。对于我们的一些模型,我们也经历过几个主要的重构,我们没有问题。
我们还有一件事就是对我们的模型进行版本控制,这有助于我们恢复我们在软件方面对模型所做的任何更改,其中South更多地用于实际数据。我们使用Django Reversion
答案 2 :(得分: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“unknown column”/ 1060“duplicate column”)到您的数据库引擎和架构更改,但这应该很容易
您可能希望添加一些额外的检查以确保正在执行的SQL实际上是错误的,因为有问题的架构更改而不是其他一些问题,但即使您不这样做,也应该重新引发无关的异常。唯一的惩罚是,在这种情况下,你会在提出异常之前尝试升级和错误查询两次。
我最喜欢的一个关于python的东西是能够在运行时轻松覆盖系统方法的能力。它提供了很大的灵活性。
答案 3 :(得分:1)
南方并非随处可见。就像在我的组织中一样,我们有3级代码测试。一个是本地开发环境,一个是开发环境,第三个是生产环境。
本地开发人员可以根据他的需要在开发者手中进行游戏。然后是staging dev,它与生产相同,直到必须在实际站点上进行db更改,我们首先在staging上进行db更改,并检查一切是否正常工作然后我们手动更改生产db使它再次与staging相同。
答案 4 :(得分:1)
如果您的数据库非常重要,那么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
。这会在出现问题时停止升级脚本。并且,通过大量测试,您可以确保没有任何功能。
有许多可用的数据库模式差异工具,在this 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)