2020 年 5 月,我們與 OnGres 合作,對 GitLab 上的 Postgres 集群進行版本大更新,從 9.6 版本升級到 11 版本。升級全部在維護窗口內運行,沒有絲毫差錯;更新中所有涉及的內容、計劃、測試,以及全流程自動化,全部進行拆包,只為實現一次近乎完美的 PostgreSQL 升級。
本次版本更新,我們面臨的最大難題在于如何利用一個規劃完善的 pg_upgrade,方便且高效地對整體項目進行重要版本升級。為此,我們需要制定一個回滾計劃,以保證 12 節點集群的 6 TB 數據一致的同時,優化恢復目標時間(RTO)后的容量,為 600 萬用戶提供每秒 300000 次的聚合交易服務。
解決工程難題的最佳方案是按照藍圖和設計文檔行事。在創建藍圖的過程中,我們需要定義目標問題,評估最合適的解決方案,并考慮每個解決方案的優缺點。
在此,我們附上為這個項目準備的藍圖鏈接。
我們為什么要升級PostgreSQL
我們決定在 GitLab 13.0 中停止對 PostgreSQL 10.0 的支持,而 PostgreSQL 9.6 版本將在 2021 年 11 月 EOL(項目終止),因此,我們需要采取相應的行動。
以下是 PostgreSQL9.6 和 11 版本之間的主要區別:
- 表分區支持 LIST、RANGE,以及 HASH
- 存儲過程支持事務
- 即時編譯(JIT)加快查詢表達式的運行速度
- 并行查詢,增加并行化數據定義功能
- 新版本的 PostgreSQL 繼承了版本 10 中的“邏輯復制——分發數據的發布 / 訂閱框架”,該功能可以使日后的升級更加順滑,簡化了其他相關流程
- 基于 Quorum 的提交(commit),確保事務能在集群中指定節點進行提交
- 提升了通過分區表進行查詢的性能
環境與架構
PostgreSQL 集群的基礎架構容量由 12 個服務于 OLTP 以及異步管道的 n1-highmem-96 GCP 示例組成,同時還有兩個不同規格的 BI 節點,每個節點都有 96 個 CPU 內核以及 614GB 的 RAM。HA 集群通過 Patroni 進行管理和配置,以保證 Consul 集群及其所有復制體在異步流復制中,使用復制槽和 WAL 對 GCS 存儲桶進行復制工作時的 leader 選舉一致性。
我們的配置目前使用的是 Patroni HA 解決方案,它會不斷收集集群、leader 檢測,以及節點可用性的關鍵信息。該解決方案采用 Consul 的 DNS 服務等關鍵功能來實現,進而更新 PgBouncer 端點,確保讀寫和只讀流量使用不同架構。
GitLab.com 架構
因為 HA 的緣故,其中兩個復制體不在只讀服務器列表池中,而是由 Consul DNS 支持,服務于 API。對 GitLab 架構的幾次改進后,我們得以將項目整體降到 7 個節點。
此外,我們的整個集群平均每周要處理大約 181000 個交易每秒,如下圖所示,流量會在周一有明顯增加,并在周一至周五 / 六內保持該吞吐量。我們需要讓維護影響到盡量少的用戶,因此流量數據的統計對于設置合適的維護窗口至關重要。
GitLab.com 上連接數量統計
項目整體在全天中最忙碌的時刻可以到達 25000 交易每秒。
GitLab.com 上 commit 數量統計
與此同時,項目處理的交易峰值可以到達每秒 30 萬次交易,GitLab.com 能到達每秒 6 萬次連接。
我們的升級需求
在生產環境進行升級前,我們首先確定了一些需求:
- PostgreSQL 版本 11 上不能有回歸。我們開發了一個自定義基準測試來運行更廣泛的回歸測試,目標是識別 PostgreSQL 11 中潛在的查詢性能下降。
- 升級應當針對整體項目,并在維護窗口內完成。
- 使用 pg_upgrade 升級,其依賴于物理層面,而非邏輯或者復制。
- 保留一個 9.6 版本的集群樣本。并非所有節點都需要升級,我們應保留一些 9.6 版本的節點以備回滾。
- 升級應全自動化,以降低人類失誤的可能性。
- 全部數據庫升級的維護窗口只有 30 分鐘。
- 升級應留有記錄并將其發布。
項目
為使生產升級能順利運行,我們將項目劃分為以下幾個階段:
第一階段:在封閉環境中開發自動化
- 開發 ansible-playbook ,并在 staging 上備份的 PostgreSQL 環境中進行測試。
- 獨立環境的使用讓我們可以隨時停止、啟動,或者恢復備份,也讓我們專注開發,并得以將環境隨時回滾到升級前。
- 我們使用 staging 上的備份在環境中進行項目升級,在這個過程中,我們也遇到一些諸如在遷移數據庫的過程中如何監視不同程序之類的挑戰。
第二階段:在 staging 中將升級開發與配置管理進行分段式融合
- 在 Chef 中集成配置管理,并運行數據庫磁盤中的一個快照(可用于還原更新前狀態)。
- 通知用戶,本次維護窗口將力爭對他們工作的影響降到最低,并在沒有數據損失風險的情況下進行安全升級。
- 在對配置管理進行迭代和集成測試后,我們開始在 staging 上運行端到端測試。這些測試內容是在內部公開的,所以其他共享這個環境的團隊會知道 staging 在這段時間暫時不可用。
第三階段:在 staging 上測試端到端升級
- 正式運行前對環境的檢查。我們有時候會在這一步發現認證的問題,有時候也會做一些能提升測試效率的小調整。
- 停止 GitLab 上所有應用和流量,在 CloudFlare 和 HA-proxy 上添加維護模式,停止包括數據庫、sidekiq、workhorse、WEB-API 等一切能訪問數據庫的應用。
- 升級集群中六個節點中的三個。與生產中部分場景的策略類似,我們同樣準備了回滾方案。
- 為 PostgreSQL 的更新運行 ansible-playbook。首先是數據庫 leader 節點,之后是一些二級節點。
- 升級之后:我們在 ansible-playbook 中運行了一些自動化測試,用以檢測復制數據與原數據是否相符。
- 接下來啟動應用程序,讓我們的 QA 團隊能運行一些測試。他們在升級后的數據庫上運行了本地單元測試,我們對負面結果進行了調查。
- 測試結束后,我們再次停止程序運行,并將 staging 集群還原到 9.6 版本,將升級過后的節點關閉到版本 11,最后啟動舊版集群。Patroni 會 promote 其中一個節點,啟動應用后集群就可以收到流量反饋。我們將 Chef 的配置恢復到集群 9.6 版本后重建數據庫,留出六個節點為下次測試做準備。
我們總共在 staging 中運行過 7 次測試,并通過反饋不斷完善程序。
第四階段:升級進入生產環境
生產環境的步驟與 staging 中類似,我們計劃遷移八個節點,留下四個作為備份。
- 執行項目前期檢查
- 宣布維護開始
- 運行 ansible-playbook 以停止流量和應用
- 運行 ansible-playbook 以進行 PostgreSQL 升級
- 開始驗證測試并恢復流量。我們只運行了必需的測試,才能在短暫的維護窗口內完成所有內容
回滾計劃只會在數據庫不一致或者 QA 測試出錯時才調用,以下是具體步驟:
- 停止 PostgreSQL 11 集群
- 還原 Chef 中配置到 PostgreSQL 9.6
- 用 9.6 版本中的四個節點初始化集群。通過這四個節點,我們可以在流量較低的時候恢復 GitLab 上的活動。
- 開始接收流量,借此可以盡量減少停機時間。
- 使用在維護期間和升級前的磁盤快照恢復其他節點
升級中的所有步驟都在用于運行項目的模板中有詳細說明
pg_upgrade 運行原理
pg_upgrade 讓我們可以在不用 dump/reload 策略,不用更多停機時間的情況下,將 PostgreSQL 數據文件升級到日后的主要版本。
正如在 PostgreSQL 官方文檔中所寫,pg_upgrade 工具通過避免執行 dump/restore 的方法來升級 PostgreSQL 版本。這里有幾點細節需要注意:PostgreSQL 的主要版本會添加新功能,這些新功能經常會改變系統表的布局,但內部數據存儲格式基本會保持不變。如果某次主要版本升級改變了數據格式,那么就不能繼續用 pg_upgrade 了。因此,我們必須要先驗證這些版本之間都有什么變化。
還有一點很重要,任何外部模塊都必須兼容二進制,雖然你并不能通過 pg_upgrade 來檢查這點。對 GitLab 的更新來說,我們在升級前先卸載了 postgres_exporter 等視圖及拓展,以便在升級后重新創建,出于兼容性考慮,還要稍作修改。
在更新之前,必須先安裝新版本的二進制文件。新的 PostgreSQL 二進制文件及拓展文件都裝在需要升級的主機中。
pg_upgrade 在使用時有很多選項。我們選擇在 Leader 節點上使用 pg_upgrade 的鏈接模式,因為維護窗口很短暫,只有兩個小時。這種模式可以通過 inode 硬鏈接文件,避免了復制 6TB 文件的麻煩。缺點則是舊數據集群無法回滾到 9.6 版本。我們保存了 9.6 版本的副本和 GCP 快照作為后備計劃的回滾路徑。因為從頭開始重建副本是不可能,所以我們選擇使用 rsync 增量功能來進行升級。pg_upgrade 的官方文檔也有寫:“從主服務器上位于舊數據庫集群目錄和新數據庫集群目錄上方的目錄中,在每個備用服務器的 primary 上運行此命令。”
ansible-playbook 對于這一步的實現,是通過從 leader 節點到每一個副本都有一個任務,在新舊數據目錄中的父目錄中觸發 rsync 命令。
回歸測試的基準
任何的遷移或數據庫升級都需要在最終的生產升級前進行回歸測試。對團隊來說,數據庫測試在升級過程中是至關重要的一步,根據生產過程中的查詢數額來進行性能測試,將結果存到 pg_stat_statement 表中。這些都是在同一個數據集中運行的,一次是在 9.6 版本,一次是在 11 版本的迭代。這一步過程可以在下面這個公共的 issue 中找到:
- 工具的準備
- 創建測試環境
- 計算容量
- 使用 JMeter 工具運行基準測試
最后,根據 OnGres 在這一基準測試上的工作,GitLab 將在未來跟進新的基準測試。
- 主要生產數據庫集群的能力評估
- 數據庫容量及飽和度分析
升級過程:全自動就完事了
在升級項目中,升級團隊堅持使用自動化和基礎架構及代碼工具(IaC)。所有流程必須全部自動化,以減少在維護窗口的人為失誤。pg_upgrade 所有的運行步驟都可以在這個 GitLab 的 pg_upgrade 的模板 issue 上找到詳細說明。
GitLab.com 的環境由 Terraform 和 Chef 共同管理,所有的升級自動化都是用 Ansible 2.9 的 playbook 和 roles 編寫的,我們用了兩個 ansible-playbook 來完成升級自動化:
一個 ansible-playbook 控制流量和應用:
- 將 Cloudflare 設置為維護狀態,不接受流量:
- 停止 HA-proxy
- 停止訪問數據庫的中間件:Sidekiq、Workhorse、WEB-API
另一個 ansible-playbook 運行升級過程:
- 協調所有數據庫和連接池的流量
- 控制 Patroni 集群和 Consul 實例
- 在主節點和次級節點上執行升級
- 收集升級后的統計數據
- 使用 Chef 同步更改,以保持配置管理的完整性
- 驗證集群的完整性和狀態
- 執行 GCP 快照
- (可能的)回滾過程
playbook 以交互方式逐個運行所有任務,讓程序員得以在任意給定執行點跳過或暫停程序。參與 staging 測試和迭代的所有團隊成員都要過目升級過程中的所有步驟,staging 環境讓我們通過演習提前找到升級過程中潛在的漏洞。而執行和迭代 staging 中自動化過程則讓我們實現了 PostgreSQL 9.6 版本至 11 版本的基本無缺陷升級。
為完成本次的版本升級,GitLab 的 QA 團隊將部分測試中發現的問題反饋給我們,這一部分的工作可以在這條 issue 中找到。
PostgreSQL 預升級的步驟
升級工作的第一步是“預升級”,這里涉及到預留給回滾的示例。我們做了相應分析,以確保新的集群可以不丟失吞吐量的情況下,以 8 個示例為起點,保留 4 個通過標準 Patroni 集群同步的 9.6 版本示例,為后續可能需要的回滾情況準備(共計 12 個實例)。
在這個階段,我們還需要停止依賴 PostgreSQL 的服務,諸如 PgBouncer、Chef 客戶端,以及 Patroni 服務。
在正式開始更新前,必須要告知 Patroni,避免任何虛假 leader 選舉,通過 GCP 快照(通過對應低級備份API 獲得)進行一致的備份,并通過運行Chef 應用新的設置。
PostgreSQL 升級階段
首先,停止所有節點。
然后,運行以下檢查:
- pg_upgrade 版本檢查
- 驗證所有節點都已同步,并且不再接受任何流量
一旦主節點數據升級完畢,就會觸發 rsync 進程以同步所有副本數據。在升級完成后,啟動 Patroni 服務,這樣所有副本都能輕松更新至新集群的配置。
通過 Chef 安裝二進制文件,新集群在版本方面的設置是在同一個 MR 中定義的,MR 源自 GitLab.com,可以安裝用于數據庫中的拓展項。
最后一個階段則包括恢復流量、運行初始的真空期,以及最后的啟動 PgBouncer 和 Chef 客戶端服務。
遷移日
到了最后,我們為運行生產線上升級做好了萬全準備,團隊在周日一早 8:45 UTC 開始會議(對有的人來說是晚上)。服務將最多下線兩小時,當最終的通知下達后,工程團隊終于可以開始進行。
升級過程由停止所有流量及相關服務開始,這是為了避免用戶在更新中途訪問網站。
下面圖表顯示在服務更新之前,維護期間(圖標中的空白部分)、以及維護結束、流量恢復后的流量和 HTTP 數據統計。
GitLab.com 上的數據統計圖,從維護開始到結束
整個流程共花費四個小時,其中僅包括兩小時斷線時間。
此外,我們錄下了PostgreSQL 更新的全過程并發布在 GitLab Unfiltered 上。
原文鏈接:
https://about.gitlab.com/blog/2020/09/11/gitlab-pg-upgrade/