如何對DevOps資料庫進行原始碼控制

weixin_33763244發表於2019-01-29

提綱:

  • 包括索引在內的資料庫模式需要進行原始碼控制
  • 諸如查詢表這類用於控制業務邏輯的資料需要進行原始碼控制
  • 開發人員需要一種能夠便捷地建立本地資料庫的方法
  • 共享資料庫的更新只能通過構建伺服器完成

健壯的DevOps環境需要對系統的每個元件進行持續整合。但是,資料庫常常被排除在這之外,這會導致從脆弱的產品釋出和低效的開發實踐到讓新入職的程式設計師工作更困難等一系列問題。

在本文中,我們將討論在成功的持續整合環境中,關係型資料庫和NoSQL資料庫的獨特的一面。

模式的原始碼控制

首先需要解決的就是原始碼控制和模式問題。讓開發人員以一種特別的方式進行資料庫變更是不合適的。這相當於在生產伺服器上通過直接編輯JavaScript檔案對其進行更改。

在為資料庫規劃原始碼控制時,需要確保囊括了所有內容。這包括但不限於:

  • 表或者集合
  • 約束
  • 索引
  • 檢視
  • 儲存過程、函式以及觸發器
  • 資料庫配置

您可能會想,“我使用的是無模式資料庫,所以我不需要原始碼控制”。即便如此,您仍需要考慮索引和資料庫的整體配置。如果在QA和生產資料庫中索引計劃不同,那麼執行效能測試將毫無意義。

資料庫的原始碼控制有兩種基本型別,我們將其稱為“全模式”和“變更指令碼”。

全模式原始碼控制

“全模式”原始碼控制指的是原始碼控制與你所希望的資料庫的外觀看起來十分相似。在使用此模式時,可以看到所有的表和檢視都按照預期的樣子排列,這讓你無需部署資料庫就能夠更容易理解它。

SQL Server的SQL Server Data Tools(SSDT)就是全模式原始碼控制的一個例子。這個工具可以通過CREATE指令碼的形式表示所有資料庫物件。當想要用SQL建立一個新的物件,只需將最終指令碼直接貼上到處於原始碼控制下的資料庫專案中即可,這就很方便了。

全模式原始碼控制的另一個例子是實體框架遷移(Entity Framework Migrations)。在這個案例中,資料庫主要通過C#/VB類的方式而非SQL指令碼的方式表現。但同樣,可以通過瀏覽原始碼獲得對資料庫的整體認識。

在使用全模式原始碼控制時,通常不需要直接編寫遷移指令碼。部署工具通過將資料庫的當前狀態與處於原始碼控制中的理想版本進行比較來確定需要進行哪些更改。這可以讓你快速完成資料庫變更並看到結果。在使用這種型別的工具時,我很少直接更改資料庫,而是使用工具完成大部分工作。

有些情況下,只有工具是不夠的,即使包含部署前和部署後指令碼也是如此。在這種情況下,生成的遷移指令碼必須由資料庫開發人員或DBA手工修改,這可能會破壞持續部署計劃。這種情況通常發生在對錶結構進行重大變更時,因為在這些情況下生成的遷移指令碼效率可能較低。

全模式原始碼控制的另一個優點是它支援程式碼分析。例如,如果列的名稱被更改,但在檢視中未做出相應更新,SSDT將返回一個編譯錯誤。就像應用程式設計語言中的靜態型別一樣,可以捕獲大量錯誤,並且能夠對部署有明顯錯誤的指令碼提前防範。

變更指令碼原始碼控制方式

另一種原始碼控制方式就是變更指令碼原始碼控制。這種方式不儲存資料庫物件本身,而是儲存建立資料庫物件所需的步驟列表。我曾經成功使用的是Liquibase資料庫,總的來說這類工具的工作機制都差不多。

變更指令碼工具的主要優點是可以完全控制最終遷移指令碼的樣貌。這使得複雜變更的執行更加容易,例如表的拆分或合併。

不幸的是,這種型別的原始碼控制也存在一些缺陷。首先是需要編寫變更指令碼。雖然它的控制力度更強,但同樣也更費時。相比於手寫ALTER TABLE指令碼,為C#類新增一個屬性要容易得多。

當使用SchemaBinding之類的功能時,會讓問題更加嚴重。對於不熟悉這個術語的人,可以將其理解為,通過鎖定表的模式啟用SQL Server中的一些高階特性。如果要變更表或檢視的設計,必須首先將SchemaBinding從任何與該表或檢視有關的檢視中移除。如果這些檢視被其他檢視模式繫結(schema-bound),那麼這些檢視同樣也需要臨時解除繫結。雖然對於全模式原始碼控制工具來說,生成所有的樣板很容易,但是想要用手工的方式正確地生成它們還是一項相當困難的工作。

變更指令碼工具的另一個缺點是它們往往難以理解。例如,如果你希望在不部署表的情況下了解某個表中列的資訊,則需要查閱所有涉及該表的變更指令碼。這就很容易錯過某些資訊。

該如何選擇原始碼控制模型?

從頭開始一個新的資料庫專案時,我會選擇全模式原始碼控制。這種模式會讓開發人員工作更加高效並且只要有一名人員完成設定工作,之後只需要很少的知識就可以正常使用。

對於現存的資料庫,特別是那些已經存續多年的生產環境資料庫,通常選擇變更指令碼原始碼控制模式更加合適。全模式工具會對資料庫設計做出某些特定的假設,而更改指令碼工具則是通用的。此外,全模式工具構建難度更大,對於某些特殊的資料庫來說,可能根本不可用。

資料管理

根據表中所含資料的性質,表可以被廣泛地分類為“管理表”、“使用者表”或“混合表”。根據表所屬的類別不同,處理這些表的方式也是不同的。

管理表

將資料庫置於原始碼控制之下的一個常見錯誤是遺忘資料。總有一些“查詢表”儲存著使用者不打算修改的資料。例如,其中可能包含表驅動的業務規則邏輯、狀態機的各種狀態碼,或者僅僅是與應用程式程式碼中的列舉類相匹配的鍵-值對列表。

這類表中的資料應該被視為原始碼一樣對待。對這類資料的變更需要經過與其他程式碼變更相同的評審和QA過程。特別重要的是,為確保這些變更不會被遺漏,這些變更的部署應該與其他應用程式和資料庫部署一併自動完成。

在SQL Server資料工具中,我使用部署後指令碼處理此問題。在這個指令碼中,我將期望資料填充到一張臨時表中,然後使用MERGE語句更新實際表。

如果原始碼控制工具無法很好地處理這個問題,還可以通過構建獨立工具來執行資料更新。重要的不是如何做,而是這個過程是否易用並且可靠。

使用者表

使用者表指的是使用者可以新增或修改資料的表。因此,這包括可以直接修改的表(例如名稱和地址)和通過操作間接修改的表(例如貨單收據、日誌)。

真實的使用者資料基本不會被直接加入原始碼控制中,不過,為開發和測試提供逼真的樣本資料也是一種最佳實踐。這些資料可以直接儲存在資料庫專案中,處於原始碼控制之下的其他地方,如果特別大,也可以儲存在獨立的共享檔案中。

混合表

混合表指的是即儲存管理資料也儲存使用者資料的表。有兩種方法可以對其進行分割槽。

列分割槽是指使用者可以修改某些列,但不能修改其他列。在這種場景下,可以將該表視為有額外限制的普通管理表,使用者控制的列永遠不會被更新。

行分割槽指的是某些記錄使用者無法修改的情況。我曾經遇到的常見的場景是需要在使用者表中對某些值進行硬編碼。在較大型的系統中,對於每個可以獨立於任何真實使用者進行更改的微服務,可能都有一個獨立的使用者ID。例如,可能是一個被稱為“銀行資料匯入器”的使用者。

在我看來,管理行分割槽混合表的最佳方法是通過保留鍵的方式。當定義identity/auto-number列時,將初始值設定為1,000,通過原始碼控制對編號從1到999的使用者ID進行管理。這需要資料庫允許手動設定identity列中的值。在SQL Server中,是通過SET_IDENTITY_INSERT命令完成的。

處理此場景的另一個選擇是使用名為“SystemControlled”的列或者能夠達到類似效果的方法。當設定為1/true時,表示應用程式不可直接修改。如果設定為0/false,則部署指令碼會將其忽略。

個人開發資料庫

將模式和資料置於原始碼控制之下,就可以進行下一步並著手設定個人開發資料庫。正如每個開發人員都應該能夠執行自己的web伺服器例項一樣,有可能對資料庫設計做出修改的每個開發人員都需要能夠執行自己的資料庫副本。

這一規則經常會被打破,這對開發團隊是相當不利的。在共享環境上做變更的開發人員一定會相互干擾。他們甚至可能會陷入“部署之爭”,每個開發人員都試圖將更改部署到資料庫。當使用全模式工具執行此操作時,開發人員將交替地恢復其他開發人員的變更,甚至都不會意識到這一點。在使用變更指令碼工具的情況下,資料庫則可能處於不確定狀態,遷移指令碼可能無法正常使用,需要從備份中重新恢復。

另一個問題是模式漂移。這時,開發資料庫與處於原始碼控制下的內容不再匹配。隨著時間的推移,開發資料庫會逐漸積累越來越多的非生產表、測試指令碼、臨時檢視和其他垃圾資訊需要清除。當每個開發人員都有自己的資料庫時,這樣做要容易得多,因為他們可以隨時重置資料庫。
最後也是最重要的問題是,服務開發人員和UI開發人員需要穩定的平臺來編寫程式碼。如果共享資料庫不斷變化,他們就無法有效地工作。在我曾經工作過的一家公司,很少看到開發人員大喊“服務又當機了!”,然後玩一個小時視訊遊戲,等待共享資料庫重新組裝起來。

共享的開發和整合資料庫

對於共享的開發人員資料庫或整合資料庫來說,首要原則就是不能對資料庫直接進行修改。更新共享資料庫的唯一方法就是通過構建伺服器以及持續整合/部署流程完成。這不僅可以防止模式漂移,還可以通過有計劃的更新減少中斷的次數。

根據經驗,我會在相關分支進行程式碼檢入時同步更新共享開發人員資料庫。這可能會造成中斷,但通常是處於可控範圍的。在進行整合之前,您確實需要先驗證部署指令碼的正確性。

對於整合資料庫而言,我更傾向於每日安排一次部署,與服務的部署次數相同。這樣能夠為UI開發人員提供一個相對穩定的工作平臺。對於UI開發人員來說,沒有什麼比不知道突然開始失敗的程式碼是他們的錯誤還是服務/資料庫中的問題更令人沮喪的了。

資料庫安全和原始碼控制

在資料庫管理中,安全性是一個經常被忽略的方面。具體來說,就是哪些使用者和角色可以訪問哪些表、檢視和儲存過程。在最糟糕的情況下,應用程式可以獲得對資料庫的完全訪問許可權。它可以對每個表的每一列進行讀寫操作,甚至是那些與其沒有業務關聯的列。資料洩漏經常是由於實用小程式的訪問許可權遠遠超過其所需的訪問許可權而造成的。

鎖定資料庫的主要反對意見是,“我們無法預知什麼會崩潰”。因為以前從未被鎖定過,所以開發人員根本不知道應用程式實際需要的許可權是什麼。

解決這一問題的辦法是從第一天起就將許可權控制放入原始碼控制中。這樣,當開發人員測試應用程式時,如果許可權不正確,一開始就會失敗。這反過來又意味著,當到達QA階段時,所有許可權問題都已解決,不存在許可權缺失的猜測或風險。

容器化

根據專案的性質不同,資料庫的容器化是一個可選步驟。我將通過兩個案例說明箇中原因。

對於第一個案例,這個專案有一個非常簡單的分支結構:有一個“dev”分支,它會匯入QA分支,QA分支又會匯入階段化分支和最終的生產分支。這可以通過四個共享資料庫實現,管道中的每個階段,各使用一個資料庫。

在第二個案例中,我們有一組重要的特性分支。每個重要的特性分支被進一步細分為開發和QA分支。每個特性必須通過QA檢驗,才能夠成為合併到主分支的候選特性,因此每個重要特性都需要有各自的測試環境。

在第一個案例中,即使web服務確實需要容器,容器化也可能是浪費時間。對於第二個用例,容器化某種程度上則是至關重要的。如果沒有真正的容器(例如Docker),那麼在建立新的重要特性分支時,至少可以根據需要生成新環境的部署指令碼(例如AWS或Azure)。

關於作者

Jonathan Allen在90年代後期開始為一家醫療診所開發MIS專案,逐步將該專案從Access和Excel升級成企業級的解決方案。在花了五年時間為金融業編寫自動化交易系統之後,他成為了多個專案的顧問,其中包括自動化倉庫的UI、癌症研究軟體的中間層,以及一家大型房地產保險公司的大資料需求。在空閒時間,他喜歡研究和記錄源於16世紀的武術。

檢視英文原文: How to Source Control Your Databases for DevOps

相關文章