[譯] SQL 不是迴避 DevOps 的理由
自動化+一些原則讓測試更有效、釋出週期更短,而且商業風險更低。
by Thomas A.Limoncelli
原文地址:(https://queue.acm.org/detail.cfm?id=3300018 )
譯者按:DevOps 實踐火遍大江南北,從一線大廠擴充套件推行至傳統軟體企業,但涉及資料庫時都多少有些為難。顧介紹相關實踐,以供大家借鑑。
有朋友最近告訴筆者,“我們做不了 DevOps,我們使用了關係資料庫。“筆者聽罷,差點從椅子上摔倒。這個觀點在很多層面都是錯誤的。
“你不瞭解我的處境!”他拒絕道。“DevOps 意味著我們需要更高頻率的部署我們軟體的釋出版本!現在我們不能控制部署,我們一年只有很少的時間可以幹這個。”
筆者追問朋友當前的部署流程。
“每隔幾個月我們會有新版釋出”,他解釋道。“將其部署到生產線需要很多工作。由於我們使用 SQL,部署過程會是這樣:第一,我們踢掉所有使用者,關閉應用服務。下一步,DBA 修改資料庫的 schema。一旦他們的工作完成,新版軟體就安裝完畢,可以使用了。整個過程花費好幾個小時,所以我們一般安排在週末進行……(這讓人討厭)。如果升級失敗,我們不得不使用備份的磁帶,將一切恢復到初始狀態,然後再試一次。”
他總結道,“僅是規劃一次升級部署都需要好幾周的協商。我們通常都很難達成共識,這也是為什麼我們需要在週末來處理升級。每隔幾個月做一次都很痛苦,要面對來自方方面面的壓力!我聽說一些公司每天能做好幾次軟體釋出。如果我們那樣做,我們的應用系統就會因為升級而一直處於下線狀態了!”
這裡有很多沒有說清楚的地方。讓我先澄清一些誤解。之後再討論採用一些技術手段如何讓部署更容易。
首先,DevOps 不是一項技術,它是一套方法論。關於 DevOps 最精確的定義是,應用敏捷、精益的方法到從原始碼到部署的全過程。它是為了更快速的交付價值,或者說是用短的時間讓一個需求特性從想法轉變為生產系統的功能。更高頻率的釋出意味著減少已經就緒的特性等待上線的時間。
DevOps 不需要禁止任何特殊的資料庫技術。反過來,任何技術也不是採用和不採用 DevOps 的理由,就像使用特殊的語言不是阻礙將敏捷應有到專案中的理由。SQL 是常見的理由,但也是經不起推敲的。
很理解 DevOps 在一些人頭腦裡是如何與少 SQL 資料庫關聯在一起。在2000年到2010年左右,投資和實踐 DevOps 的公司大部分是大型網站,它們的共識是推行 NoSQL 資料庫(鍵值儲存)。然而兩者的聯絡造成了因果的混淆。這些公司還向員工免費提供豐盛的午餐,但我們都同意這不是 DevOps 的前置條件。
第二,我不確定一些人是否可以做 DevOps。你可以使用 DevOps 的技術、方法或其他。也就是說,人們已經很頻繁的使用這個詞,以致於我想我已經無法去有效討論這個問題。
朋友們有一個共識。“看”,他困惑道,“這些部署是危險的。坦白講,每次我們實施的時候都在拿公司的資料,甚至我的工作在冒險。每隔幾個月做一次已經壓力很大了,還需要更頻繁的做嗎?不!先生,那是不負責任!”
在之前的專欄中提到(小批量原則 ),當一件事情是高風險的時候,我們會傾向於更少的去嘗試做它。但與常識相悖的是,這種做法恰恰會增加風險。下次當你做危險的事情時,你應該更多的實踐,針對周圍環境而累積的變化會越來越大,越容易造成因為未知的副作用導致失敗。相應的,DevOps採取的做法是,危險的事情應該更頻繁的去做。高頻次的做法會盡早暴露大大小小的問題,而不是每年一次的大掃除。它強迫我們將過程自動化,自動測試整個過程,讓過程更流暢,這樣風險會降低。它仍人們更多參與到實踐中。實踐出精品。不再回避我們恐懼的東西,幫助我們跨越危險,克服挑戰。像任何經歷過術後恢復的人一樣,我們不斷的實踐直到它不再痛苦為止。
部署有一些固定成本。原則上,你需要逐漸將這些固定成本降低。如果增加部署頻率而不降低固定成本,則會傷害業務,這是不負責任的。
該文章其餘部分描述了兩種實踐,讓你即使使用 SQL也可以快速釋出。實施它們需要開發者、質控、運維走出各自的筒倉,彼此協作,這也是 DevOps 的實質。這樣的結果是讓業務更平滑、更少痛苦、更少壓力。
技術1:自動 Schema 更新
在這個古老的方法論中,當團隊中的專家(通常為 DBA)手工修改 schema 時,任何 schema 的變化都需要整個應用系統關閉。如果你希望實現自動化部署,你需要自動化實現 schema 的更新。
因此,應用需要管理 schema。每個 schema 的版本都需要記錄。應用從 schema V1版開始。這個值存在資料庫中(大致為1列的表,該列欄位儲存為1)。當應用啟動時,它需要知道相容 V1的schema,如果它在資料庫中找不到這個版本,它可以拒絕執行。
為了自動更新 schema,下一版本的軟體釋出時需要知道自己要求V2版的 schema,知道 SQL 命令會將 V1版的 schema 升級到 V2版。在啟動時,它看到版本為1,然後執行響應的 schema 升級命名,並將存在資料庫中的版本號升級為2,之後繼續執行應用。
執行這個操作的軟體通常還有一個儲存 SQL schema 升級命令表。這些命令以陣列儲存,索引 n 表示其是支援從Vn-1升級到 Vn。這樣,某個版本找不到也沒有關係,軟體能將資料庫恢復到任意需要的schema版本。 實際上,如果發現有沒有初始化的資料庫(如在測試環境中),它會迴圈執行若干schema 升級,直到獲取到最新的版本。不是每個軟體的釋出都需要升級 schema,因此隔離 schema 和軟體的版本號。
已經有一些開源的和商業的系統實踐了這個過程。他們的一些產品比其他人更復雜,如支援多語言、多資料庫、錯誤處理及是否支援回滾等。從一項關於”SQL 自動化更新“的研究中,你會發現更多資訊。我最熟悉的是面向.NET 程式碼的開源專案Mayflower 和麵向 Go 的Goose 。
schema 的修改會導致資料庫被鎖定幾分鐘甚至幾個小時。這會引起應用系統的超時甚至故障。現代SQL 資料庫已經減少此類問題了,需要感謝無鎖 schema 升級和線上重建索引的特性。這些特性可以在現在 SQL 資料庫產品中找到,包括開源產品如 MariaDB、MySQL、PostgresSQL。查閱相關文件,以瞭解操作時的注意事項。
一旦你的軟體使用了這些技術,採用 CI(持續整合)會變得非常容易。你的自動化測試環境可以包含測試使用舊的 schema 的資料庫並升級它,然後執行新版軟體。你的 schema 升級過程可以在釋出到生產環境前,進行數百次的測試。這會為該過程帶來更多的信心,減少schema升級的危險,解藕DBA本人直接參與到升級。他們可以享受原本屬於他們的週末了。
我對此技術最感興趣的是,你的schema可以按程式碼一起管理了。大幅減少了控制檯上的手工操作,可以在開發環境、測試環境、UAT(使用者驗收測試)環境、生產環境中不斷演練整個過程。你可以重複執行這個過程,完善它。既然它是程式碼,你可以應用程式碼管理的實踐和軟體工程的計算去管理它。
技術2:針對多 schema 編碼
在分散式計算環境中,如何升級資料庫的 schema 呢?
典型的網站系統前端是負載均衡服務,後端執行相同軟體的多個例項或副本。每個例項都承擔一部分 HTTP 負載,也訪問相同的資料庫例項。
如果軟體和資料庫 schema 緊密耦合,而軟體升級又需要資料升級 schema時,操作會變得難以操作。如果你先改變schema,應用例項會故障或者至少會因此產生混亂;你需要儘可能快的升級例項,但是其實你已經輸了這場遊戲,因為你已經在承受怒火。
為什麼不限升級應用例項呢?!悲劇的是,如果你逐一升級應用例項,最新升級的例項會不能啟動,因為它檢測到的是錯誤的 schema。只有 schema 與應用系統匹配時,你才能應用拉起,而也在這個時候停機時間才結束。
最直接的解決方案是,無視現實規律,改變資料庫的 schema,並同時升級所有應用例項的版本。如果條件真的允許你這樣做,一切真可以這樣搞定。
遺憾的是,ACM有套策略來應對現實法則,同樣大部分企業僱主也類似。這也是為什麼傳統方法是關閉整個應用,升級所有東西,然後再重新上線。這種最佳實踐延續直至IEEE 的朋友核算瞭如何暫停的過程。
無論是違法現實規律還是計劃的停機,每次停機都會引入更大的問題:你完成了許多的獨立系統變更,但只有讓系統重新執行後才知道它們是否都還正常。同樣也不知道這些累計的改變會引發什麼樣的破壞。
大爆炸式升級變更是很危險的,而每次僅做一個變更,並驗證這個變更,就會會降低風險。如果一次執行多個變更,此時碰到問題,必須逐一執行程式以確定哪個變更引起的問題。如果每次只做一個變更,即使碰到問題,排查會很簡單,也很容易回退變更。
即使是像谷歌這樣的擁有極其複雜成熟的測試技術和方法,如果不能理解預釋出環境和生產環境間細微的差別也可能會導致部署失敗。他們採用“金絲雀”釋出形式:升級其中一個例項,然後觀察是否執行是否正常,如果沒有問題,則繼續逐漸而有序的升級其餘的例項。這其實不是一項測試技術,而是應對測試不充分而採取的保障策略。需要說明的是,不是因為測試人員不優秀,而是沒有人是完美的。金絲雀技術目前已經是產業界流行的最佳實踐,而且已經被嵌入到 Kubernetes系統中。(金絲雀一詞來源於煤礦業中金絲雀。煤礦礦工通常會帶著鳥進礦,一般會選擇金絲雀,因為這種鳥對人體有害的氣體非常敏感,如果在礦裡有鳥死亡,則被視為危險的訊號,提醒大家要撤離。
由於軟體引起的問題通常與特定的 schema 相關,解決方案就是鬆耦合。在設計階段去解除耦合,讓軟體可以同時支援多個版本的 schema,以實現獨立啟用或者回滾。
第一階段是編碼是不再假定表中的欄位。用 SQL 術語說就是,SELECT 語句需要精確指出需要的欄位,而不是泛泛的使用 SELECT *。如果需要使用 SELECT *,則不要假定欄位按特定的順序排列。 LAST_NAME也許今天是第三個欄位,但明天就不一定了。
基於這個原則,從 schema 中刪除欄位也會簡單一些。新版部署後不使用相關欄位接客,一切都可以正常運轉。在所有例項都升級至新版後,再變更schema。實際上,冗餘的欄位可以忽略先,稍後再移除他們,甚至等到下次 schema 變更時再進行清理工作。
新增欄位也會變得簡單,即在第一個例項部署前在 schema 中新增新欄位即可。如前所述,我們採用技術1(應用管理自己的 schema),部署一個新發布,它會修改 schema,但不會使用這個新欄位。由於合適的事務鎖會控制住並行處理,第一個新升級的例項重啟後會更新 schema。如果有問題,則金絲雀會宕掉。你就可以修復軟體,然後實驗新的金絲雀。而回退 schema 的變更也是一個可選的方案。
由於 schema 和軟體結構,開發者可以按需啟動新欄位。過去的升級需要考慮多個團隊的需求後才能確定一個維護視窗,而現在流程解耦,所有相關方可以有序的工作,而不必相互鎖定。
更多複雜的變更需要更多的規劃。當拆分欄位,移除欄位,新增欄位等等,收益才真正體現。
第一,軟體必須按同時支援新舊 schema 的需求進行編寫,更為重要的是必須能處理變化階段。假定你需要從儲存人全名的欄位遷移至拆分為幾個單個欄位(第一個名,中間名,最後的名,頭銜等)。軟體需要檢查哪個欄位存在,且可用。在資料庫轉換過程中,它必須可用正常執行,這個過程中兩套欄位都會存在。一旦兩套欄位都存在時,一個批處理任務會將全名拆解為多個部分,然後將原欄位置為 NULL 值。程式碼需要處理這種特殊場景,即有些行已經完成轉換,而有些還沒有完成轉換。
處理這個過程方案可稱之為“五階段線上 schema 變更”。它有很多階段,包括建立新欄位,升級軟體,遷移資料,移除舊欄位。它又被成為McHenry 技術(詳見《雲系統管理實踐》),或者叫《Expand/Contract in Release It!: Design and Deploy Production》)
線上 schema 變更的五個階段
- 執行的程式碼讀寫舊 schema,即從表、檢視中選擇需要的欄位。這是初始狀態。
- 擴充套件:schema 被修改,即增加新欄位,但不刪除任何舊欄位。沒有程式碼需要變更。由於此時新欄位沒有被使用,所以如果需要回滾也不會很痛苦。
- 程式碼被修改以使用schema 中的新欄位,併發布至生產環境。如果此時發生回滾,只需回滾至第2階段。這個時候,執行資料轉換操作。
- 簽約:訪問不再使用的舊欄位的程式碼會被清除,且被髮布至生產環境。如果此時發生回滾,只需回滾至第3階段。
- 從 schema 中移除舊的不再使用的欄位。這個階段如果發生罕見的回滾事件,則資料庫可簡單回滾至第4階段。
這些技術足夠處理線上分散式系統中大部分複雜的 schema 變更。而且,每個變更都可以獨立回滾。
針對特定場景階段的數量可以裁剪。如果只是加欄位,則階段5可以忽略,因為沒有需要移除的。四、五階段可以合併或重疊。第5階段可以合併至另一次 schema 變更中的第2階段。
基於這些技術,你可以處理最複雜的 schema 變更而不會有停機時間。
總結
使用關係資料庫並不妨礙推行 DevOps。自動化 schema 管理加上一些開發原則就可以強化測試、縮短髮布週期、降低商業風險。
自動化釋出解放了我們。它將痛苦的、鴨梨山大、人工的升級過程,轉換為常規事件,且沒有事故。它減輕了商業風險,但更重要的是,建立了一個更可持續的工作環境。
當你可以充滿自信的部署新版時,你就可以更高頻率的去釋出。以前需要等數週甚至數月的新特性可以更早接觸使用者。問題修復更快。安全漏洞也及時解決。從而使企業能為客戶提供更多價值。