2012-05-31 163 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最喜歡的東西之一就是能夠像這樣在運行時輕鬆覆蓋系統方法。它提供了非常多的靈活性。