Cloudflare 最佳實踐:如何通過 Go PGO 回收 CPU

Golang 1.20 在 go 編譯器引入了對配置文件引導優化 (PGO) 的支持。這允許指導編譯器根據系統的真實行爲引入優化。在 Cloudflare 的可觀察性團隊中,我們維護着一些基於 Go 的服務,這些服務在全球範圍內使用數千個內核,因此,即使宣傳的節省 2-7%,也能大幅減少我們的 CPU 佔用空間,而且實際上是免費的。這將減少我們內部服務的 CPU 使用率,釋放這些資源來滿足客戶請求,從而顯着改善我們的客戶體驗。在這篇文章中,我將介紹我們爲試驗 PGO 而創建的流程 - 在我們的生產基礎設施中收集代表性配置文件,然後部署新的 PGO 二進制文件並測量 CPU 節省。

PGO 如何運作?

PGO 本身並不是 Go 專用的工具,儘管它相對較新。PGO 允許您從生產運行的程序中獲取 CPU 配置文件,並使用它來優化該程序生成的程序集。這包括一系列不同的優化,例如更積極地內聯頻繁使用的函數、重新設計分支預測以支持更常見的分支,以及重新排列生成的代碼以將熱路徑集中在一起以節省 CPU 緩存交換。

使用 PGO 的一般流程是編譯非 PGO 二進制文件並將其部署到生產環境,從生產環境中的二進制文件收集 CPU 配置文件,然後使用該 CPU 配置文件編譯第二個二進制文件。CPU 配置文件包含 CPU 在執行程序時花費最多時間的示例,這爲編譯器在做出優化程序決策時提供了有價值的上下文。例如,編譯器可能會選擇內聯一個被多次調用的函數以減少函數調用開銷,或者可能會選擇展開一個跳轉特別頻繁的循環。至關重要的是,使用生產環境中的配置文件可以比任何前期預製方法更有效地指導編譯器。

一個實際的例子

在可觀察性團隊中,我們運營一個稱爲 “wshim” 的系統。Wshim 是一項在我們的每臺邊緣服務器上運行的服務,爲來自我們內部 Cloudflare Workers 的遙測提供推送網關。由於該服務在每臺服務器上運行,並且每次調用內部工作線程時都會調用該服務,因此 wshim 需要大量 CPU 時間來運行。爲了準確跟蹤用量,我們將 wshim 放入其自己的 cgroup 中,並使用 cadvisor 公開與其使用的資源相關的 Prometheus 指標。

在部署 PGO 之前,wshim 在全球使用了 3000 多個核:

container_cpu_time_seconds 是我們的內部指標,用於跟蹤 CPU 在全球範圍內運行 wshim 所花費的時間。即使節省 2%,我們也會爲客戶返還 60 個核心,從而使 Cloudflare 網絡更加高效。

部署 PGO 的第一步是從我們全球的服務器收集代表性配置文件。我們遇到的第一個問題是,我們運行數千臺服務器,每臺服務器在給定時間點都有不同的使用模式 - 在白天提供大量請求的數據中心與位於本地的不同數據中心具有不同的使用模式。因此,準確選擇要分析的服務器對於收集良好的配置文件供 PGO 使用至關重要。

最後,我們決定最好的樣本來自那些負載較重的數據中心——這些數據中心的 wshim 最慢部分最爲明顯。更進一步,我們只會從我們的一級數據中心收集配置文件。這些數據中心爲我們人口最稠密的地區提供服務,通常是我們最大的數據中心,並且在高峯時段通常負載非常重。

具體來說,我們可以通過查詢 Thanos 基礎設施來獲取高 CPU 服務器列表:

num_profiles="1000"
# Fetch the top n CPU users for wshim across the edge using Thanos.
cloudflared access curl "https://thanos/api/v1/query?query=topk%28${num_profiles}%2Cinstance%3Acontainer_cpu_time_seconds_total%3Arate2m%7Bapp_name%3D%22wshim.service%22%7D%29&dedup=true&partial_response=true" --compressed | jq '.data.result[].metric.instance' -r > "${instances_file}"

Go 使用 pprof 使實際獲取 CPU 配置文件變得異常簡單。爲了讓我們的工程師在生產中調試他們的系統,我們提供了一種輕鬆檢索生產配置文件的方法,我們可以在此處使用。Wshim 提供了一個 pprof 接口,我們可以使用它來檢索配置文件,並且我們可以使用 bash 再次收集這些配置文件:

# For every instance, attempt to pull a CPU profile. Note that due to the transient nature of some data centers
# a certain percentage of these will fail, which is fine, as long as we get enough nodes to form a representative sample.
while read instance; do fetch-pprof $instance –port 8976 –seconds 30' > "${working_dir}/${instance}.pprof" & done < "${instances_file}"
wait $(jobs -p)

然後使用 go tool 將所有收集到的配置文件合併爲一個:

# Merge the fetched profiles into one.
go tool pprof -proto "${working_dir}/"*.pprof > profile.pprof

我們將使用這個合併的配置文件來編譯 pprof 二進制文件。因此,我們將其提交到我們的存儲庫中,以便它與 wshim 的所有其他部署組件一起存在:

~/cf-repos/wshim ± master
23/01/2024 10:49:08 AEDT❯ tree pgo
pgo
├── README.md
├── fetch-profiles.sh
└── profile.pprof

並更新我們的 Makefile 以將 -pgo 標誌傳遞給 go build 命令:

build:
       go build -pgo ./pgo/profile.pprof -o /tmp/wshim ./cmd/wshim

之後,我們可以像任何其他版本一樣構建和部署新的 PGO 優化版本的 wshim。

結果

部署新版本後,我們可以檢查 CPU 指標,看看是否有任何有意義的節省。衆所周知,資源使用情況很難比較。由於 wshim 的 CPU 使用率隨着任何給定服務器接收的流量而變化,因此它具有許多潛在的混淆變量,包括一天中的不同時間段、一年中的某一天以及是否存在影響數據中心的任何主動攻擊。話雖這麼說,我們可以採取一些數字來很好地表明任何潛在的節省。

首先,我們可以查看部署前後 wshim 的 CPU 使用情況。這可能會受到兩組之間的時間差異的影響,但它顯示出相當大的改進。由於我們的版本只需不到兩個小時即可滾動到每個 1 級數據中心,因此我們可以使用 PromQL 的 “offset” 運算符來衡量差異:

這表明在發佈後,我們使用的核比發佈前減少了約 97 個,減少了約 3.5%。這似乎與官方文檔給出的數字在 2% 到 14% 之間一致。

我們可以查看的第二個數字是一週中不同日期的同一時間的使用情況。發佈前 7 天的平均使用率爲 3067.83 個核,而發佈後 7 天爲 2996.78 個核,節省了 71 個 CPU。不如我們前面計算的 97 個 CPU 節省那麼多,但仍然相當可觀!

這似乎證明了 PGO 的好處——在完全不改變代碼的情況下,我們成功地節省了幾臺服務器的 CPU 時間。

未來的工作

看看這些初步結果似乎確實證明了 PGO 的優勢——在不更改任何代碼的情況下節省多臺服務器的 CPU,通過釋放資源更好地滿足客戶請求。然而,這裏肯定還有更多工作要做。尤其:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/yrMkH5xOLUS8O9V2H8nfmA