2012-05-31 131 views
15

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

另外两个选项(我可以想到或已经使用)在测试数据库上进行更改,然后(与应用程序脱机)并导入该sql导出。或者,也许是一个更危险的选择,实时对生产数据库进行必要的更改,并且如果出现任何问题,还原到备份。

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

+1

我认识的大多数Django开发者都使用South。我自己使用南方。你觉得它会'错过很多'?我对南方抱怨不已,但大部分情况下,它是有效的。 – super9

+0

你的应用程序的大致数量是多少? – David542

回答

14

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

首先是管理数据库架构及其变化。我们使用South来做到这一点,将工作模型和迁移文件保存在我们的SCM存储库中。为了安全(或偏执狂),我们在运行任何迁移之前(如果我们真的害怕,之后)运行数据库转储。到目前为止,South已经足够满足我们的所有要求。

其次,部署模式更改不仅仅是运行由South生成的迁移文件。根据我的经验,对数据库的更改通常需要更改已部署的代码。如果您的网站甚至是小型网站,那么将部署的代码与当前版本的数据库模式保持同步可能并不简单 - 如果考虑到不同的缓存层并影响已经处于活动状态的网站用户,则情况会变得更糟。不同的网站处理这个问题的方式不同,我不认为有一个通用的答案。


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

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

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

如果您的更改向后兼容,并且您有一个Web场,有时可以更新实时生产数据库服务器(我认为这在大多数情况下是不可避免的),然后逐步更新场中的节点他们在短时间内离开负载平衡器。这可以正常工作 - 但是这里的主要问题是如果一个已经更新的节点发送一个旧节点不支持的URL请求,那么你将无法在负载平衡器级别管理该节点。

我见过/听说过其他几种方式都很好。

首先是将所有代码更改封装在功能锁中,然后通过某些站点范围的配置选项在运行时对其进行配置。这基本上意味着您可以在所有更改关闭的情况下发布代码,然后在完成对服务器的所有必要更新后,更改配置选项以启用该功能。但是这使得代码非常繁重......

第二个是让代码管理迁移。我听说过代码的变化是以这样的方式编写的,即它在运行时处理迁移。它能够检测正在使用的模式的版本以及它收回的数据的格式 - 如果数据来自旧模式,则它会进行迁移,如果数据已经来自新模式,则它不执行任何操作。从自然网站的使用情况来看,大部分数据将被使用该网站的用户迁移,其他用户可以随时使用迁移脚本进行迁移。

但我认为在这一点上谷歌成为你的朋友,因为正如我所说,解决方案是非常具体的情况下,我担心这个答案将开始变得毫无意义...搜索“零停机时间部署“你会得到结果,如this有很多想法... ...

+0

感谢您的回答。您能否简要介绍一下您是如何完成第二部分的? – David542

+1

我已经为问题的第二部分添加了更多的细节 - 希望它有帮助。这也取决于你的规模 - Facebook必须以非常不同的方式来处理这个问题,你可能会从你的卧室托管的博客! –

+0

非常感谢您的光临 – David542

4

我使用南方生产服务器的代码库大约40K行,到目前为止我们没有任何问题。对于我们的一些模型,我们也经历了几次重要的重构,而且我们没有遇到任何问题。

我们还有一件事是对我们的模型进行版本控制,这有助于我们恢复我们在软件方面对模型所做的任何更改,其中南方更多是针对实际数据。我们使用Django Reversion

+1

我只是简单地阅读了Django Reversion的文档,但是我不清楚为什么你会使用它来优先使用“正确的”SCM(如SVN或git)来管理模型(并且以同样的方式)为您的网站的其余代码。我错过了什么? –

+0

@MarkStreatfield:Reversion用于管理模型的内容,而不是结构。 –

+0

啊,我明白了,谢谢你的澄清 - 我完全错过了。所以这将有助于做类似于这个问题中描述的东西http://stackoverflow.com/questions/39281/database-design-for-revisions/126468? –

1

南部没有使用无处不在。就像在我的讨论中一样,我们有3个级别的代码测试。一个是本地开发环境,一个是开发环境,第三个是生产环境。

本地开发者在开发者手中可以根据自己的需求进行游戏。然后将分阶段开发与生产保持一致,直到在生活网站上进行数据库更改,我们首先在分​​段上进行数据库更改,然后检查一切是否正常工作,然后手动更改生产数据库使其与再次登台相同。

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 

如果你喜欢上面的安排,但你担心它需要运行提升两倍的时间,你可以锁定分贝用于写,然后,如果升级到upgradetest顺利然后你可以重命名dbdboldupgradetestdb。有很多选择。

如果你有一个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) 
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最喜欢的东西之一就是能够像这样在运行时轻松覆盖系统方法。它提供了非常多的灵活性。