GitHubMySQL升級8.0覆盤【譯】

15年前,GitHub作爲一個Ruby on Rails應用程序開始,只有一個MySQL數據庫。從那時起,GitHub已經發展了其MySQL架構,以滿足平臺的擴展和彈性需求,包括構建高可用性,實現測試自動化和分區數據。今天,MySQL仍然是GitHub基礎設施的核心部分,也是我們選擇的關係數據庫。

這是我們如何將1200多臺MySQL主機升級到8.0的故事。在不影響我們的服務水平目標(SLO)的情況下升級車隊並不是一個小的功能規劃,測試和升級本身花了一年多的時間,並在GitHub內的多個團隊之間進行協作。

升級動機

爲什麼要升級到MySQL 8.0?隨着MySQL 5.7的生命週期即將結束,我們將集羣升級到下一個主要版本MySQL 8.0。我們還希望MySQL的版本能夠獲得最新的安全補丁,錯誤修復和性能增強。我們還希望測試8.0中的新特性並從中受益,包括Instant DDL、不可見索引和壓縮bin日誌等。

GitHub的MySQL基礎設施

在我們深入討論如何進行升級之前,讓我們先來看看我們的MySQL基礎設施:

  • 我們的集羣由1200+主機組成。它是Azure虛擬機和我們數據中心中的裸機主機的組合。
  • 我們在50多個數據庫集羣中存儲300多TB的數據,每秒處理550萬次查詢。
  • 每個羣集都配置爲具有高可用性,即主羣集加副本羣集設置。
  • 我們的數據是分區的。我們利用水平和垂直分片來擴展我們的MySQL集羣。我們有MySQL集羣來存儲特定產品領域的數據。我們也有水平分片的Vitess集羣,用於超過單主MySQL集羣的大型域區域。
  • 我們擁有一個龐大的工具生態系統,包括Percona Toolkit、gh-ost、orchestrator、freno和用於操作車隊的內部自動化。

所有這些都歸結爲一個多樣化和複雜的部署,需要在維護我們的SLO的同時進行升級。

準備旅程

作爲GitHub的主要數據存儲,我們對可用性有很高的要求。由於我們集羣的規模和MySQL基礎設施的關鍵性,我們對升級過程有一些要求:

  • 我們必須能夠升級每個MySQL數據庫,同時遵守我們的服務水平目標(SLO)和服務水平協議(SLA)。
  • 我們無法解釋測試和確認階段的所有失效模式。因此,爲了保持在SLO內,我們需要能夠回滾到MySQL 5.7的早期版本,而不會中斷服務。
  • 我們的MySQL車隊有非常多樣化的工作負載。爲了降低風險,我們需要原子地升級每個數據庫集羣,並圍繞其他主要更改進行調度。這意味着升級過程將是一個漫長的過程。因此,我們從一開始就知道,我們需要能夠持續運行混合版本的環境。

升級的準備工作於2022年7月開始,即使在升級單個生產數據庫之前,我們也有幾個里程碑要達到。

準備基礎架構以進行升級

我們需要爲MySQL 8.0確定適當的默認值,並執行一些基準性能測試。由於我們需要操作兩個版本的MySQL,我們的工具和自動化需要能夠處理混合版本,並瞭解5.7和8.0之間的新語法,不同語法或棄用語法。

確保應用程序兼容性

我們爲所有使用MySQL的應用程序添加了MySQL 8.0到持續集成(CI)。我們在CI中並行運行MySQL 5.7和8.0,以確保在長時間的升級過程中不會出現退化。我們在CI中檢測到各種錯誤和不兼容性,幫助我們刪除任何不支持的配置或功能,並轉義任何新的保留關鍵字。

爲了幫助應用程序開發人員過渡到MySQL 8.0,我們還啓用了一個選項,可以在GitHub Codespaces中選擇MySQL 8.0預構建容器進行調試,並提供MySQL 8.0開發集羣進行額外的預生產測試。

溝通和透明度

我們使用GitHub Projects創建了一個滾動日曆,以便在內部溝通和跟蹤我們的升級計劃。我們爲應用程序團隊和數據庫團隊創建了跟蹤檢查表的問題模板,以協調升級。

升級計劃

爲了滿足我們的可用性標準,我們有一個漸進的升級策略,在整個過程中允許檢查點和回滾。

步驟1:滾動複製副本升級

我們從升級單個副本開始,並在它仍然離線時進行監視,以確保基本功能穩定。然後,我們啓用了生產流量,並繼續監控查詢延遲、系統指標和應用程序指標。我們逐漸將8.0複製副本上線,直到我們升級整個數據中心,然後在其他數據中心迭代。爲了回滾,我們保留了足夠的5.7副本在線,但我們禁用了生產流量,以開始通過8.0服務器爲所有讀取流量提供服務。

步驟2:更新複製拓撲

一旦所有隻讀流量都通過8.0副本提供服務,我們就按如下方式調整了複製拓撲:

  • 8.0主候選項被配置爲直接在當前5.7主項下複製。
  • 在該8.0複製副本的下游創建了兩個複製鏈:
    • 一組只有5.7個副本(不提供流量,但在回滾時準備就緒)。
    • 一組只有8.0個副本(服務流量)。
  • 在我們進入下一步之前,拓撲只在這種狀態下保持很短的時間(最多幾個小時)。

步驟3:將MySQL 8.0主機升級爲主機

我們選擇不在主數據庫主機上進行直接升級。相反,我們將通過 Orchestrator 執行優雅故障轉移,將一個 MySQL 8.0 副本提升爲主副本。在那個時刻,複製拓撲結構由一個8.0主服務器和兩個連接到它的複製鏈組成:一個離線的5.7副本(用於回滾)和一個正在服務的8.0副本。

Replastrator還配置爲將5.7主機列爲潛在故障轉移候選主機的黑名單,以防止在發生計劃外故障轉移時意外回滾。

步驟4:升級面向內部的實例類型

一旦我們確認集羣不需要回滾併成功升級到8.0,我們就刪除了5.7服務器。驗證包括至少一個完整的24小時交通週期,以確保在交通高峯期間沒有問題。

回滾能力

保持升級策略安全的核心部分是保持回滾到MySQL 5.7之前版本的能力。對於讀取副本,我們確保有足夠的5.7副本保持在線以服務於生產流量負載,並且如果8.0副本的性能不佳,則通過禁用它們來啓動回滾。對於主服務器,爲了在不丟失數據或服務中斷的情況下進行回滾,我們需要能夠在8.0和5.7之間維護向後數據複製。

MySQL支持從一個版本複製到下一個更高的版本,但不明確支持反向複製(MySQL複製兼容性)。當我們測試將一個8.0主機升級到我們的臨時集羣上的主主機時,我們看到所有5.7複製副本上的複製都中斷了。有幾個問題我們需要克服:

  1. 在MySQL 8.0中, utf8mb4 是默認字符集,並使用更現代的 utf8mb4_0900_ai_ci 排序規則作爲默認值。MySQL 5.7的早期版本支持 utf8mb4_unicode_520_ci 排序規則,但不支持Unicode utf8mb4_0900_ai_ci 的最新版本。
  2. MySQL 8.0引入了管理權限的角色,但MySQL 5.7中不存在此功能。當一個8.0實例被提升爲集羣中的主實例時,我們遇到了問題。我們的配置管理正在擴展某些權限集,以包括角色語句並執行它們,這破壞了5.7副本中的下游複製。我們通過在升級窗口期間臨時調整受影響用戶的定義權限解決了此問題。

爲了解決字符排序規則不兼容的問題,我們必須將默認字符編碼設置爲 utf8 ,將排序規則設置爲 utf8_unicode_ci 。

對於GitHub.com monolith,我們的Rails配置確保了字符排序的一致性,並使得將客戶端配置標準化到數據庫變得更加容易。因此,我們非常有信心能夠爲我們最關鍵的應用程序保持向後複製。

挑戰

在我們的測試、準備和升級過程中,我們遇到了一些技術挑戰。

Vitess處理

我們使用 Vitess 對關係型數據進行水平分片。在很大程度上,升級我們的 Vitess 集羣與升級 MySQL 集羣並無太大不同。我們已經在 CI 中運行 Vitess,因此能夠驗證查詢兼容性。在我們針對分片集羣的升級策略中,我們逐個升級每個分片。Vitess 代理層 VTgate 廣告展示了 MySQL 的版本信息,某些客戶端行爲取決於這個版本信息。例如,某個應用程序使用了一個 Java 客戶端,在 5.7 服務器上禁用了查詢緩存——因爲在 8.0 中移除了查詢緩存,對他們來說這會產生阻塞錯誤。因此,一旦給定 keyspace 下的單個 MySQL 主機完成升級,我們必須確保更新 VTgate 的設置以展示 8.0 版本。

複製延遲

在我們測試的早期,我們在MySQL中遇到了一個複製錯誤,該錯誤已在8.0.28上修復:

Replication: If a replica server with the system variable `replica_preserve_commit_order` = 1 set was used under intensive load for a long period, the instance could run out of commit order sequence tickets. Incorrect behavior after the maximum value was exceeded caused the applier to hang and the applier worker threads to wait indefinitely on the commit order queue. The commit order sequence ticket generator now wraps around correctly. Thanks to Zhai Weixiang for the contribution. (Bug #32891221, Bug #103636)

如果將系統變量 `replica_preserve_commit_order` 設置爲1的副本服務器長時間在密集負載下使用,則該實例可能會用完提交順序序列票證。超過最大值後的錯誤行爲導致應用程序掛起,並且應用程序工作線程在提交順序隊列中無限期等待。提交順序票證生成器現在可以正確地環繞。感謝翟偉祥的貢獻。(Bug#32891221,錯誤#103636)

我們正好符合所有的標準擊中這個錯誤。

  • 我們使用 replica_preserve_commit_order 是因爲我們使用基於GTID的複製。
  • 我們的許多集羣都長時間處於密集負載狀態,當然還有所有最關鍵的集羣。我們的大多數集羣都是非常重寫的。

由於這個錯誤已經在新版本修復,我們只需要確保我們部署的MySQL版本高於8.0.28。

我們還觀察到在 MySQL 8.0 中,導致複製延遲的大量寫操作問題變得更加嚴重。這使得我們更加重視避免大量的寫入突發情況。在 GitHub,我們使用 freno 根據複製延遲來限制寫入工作負載。

測試pass,生產fail

我們知道在生產環境中第一次出現問題是不可避免的,因此我們採用了逐步升級副本的策略。我們遇到了通過CI的查詢,但在遇到實際工作負載時會在生產中失敗。最值得注意的是,我們遇到了一個問題,帶有大型 WHERE IN 子句的查詢會使MySQL崩潰。我們有一個包含數萬個值的大型 WHERE IN 查詢。在這些情況下,我們需要在繼續升級過程之前重寫查詢。查詢採樣有助於跟蹤和檢測這些問題。在GitHub上,我們使用SolarwindsTM(VividCortex),一個SaaS數據庫性能監視器,用於查詢可觀察性。

學習和收穫

在測試、性能調優和解決已發現的問題之間,整個升級過程花了一年多的時間,並涉及來自GitHub多個團隊的工程師。我們將整個集羣升級到MySQL 8.0 GitHub這次升級突出了我們的可觀測性平臺、測試計劃和回滾功能的重要性。測試和逐步部署策略使我們能夠及早發現問題,並降低主要升級遇到新故障模式的可能性。

雖然有一個漸進的推出策略,但我們仍然需要在每一步都回滾的能力,我們需要可觀察性來識別信號,以指示何時需要回滾。啓用回滾最具挑戰性的方面是保持從新的8.0主副本到5.7副本的反向複製。我們瞭解到,Trilogy客戶端庫的一致性給了我們更多的連接行爲的可預測性,並讓我們相信來自主Rails單體的連接不會中斷向後複製。

然而,對於我們的一些MySQL集羣,它們與來自不同框架/語言的多個不同客戶端的連接,我們看到向後複製在幾個小時內就會中斷,這縮短了回滾的機會窗口。幸運的是,這些情況很少,我們沒有在需要回滾之前複製中斷的實例。但對我們來說,這是一個教訓,即擁有已知和良好理解的客戶端連接配置是有好處的。委員會強調了制定準則和框架以確保這種組合的一致性的重要性。

先前對數據進行分區的努力取得了成效 — 它使我們能夠針對不同的數據領域進行更有針對性的升級。這是很重要的,因爲一個失敗的查詢會阻塞整個集羣的升級,而不同的工作負載分區允許我們分階段升級,減少在過程中遇到的未知風險的影響範圍。這裏的權衡是,這也意味着我們的 MySQL 集羣規模變大了。

上一次 GitHub 升級 MySQL 版本時,我們有五個數據庫集羣,而現在我們有 50 個以上的集羣。爲了成功升級,我們不得不在觀測性、工具和管理集羣的流程上進行投資。

結論

MySQL升級只是我們必須執行的例行維護之一 — 對於我們運行的任何軟件,擁有一個升級路徑都至關重要。作爲升級項目的一部分,我們開發了新的流程和操作能力,以成功完成MySQL版本的升級。然而,在升級過程中仍存在許多需要手動干預的步驟,我們希望減少完成未來MySQL升級所需的工作量和時間。

我們預計隨着 GitHub.com 的增長,我們的集羣規模也將繼續增長,並且我們有將數據進一步分區的目標,這將隨着時間推移增加我們的 MySQL 集羣數量。爲運營任務構建自動化和自愈能力可以幫助我們未來擴展 MySQL 運營。我們相信,投資可靠的集羣管理和自動化將使我們能夠擴展 GitHub,並跟上所需的維護,從而提供更可預測和更具彈性的系統。

這個項目的經驗爲我們的MySQL自動化奠定了基礎,爲未來更高效地進行升級鋪平了道路,但依然保持了同樣的關懷和安全性水平。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章