天天看點

更新到 MySQL 8.0,Facebook 付出的代價。。

Facebook 稱,他們最近的一次大版本更新到 MySQL 5.6 花了一年多時間才完成,還在 5.6 版上開發 LSM 樹存儲引擎,MyRocks。在更新到 5.7 的同時建構一個新的存儲引擎,會大大減慢 MyRocks 的進度,是以我們選擇繼續使用 5.6,直到 MyRocks 完成,MySQL 5.6 的壽命也即将結束,決定更新到 MySQL 8.0 。

官博介紹說,此次過程比之前的更新更具挑戰。

MySQL 是由 Oracle 公司開發的一個開源資料庫,它為 Facebook 的一些最重要的工作負載提供了動力。我們積極開發 MySQL 中的新特性,以支援不斷演化的需求。這些特性對MySQL的許多方面進行了修改,包括客戶機連接配接器、存儲引擎、優化器以及複制。為了遷移工作負載,對于每個新的 MySQL 主版本,我們都需要投入大量的時間和精力。其中的挑戰包括:

将自定義功能移植到新版本

確定主要版本之間的複制相容

最小化現有應用程式查詢所需的更改

對阻礙伺服器支援我們工作負載的性能退化進行修複。

我們最近一次的主版本更新是到 MySQL 5.6,它花了一年多的時間才推出。當5.7 版釋出時,我們還在 5.6 版上開發 LSM 樹存儲引擎和 MyRocks。在更新到 5.7 的同時建構一個新的存儲引擎,會大大減慢 MyRocks 的進度,是以我們選擇繼續使用 5.6,直到 MyRocks 完成。MySQL 8.0 釋出之際,我們正在做 MyRocks 向使用者資料庫(UDB)服務層推出的收尾。

該版本包括一些引人注目的特性,如基于寫集的并行複制和提供原子 DDL 支援的事務資料字典等。對我們來說,遷移到 8.0 還将帶來包括文檔存儲在内的,我們已經錯過的 5.7 特性。版本 5.6 的使命即将結束,我們希望在 MySQL 社群中保持活躍,尤其是在 MyRocks 存儲引擎上的工作。

8.0 中的增強功能,比如即時 DDL,可以加快 MyRocks 的模式更改,但是我們需要在 8.0 的代碼庫中使用它。考慮到更新代碼的好處,我們決定遷移到 8.0。下面将分享我們如何解決 8.0 遷移項目的難題,以及在這個過程中發現的一些驚喜。當最初确定項目範圍時,可以明确的是,遷移到 8.0 會比遷移到 5.6 或 MyRocks 更困難。

當時,我們定制的 5.6 分支有 1700 多個代碼更新檔需要移植到 8.0。在我們移植這些更改時,新的 Facebook 的 MySQL 特性和修複已被添加到5.6 的代碼庫中,進而使目标變得更加遙不可及。

我們有許多 MySQL 伺服器在生産環境中運作,為大量截然不同的應用程式提供服務。我們還有衆多管理 MySQL 執行個體的軟體架構。這些應用執行諸如收集統計資料或管理伺服器備份之類的操作。

從 5.6 更新到 8.0 完全跳過了 5.7。在 5.6 中處于活動狀态的 API 在 5.7中可能被棄用,而在 8.0 中可能會被移除,這要求我們必須更新所有使用了現已删除API的應用程式。

許多 Facebook 功能與 8.0 中的類似功能并不向前相容,需要一種棄用或遷移途徑。

MyRocks 的增強功能需要在 8.0 中運作,包括本地化分區和崩潰恢複。

1、代碼更新檔

首先我們建立了 8.0 分支,用于在開發環境中進行建構和測試。然後,我們開始從 5.6 分支移植更新檔的漫長過程。開始的時候有 1700 多個更新檔,但我們能将其組織成幾個主要類别。

我們的大多數自定義代碼都有很好的注釋和描述,是以可以很容易地确定應用程式是否仍然需要它,或者是否可以将它删除。通過特殊關鍵字或唯一變量名所啟用的功能,也使得确定關聯變得很容易,因為我們可以搜尋應用程式代碼庫來找到它們的用例。有些更新檔非常晦澀難懂,需要做調查工作 — 挖掘舊的設計文檔、郵件或代碼評審注釋,以了解它們的曆史。

我們将每個更新檔分入四類之一:

Drop:不再使用,或在8.0中具有同等功能的特性,不需要移植。

Build/Client:支援我們建構環境的非伺服器特性,修改過的 MySQL 工具,比如 mysqlbinlog,或者增加的功能,如異步用戶端 API 等,需要移植。

非 MyRocks 伺服器:mysqld 伺服器中與 MyRocks 存儲引擎無關的特性,需要移植。

MyRocks 伺服器:支援 MyRocks 存儲引擎的特性,需要移植。

我們使用電子表格跟蹤每個更新檔的狀态和相關曆史資訊,并且在删除更新檔時記錄理由。更新相同特性的多個更新檔被組在一起進行移植。移植并送出到 8.0 分支的更新檔,用 5.6 送出資訊進行了注釋。由于我們需要篩選大量的更新檔,将不可避免地出現移植狀态上的差異,這些注釋幫助我們解決了此類問題。

用戶端和伺服器類别中的每個更新檔都自然而然地成為一個軟體釋出裡程碑。随着所有與用戶端相關的更改的移植,我們能夠将用戶端工具和連接配接器代碼更新到8.0。一旦所有非 MyRocks 伺服器特性都被移植,我們就可以為 InnoDB 伺服器部署8.0 mysqld了。完成 MyRocks 伺服器特性移植使我們能夠更新 MyRocks 安裝。

有些複雜特性需要對 8.0 進行重大更改,一些方面存在很大的相容性問題。例如,上遊 8.0 binlog 事件格式與我們一些對 5.6 的定制修改不相容。Facebook 5.6 特性使用的錯誤代碼與上遊 8.0 配置設定給新特性的錯誤代碼沖突。我們最終需要修補 5.6 伺服器,以使其與 8.0 向前相容。

完成所有這些特性的移植花了幾年時間。到最終結束時,我們已經評估了 2300 多個更新檔,并将其中 1500 個移植到了 8.0 版本。

2、遷移途徑

我們将多個 mysqld 執行個體組合到一個 MySQL 副本集中。副本集中的每個執行個體都包含相同的資料,但在地理上分布到不同的資料中心,以提供資料可用性和故障切換支援。每個副本集都有一個主執行個體。其餘的執行個體都是從執行個體。主執行個體處理所有寫流量,并将資料異步複制到所有從執行個體。

由 5.6 主/5.6 從所組成的副本集開始,最終目标是包含 8.0 主/ 8.0 從的副本集。我們遵循一個類似于 UDB MyRocks migration plan 的遷移規劃。

對于每個副本集,通過一個使用 mysqldump 生成的邏輯備份,建立并添加到 8.0 的從執行個體。這些從執行個體不提供任何應用程式讀取流量;

在 8.0 從執行個體上開啟讀取流量;

允許将 8.0 從執行個體更新為主執行個體;

禁用 5.6 執行個體的讀取流量;

移除所有 5.6 執行個體。

每個副本集可以獨立地通過上述步驟進行遷移,并可根據需要停留在一個步驟上。我們将副本集分成更小的組,在組中進行每一次遷移。如果發現問題,我們可以復原到上一步。在某些情況下,副本集能夠在其它副本集開始之前到達最後一步。

為了自動化遷移大量副本集,我們需要建構新的軟體架構。可以通過簡單地更改配置檔案中的一行,将副本集組合并在每個階段中移動它們。任何遇到問題的副本集都能單獨復原。

3、基于行的複制

作為 8.0 遷移工作的一部分,我們決定将使用基于行的複制(row-based replication,RBR)作為标準。一些 8.0 特性需要 RBR,并且它簡化了 MyRocks 的移植工作。我們的大多數 MySQL 副本集已經在使用 RBR,而那些仍然運作基于語句的複制(statement-based replication,SBR)的副本集不容易遷移。這些副本集通常有不含任何高基數鍵的表。完全轉向 RBR 是一個目标,但添加主鍵所需的長尾工作的優先級往往低于其它項目。

是以,我們将 RBR 作為 8.0 的要求。在評估并向每個表添加主鍵之後,我們今年切換了最後一個 SBR 副本集。使用 RBR 還為我們提供了一個解決應用程式問題的替代解決方案,我們在将一些副本集移動到 8.0 主執行個體時遇到了這個問題,将在後面讨論。

4、自動化驗證

大多數 8.0 遷移過程都涉及使用我們的自動化架構和應用查詢來測試和驗證 mysqld 伺服器。

我們用來管理伺服器的自動化基礎架構在随着 MySQL 伺服器的增長而增長。為了確定所有 MySQL 自動化元件都與 8.0 版本相容,我們投資建構了一個測試環境,該環境利用虛拟機上的測試副本集來驗證行為。我們為 canary 編寫了在 5.6 版本和 8.0 版本上運作的每個自動化元件的內建測試,并驗證了它們的正确性。在進行此演練時,我們發現了幾個錯誤和行為差異。

當 MySQL 架構的每一部分都在我們的 8.0 伺服器上進行驗證時,我們發現并修複了(或解決了)一些有趣的問題:

解析錯誤日志、mysqldump 輸出或伺服器 show 指令的文本輸出的軟體很容易損壞。伺服器輸出的細微變化常常會暴露出工具解析邏輯中的錯誤。

8.0 的預設 utf8mb4 排序規則設定導緻 5.6 和 8.0 執行個體之間的排序規則不比對。8.0 表可能會使用新的 utf8mb4_0900 排序規則,即使對于由 5.6 的show create table生成的create語句也是如此,因為使用utf8mb4_general_ci 的 5.6 模式沒有顯式指定排序規則。這些表差異通常會導緻複制和模式驗證工具出現問題;

某些複制失敗的錯誤代碼發生了變化,我們必須修複我們的自動化程式來正确處理它們;

8.0 版本的資料字典廢棄了 table.frm 檔案,但是我們的一些自動化系統使用它們來檢測表模式的修改;

我們必須更新自動化系統,以支援 8.0 中引入的動态權限。

5、應用程式驗證

我們希望遷移對應用程式盡可能透明,但是有些應用程式的查詢會出現性能退化,或者在8.0 上會失敗。

對于 MyRocks 遷移,我們建構了一個 MySQL 影子測試架構,該架構捕獲生産流量并将其重放到測試執行個體中。對于每個應用程式工作負載,我們在 8.0 上建立了測試執行個體,并向它們回放影子流量的查詢。我們捕獲并記錄了從 8.0 伺服器傳回的錯誤,并發現了一些有趣的問題。不幸的是,并非所有這些問題都是在測試過程中發現的。例如,事務死鎖是應用程式在遷移過程中發現的。在研究不同的解決方案時,我們可以暫時将這些應用程式復原到 5.6 版本。

8.0 引入了新的保留關鍵字,其中一些關鍵字,如 groups 和 rank,與應用程式查詢中常用的表列名或别名相沖突。這些查詢沒有通過反引号轉義名稱,導緻解析錯誤。使用了自動轉義查詢中列名的軟體庫的應用程式沒有遇到這些問題,但并非所有應用程式都使用這些軟體庫。解決這個問題很簡單,但是需要時間來跟蹤生成這些查詢的應用程式屬主和代碼庫。

在 5.6 和 8.0 之間還發現了有些 REGEXP 不相容。

一些包含在 InnoDB 上的 insert ... on duplicate key 查詢的應用程式遇到了 repeatable-read 事務死鎖。5.6 有一個 bug,在 8.0 中得到了修複,但是修複增加了事務死鎖的可能性。在分析了查詢之後,我們能夠通過降低隔離級别來解決該問題。這個選項對我們來說是可用的,因為我們已經切換到基于行的複制。

我們自定義的 5.6 文檔存儲和 JSON 函數與 8.0 不相容。使用文檔存儲的應用程式需要将文檔類型轉換為文本以進行遷移。對于 JSON 函數,我們向 8.0 伺服器中添加了相容 5.6 的版本,以便應用程式以後可以遷移到 8.0 API。

我們對 8.0 伺服器的查詢和性能測試發現了一些需要立即解決的問題。

我們發現在 ACL 緩存部分出現了新的互斥争用熱點。當大量連接配接同時打開時,它們都會阻塞 ACL 檢查;

當存在大量 binlog 檔案并且 binlog 的高速寫入導緻頻繁輪換檔案時,binlog 索引通路也發現了類似的争用;

幾個涉及臨時表的查詢被中斷。這些查詢會傳回意外錯誤,或者運作時間太長以緻逾時。

記憶體使用量與 5.6 相比有所增加,特别是對于 MyRocks 執行個體,因為必須加載 8.0 中的 InnoDB 。預設的 performance_schema 設定啟用了所有工具集并消耗了大量記憶體。我們限制了記憶體使用,隻啟用了少量的工具,并對代碼進行了更改,以禁用無法手動關閉的表。

然而,并不是所有增加的記憶體都是配置設定給 performance_schema 的。我們需要檢查和修改各種 InnoDB 内部資料結構,以進一步減少記憶體占用。這一努力使 8.0 的記憶體使用率降到了可以接受的水準。

6、接下來的工作

到目前為止,8.0 的移植已經花了幾年時間。我們已将許多 InnoDB 副本集轉換為完全在 8.0 上運作。剩下的大部分都處于遷移途徑的不同階段。現在,我們的大多數定制功能都已移植到 8.0,更新到 Oracle 的次版本相對容易些,我們計劃跟上最新版本的步伐。

跳過 5.7 這樣的主版本會帶來一些問題,我們的遷移需要解決這些問題。

首先,我們無法就地更新伺服器,需要使用邏輯轉儲和還原來建構新伺服器。但是,對于非常大的 mysqld 執行個體,這可能需要在活躍生産伺服器上運作很多天,而且這個脆弱的過程可能會在完成之前被中斷。對于這些大型執行個體,我們必須修改備份和恢複系統來應對重建。

其次,檢測 API 更改要困難得多,因為 5.7 可能會向我們的應用程式用戶端發出不推薦警告,以提示修複潛在的問題。而我們需要在遷移生産工作負載之前,運作額外的影子測試來查找失敗。使用自動轉義模式對象名稱的 mysql 用戶端軟體,有助于減少相容性問題的數量。

在一個副本集中支援兩個主版本非常困難。一旦副本集将其主執行個體更新為 8.0,最好盡快禁用并移除 5.6 執行個體。應用程式使用者往往會發現隻有 8.0 支援的新特性,比如 utf8mb4_0900 排序規則,使用這些排序規則可能中斷 8.0 和 5.6 執行個體之間的複制流。

盡管我們在遷移過程中遇到了種種障礙,但我們已經看到了運作 8.0 帶來的好處。一些應用程式選擇了提早遷移到 8.0,以利用諸如文檔存儲和改進的日期時間支援等功能。我們一直在考慮如何在 MyRocks 上支援像即時DDL這樣的存儲引擎特性。總的來說,新版本大大擴充了 MySQL@Facebook 的功能。