2020 年 5 月,我們與 OnGres 合作,對 GitLab 上的 Postgres 集群進(jìn)行版本大更新,從 9.6 版本升級到 11 版本。升級全部在維護(hù)窗口內(nèi)運(yùn)行,沒有絲毫差錯;更新中所有涉及的內(nèi)容、計(jì)劃、測試,以及全流程自動化,全部進(jìn)行拆包,只為實(shí)現(xiàn)一次近乎完美的 PostgreSQL 升級。
本次版本更新,我們面臨的最大難題在于如何利用一個規(guī)劃完善的 pg_upgrade,方便且高效地對整體項(xiàng)目進(jìn)行重要版本升級。為此,我們需要制定一個回滾計(jì)劃,以保證 12 節(jié)點(diǎn)集群的 6 TB 數(shù)據(jù)一致的同時,優(yōu)化恢復(fù)目標(biāo)時間(RTO)后的容量,為 600 萬用戶提供每秒 300000 次的聚合交易服務(wù)。
解決工程難題的最佳方案是按照藍(lán)圖和設(shè)計(jì)文檔行事。在創(chuàng)建藍(lán)圖的過程中,我們需要定義目標(biāo)問題,評估最合適的解決方案,并考慮每個解決方案的優(yōu)缺點(diǎn)。
在此,我們附上為這個項(xiàng)目準(zhǔn)備的藍(lán)圖鏈接。
我們?yōu)槭裁匆塒ostgreSQL
我們決定在 GitLab 13.0 中停止對 PostgreSQL 10.0 的支持,而 PostgreSQL 9.6 版本將在 2021 年 11 月 EOL(項(xiàng)目終止),因此,我們需要采取相應(yīng)的行動。
以下是 PostgreSQL9.6 和 11 版本之間的主要區(qū)別:
- 表分區(qū)支持 LIST、RANGE,以及 HASH
- 存儲過程支持事務(wù)
- 即時編譯(JIT)加快查詢表達(dá)式的運(yùn)行速度
- 并行查詢,增加并行化數(shù)據(jù)定義功能
- 新版本的 PostgreSQL 繼承了版本 10 中的“邏輯復(fù)制——分發(fā)數(shù)據(jù)的發(fā)布 / 訂閱框架”,該功能可以使日后的升級更加順滑,簡化了其他相關(guān)流程
- 基于 Quorum 的提交(commit),確保事務(wù)能在集群中指定節(jié)點(diǎn)進(jìn)行提交
- 提升了通過分區(qū)表進(jìn)行查詢的性能
環(huán)境與架構(gòu)
PostgreSQL 集群的基礎(chǔ)架構(gòu)容量由 12 個服務(wù)于 OLTP 以及異步管道的 n1-highmem-96 GCP 示例組成,同時還有兩個不同規(guī)格的 BI 節(jié)點(diǎn),每個節(jié)點(diǎn)都有 96 個 CPU 內(nèi)核以及 614GB 的 RAM。HA 集群通過 Patroni 進(jìn)行管理和配置,以保證 Consul 集群及其所有復(fù)制體在異步流復(fù)制中,使用復(fù)制槽和 WAL 對 GCS 存儲桶進(jìn)行復(fù)制工作時的 leader 選舉一致性。
我們的配置目前使用的是 Patroni HA 解決方案,它會不斷收集集群、leader 檢測,以及節(jié)點(diǎn)可用性的關(guān)鍵信息。該解決方案采用 Consul 的 DNS 服務(wù)等關(guān)鍵功能來實(shí)現(xiàn),進(jìn)而更新 PgBouncer 端點(diǎn),確保讀寫和只讀流量使用不同架構(gòu)。

GitLab.com 架構(gòu)
因?yàn)?HA 的緣故,其中兩個復(fù)制體不在只讀服務(wù)器列表池中,而是由 Consul DNS 支持,服務(wù)于 API。對 GitLab 架構(gòu)的幾次改進(jìn)后,我們得以將項(xiàng)目整體降到 7 個節(jié)點(diǎn)。
此外,我們的整個集群平均每周要處理大約 181000 個交易每秒,如下圖所示,流量會在周一有明顯增加,并在周一至周五 / 六內(nèi)保持該吞吐量。我們需要讓維護(hù)影響到盡量少的用戶,因此流量數(shù)據(jù)的統(tǒng)計(jì)對于設(shè)置合適的維護(hù)窗口至關(guān)重要。

GitLab.com 上連接數(shù)量統(tǒng)計(jì)
項(xiàng)目整體在全天中最忙碌的時刻可以到達(dá) 25000 交易每秒。

GitLab.com 上 commit 數(shù)量統(tǒng)計(jì)
與此同時,項(xiàng)目處理的交易峰值可以到達(dá)每秒 30 萬次交易,GitLab.com 能到達(dá)每秒 6 萬次連接。
我們的升級需求
在生產(chǎn)環(huán)境進(jìn)行升級前,我們首先確定了一些需求:
- PostgreSQL 版本 11 上不能有回歸。我們開發(fā)了一個自定義基準(zhǔn)測試來運(yùn)行更廣泛的回歸測試,目標(biāo)是識別 PostgreSQL 11 中潛在的查詢性能下降。
- 升級應(yīng)當(dāng)針對整體項(xiàng)目,并在維護(hù)窗口內(nèi)完成。
- 使用 pg_upgrade 升級,其依賴于物理層面,而非邏輯或者復(fù)制。
- 保留一個 9.6 版本的集群樣本。并非所有節(jié)點(diǎn)都需要升級,我們應(yīng)保留一些 9.6 版本的節(jié)點(diǎn)以備回滾。
- 升級應(yīng)全自動化,以降低人類失誤的可能性。
- 全部數(shù)據(jù)庫升級的維護(hù)窗口只有 30 分鐘。
- 升級應(yīng)留有記錄并將其發(fā)布。
項(xiàng)目
為使生產(chǎn)升級能順利運(yùn)行,我們將項(xiàng)目劃分為以下幾個階段:
第一階段:在封閉環(huán)境中開發(fā)自動化
- 開發(fā) ansible-playbook ,并在 staging 上備份的 PostgreSQL 環(huán)境中進(jìn)行測試。
- 獨(dú)立環(huán)境的使用讓我們可以隨時停止、啟動,或者恢復(fù)備份,也讓我們專注開發(fā),并得以將環(huán)境隨時回滾到升級前。
- 我們使用 staging 上的備份在環(huán)境中進(jìn)行項(xiàng)目升級,在這個過程中,我們也遇到一些諸如在遷移數(shù)據(jù)庫的過程中如何監(jiān)視不同程序之類的挑戰(zhàn)。
第二階段:在 staging 中將升級開發(fā)與配置管理進(jìn)行分段式融合
- 在 Chef 中集成配置管理,并運(yùn)行數(shù)據(jù)庫磁盤中的一個快照(可用于還原更新前狀態(tài))。
- 通知用戶,本次維護(hù)窗口將力爭對他們工作的影響降到最低,并在沒有數(shù)據(jù)損失風(fēng)險(xiǎn)的情況下進(jìn)行安全升級。
- 在對配置管理進(jìn)行迭代和集成測試后,我們開始在 staging 上運(yùn)行端到端測試。這些測試內(nèi)容是在內(nèi)部公開的,所以其他共享這個環(huán)境的團(tuán)隊(duì)會知道 staging 在這段時間暫時不可用。
第三階段:在 staging 上測試端到端升級
- 正式運(yùn)行前對環(huán)境的檢查。我們有時候會在這一步發(fā)現(xiàn)認(rèn)證的問題,有時候也會做一些能提升測試效率的小調(diào)整。
- 停止 GitLab 上所有應(yīng)用和流量,在 CloudFlare 和 HA-proxy 上添加維護(hù)模式,停止包括數(shù)據(jù)庫、sidekiq、workhorse、WEB-API 等一切能訪問數(shù)據(jù)庫的應(yīng)用。
- 升級集群中六個節(jié)點(diǎn)中的三個。與生產(chǎn)中部分場景的策略類似,我們同樣準(zhǔn)備了回滾方案。
- 為 PostgreSQL 的更新運(yùn)行 ansible-playbook。首先是數(shù)據(jù)庫 leader 節(jié)點(diǎn),之后是一些二級節(jié)點(diǎn)。
- 升級之后:我們在 ansible-playbook 中運(yùn)行了一些自動化測試,用以檢測復(fù)制數(shù)據(jù)與原數(shù)據(jù)是否相符。
- 接下來啟動應(yīng)用程序,讓我們的 QA 團(tuán)隊(duì)能運(yùn)行一些測試。他們在升級后的數(shù)據(jù)庫上運(yùn)行了本地單元測試,我們對負(fù)面結(jié)果進(jìn)行了調(diào)查。
- 測試結(jié)束后,我們再次停止程序運(yùn)行,并將 staging 集群還原到 9.6 版本,將升級過后的節(jié)點(diǎn)關(guān)閉到版本 11,最后啟動舊版集群。Patroni 會 promote 其中一個節(jié)點(diǎn),啟動應(yīng)用后集群就可以收到流量反饋。我們將 Chef 的配置恢復(fù)到集群 9.6 版本后重建數(shù)據(jù)庫,留出六個節(jié)點(diǎn)為下次測試做準(zhǔn)備。
我們總共在 staging 中運(yùn)行過 7 次測試,并通過反饋不斷完善程序。
第四階段:升級進(jìn)入生產(chǎn)環(huán)境
生產(chǎn)環(huán)境的步驟與 staging 中類似,我們計(jì)劃遷移八個節(jié)點(diǎn),留下四個作為備份。
- 執(zhí)行項(xiàng)目前期檢查
- 宣布維護(hù)開始
- 運(yùn)行 ansible-playbook 以停止流量和應(yīng)用
- 運(yùn)行 ansible-playbook 以進(jìn)行 PostgreSQL 升級
- 開始驗(yàn)證測試并恢復(fù)流量。我們只運(yùn)行了必需的測試,才能在短暫的維護(hù)窗口內(nèi)完成所有內(nèi)容
回滾計(jì)劃只會在數(shù)據(jù)庫不一致或者 QA 測試出錯時才調(diào)用,以下是具體步驟:
- 停止 PostgreSQL 11 集群
- 還原 Chef 中配置到 PostgreSQL 9.6
- 用 9.6 版本中的四個節(jié)點(diǎn)初始化集群。通過這四個節(jié)點(diǎn),我們可以在流量較低的時候恢復(fù) GitLab 上的活動。
- 開始接收流量,借此可以盡量減少停機(jī)時間。
- 使用在維護(hù)期間和升級前的磁盤快照恢復(fù)其他節(jié)點(diǎn)
升級中的所有步驟都在用于運(yùn)行項(xiàng)目的模板中有詳細(xì)說明
pg_upgrade 運(yùn)行原理
pg_upgrade 讓我們可以在不用 dump/reload 策略,不用更多停機(jī)時間的情況下,將 PostgreSQL 數(shù)據(jù)文件升級到日后的主要版本。
正如在 PostgreSQL 官方文檔中所寫,pg_upgrade 工具通過避免執(zhí)行 dump/restore 的方法來升級 PostgreSQL 版本。這里有幾點(diǎn)細(xì)節(jié)需要注意:PostgreSQL 的主要版本會添加新功能,這些新功能經(jīng)常會改變系統(tǒng)表的布局,但內(nèi)部數(shù)據(jù)存儲格式基本會保持不變。如果某次主要版本升級改變了數(shù)據(jù)格式,那么就不能繼續(xù)用 pg_upgrade 了。因此,我們必須要先驗(yàn)證這些版本之間都有什么變化。
還有一點(diǎn)很重要,任何外部模塊都必須兼容二進(jìn)制,雖然你并不能通過 pg_upgrade 來檢查這點(diǎn)。對 GitLab 的更新來說,我們在升級前先卸載了 postgres_exporter 等視圖及拓展,以便在升級后重新創(chuàng)建,出于兼容性考慮,還要稍作修改。
在更新之前,必須先安裝新版本的二進(jìn)制文件。新的 PostgreSQL 二進(jìn)制文件及拓展文件都裝在需要升級的主機(jī)中。
pg_upgrade 在使用時有很多選項(xiàng)。我們選擇在 Leader 節(jié)點(diǎn)上使用 pg_upgrade 的鏈接模式,因?yàn)榫S護(hù)窗口很短暫,只有兩個小時。這種模式可以通過 inode 硬鏈接文件,避免了復(fù)制 6TB 文件的麻煩。缺點(diǎn)則是舊數(shù)據(jù)集群無法回滾到 9.6 版本。我們保存了 9.6 版本的副本和 GCP 快照作為后備計(jì)劃的回滾路徑。因?yàn)閺念^開始重建副本是不可能,所以我們選擇使用 rsync 增量功能來進(jìn)行升級。pg_upgrade 的官方文檔也有寫:“從主服務(wù)器上位于舊數(shù)據(jù)庫集群目錄和新數(shù)據(jù)庫集群目錄上方的目錄中,在每個備用服務(wù)器的 primary 上運(yùn)行此命令。”
ansible-playbook 對于這一步的實(shí)現(xiàn),是通過從 leader 節(jié)點(diǎn)到每一個副本都有一個任務(wù),在新舊數(shù)據(jù)目錄中的父目錄中觸發(fā) rsync 命令。
回歸測試的基準(zhǔn)
任何的遷移或數(shù)據(jù)庫升級都需要在最終的生產(chǎn)升級前進(jìn)行回歸測試。對團(tuán)隊(duì)來說,數(shù)據(jù)庫測試在升級過程中是至關(guān)重要的一步,根據(jù)生產(chǎn)過程中的查詢數(shù)額來進(jìn)行性能測試,將結(jié)果存到 pg_stat_statement 表中。這些都是在同一個數(shù)據(jù)集中運(yùn)行的,一次是在 9.6 版本,一次是在 11 版本的迭代。這一步過程可以在下面這個公共的 issue 中找到:
- 工具的準(zhǔn)備
- 創(chuàng)建測試環(huán)境
- 計(jì)算容量
- 使用 JMeter 工具運(yùn)行基準(zhǔn)測試
最后,根據(jù) OnGres 在這一基準(zhǔn)測試上的工作,GitLab 將在未來跟進(jìn)新的基準(zhǔn)測試。
- 主要生產(chǎn)數(shù)據(jù)庫集群的能力評估
- 數(shù)據(jù)庫容量及飽和度分析
升級過程:全自動就完事了
在升級項(xiàng)目中,升級團(tuán)隊(duì)堅(jiān)持使用自動化和基礎(chǔ)架構(gòu)及代碼工具(IaC)。所有流程必須全部自動化,以減少在維護(hù)窗口的人為失誤。pg_upgrade 所有的運(yùn)行步驟都可以在這個 GitLab 的 pg_upgrade 的模板 issue 上找到詳細(xì)說明。
GitLab.com 的環(huán)境由 Terraform 和 Chef 共同管理,所有的升級自動化都是用 Ansible 2.9 的 playbook 和 roles 編寫的,我們用了兩個 ansible-playbook 來完成升級自動化:
一個 ansible-playbook 控制流量和應(yīng)用:
- 將 Cloudflare 設(shè)置為維護(hù)狀態(tài),不接受流量:
- 停止 HA-proxy
- 停止訪問數(shù)據(jù)庫的中間件:Sidekiq、Workhorse、WEB-API
另一個 ansible-playbook 運(yùn)行升級過程:
- 協(xié)調(diào)所有數(shù)據(jù)庫和連接池的流量
- 控制 Patroni 集群和 Consul 實(shí)例
- 在主節(jié)點(diǎn)和次級節(jié)點(diǎn)上執(zhí)行升級
- 收集升級后的統(tǒng)計(jì)數(shù)據(jù)
- 使用 Chef 同步更改,以保持配置管理的完整性
- 驗(yàn)證集群的完整性和狀態(tài)
- 執(zhí)行 GCP 快照
- (可能的)回滾過程
playbook 以交互方式逐個運(yùn)行所有任務(wù),讓程序員得以在任意給定執(zhí)行點(diǎn)跳過或暫停程序。參與 staging 測試和迭代的所有團(tuán)隊(duì)成員都要過目升級過程中的所有步驟,staging 環(huán)境讓我們通過演習(xí)提前找到升級過程中潛在的漏洞。而執(zhí)行和迭代 staging 中自動化過程則讓我們實(shí)現(xiàn)了 PostgreSQL 9.6 版本至 11 版本的基本無缺陷升級。
為完成本次的版本升級,GitLab 的 QA 團(tuán)隊(duì)將部分測試中發(fā)現(xiàn)的問題反饋給我們,這一部分的工作可以在這條 issue 中找到。
PostgreSQL 預(yù)升級的步驟
升級工作的第一步是“預(yù)升級”,這里涉及到預(yù)留給回滾的示例。我們做了相應(yīng)分析,以確保新的集群可以不丟失吞吐量的情況下,以 8 個示例為起點(diǎn),保留 4 個通過標(biāo)準(zhǔn) Patroni 集群同步的 9.6 版本示例,為后續(xù)可能需要的回滾情況準(zhǔn)備(共計(jì) 12 個實(shí)例)。
在這個階段,我們還需要停止依賴 PostgreSQL 的服務(wù),諸如 PgBouncer、Chef 客戶端,以及 Patroni 服務(wù)。
在正式開始更新前,必須要告知 Patroni,避免任何虛假 leader 選舉,通過 GCP 快照(通過對應(yīng)低級備份API 獲得)進(jìn)行一致的備份,并通過運(yùn)行Chef 應(yīng)用新的設(shè)置。
PostgreSQL 升級階段
首先,停止所有節(jié)點(diǎn)。
然后,運(yùn)行以下檢查:
- pg_upgrade 版本檢查
- 驗(yàn)證所有節(jié)點(diǎn)都已同步,并且不再接受任何流量
一旦主節(jié)點(diǎn)數(shù)據(jù)升級完畢,就會觸發(fā) rsync 進(jìn)程以同步所有副本數(shù)據(jù)。在升級完成后,啟動 Patroni 服務(wù),這樣所有副本都能輕松更新至新集群的配置。
通過 Chef 安裝二進(jìn)制文件,新集群在版本方面的設(shè)置是在同一個 MR 中定義的,MR 源自 GitLab.com,可以安裝用于數(shù)據(jù)庫中的拓展項(xiàng)。
最后一個階段則包括恢復(fù)流量、運(yùn)行初始的真空期,以及最后的啟動 PgBouncer 和 Chef 客戶端服務(wù)。
遷移日
到了最后,我們?yōu)檫\(yùn)行生產(chǎn)線上升級做好了萬全準(zhǔn)備,團(tuán)隊(duì)在周日一早 8:45 UTC 開始會議(對有的人來說是晚上)。服務(wù)將最多下線兩小時,當(dāng)最終的通知下達(dá)后,工程團(tuán)隊(duì)終于可以開始進(jìn)行。
升級過程由停止所有流量及相關(guān)服務(wù)開始,這是為了避免用戶在更新中途訪問網(wǎng)站。
下面圖表顯示在服務(wù)更新之前,維護(hù)期間(圖標(biāo)中的空白部分)、以及維護(hù)結(jié)束、流量恢復(fù)后的流量和 HTTP 數(shù)據(jù)統(tǒng)計(jì)。

GitLab.com 上的數(shù)據(jù)統(tǒng)計(jì)圖,從維護(hù)開始到結(jié)束
整個流程共花費(fèi)四個小時,其中僅包括兩小時斷線時間。
此外,我們錄下了PostgreSQL 更新的全過程并發(fā)布在 GitLab Unfiltered 上。
原文鏈接:
https://about.gitlab.com/blog/2020/09/11/gitlab-pg-upgrade/