從 greptimedb#1733 開始,GreptimeDB 使用 Jemalloc 作為默認的內存分配器,這不僅有助於提升性能和降低內存碎片,也提供了便捷的內存分析功能。在 記一次 Rust 內存泄漏排查之旅 | 經驗總結篇 這篇文章中,我們介紹了分析 Rust 應用內存泄漏的幾種常用方法,而在本文中將詳細介紹基於 Jemalloc 的排查手段。
當您在使用或者開發 GreptimeDB 的過程中,如果遇到內存使用量異常的問題,可以參照本篇文章快速排查和定位可能存在的內存泄漏。
準備工作
安裝必要工具
安裝 flamegraph.pl 腳本
curl -s https://raw.githubusercontent.com/brendangregg/FlameGraph/master/flamegraph.pl > ${HOME}/.local/bin/flamegraph.pl
chmod +x ${HOME}/.local/bin/flamegraph.pl
export PATH=$PATH:${HOME}/.local/bin
flamegraph.pl是由 Brendan Gregg 編寫的用於可視化熱點代碼調用棧的一個 Perl 腳本。Brendan Gregg 是一位致力於系統性能優化的專家,在此感謝他開發並開源了包括flamegraph.pl在內的諸多工具。
安裝 jeprof 命令
# For Ubuntu
sudo apt install -y libjemalloc-dev
# For Fedora
sudo dnf install jemalloc-devel
對於其他操作系統,您可以通過pkgs.org來查找jeprof的依賴包。
啓用 GreptimeDB 的內存分析功能
GreptimeDB 的內存分析功能是一個默認關閉的特性,您可以通過在編譯 GreptimeDB 時打開 mem-prof feature 來啓用此功能。
cargo build --release -F mem-prof
關於是否應該默認開啓 mem-prof 特性,greptimedb#3166 正在進行討論,您也可以留下您的看法。
啓動 GreptimeDB 並啓用 mem-prof 功能
為了啓用內存分析功能,在啓動 GreptimeDB 進程時也需要設置環境變量MALLOC_CONF:
MALLOC_CONF=prof:true ./<path_to_greptime_binary> standalone start
您可以通過 curl 命令來檢查內存分析功能是否正常使用:
curl <greptimedb_ip>:4000/v1/prof/mem
如果內存分析功能已經啓用,此命令會返回類似如下的響應:
heap_v2/524288
t*: 125: 136218 [0: 0]
t0: 59: 31005 [0: 0]
...
MAPPED_LIBRARIES:
55aa05c66000-55aa0697a000 r--p 00000000 103:02 40748099 /home/lei/workspace/greptimedb/target/debug/greptime
55aa0697a000-55aa11e74000 r-xp 00d14000 103:02 40748099 /home/lei/workspace/greptimedb/target/debug/greptime
...
否則如果返回 {"error":"Memory profiling is not enabled"} ,則説明 MALLOC_CONF=prof:true 環境變量未正確設置。
關於 內存分析 API 所返回的數據格式,請參考 HEAP PROFILE FORMAT - jemalloc.net。
開始您的內存探險!
通過 curl <greptimedb_ip>:4000/v1/prof/mem 命令,您可以快速獲取 GreptimeDB 所分配的內存詳情;通過 jeprof 和 flamegraph.pl 可以將內存使用詳情可視化為火焰圖:
# 獲取內存分配詳情
curl <greptimedb_ip>:4000/v1/prof/mem > mem.hprof
# 生成內存分配的火焰圖
jeprof <path_to_greptime_binary> ./mem.hprof --collapse | flamegraph.pl > mem-prof.svg
執行完以上命令後將在當前目錄生成文件名為 mem-prof.svg 的火焰圖:
如何閲讀火焰圖
火焰圖是由 Brendan Gregg 創造的一種能夠對 CPU 開銷和內存分配詳情進行分析的利器,其生成原理是,在每次採樣內存分配事件時,記錄觸發此次內存分配的函數調用棧。在記錄足夠多次後,將每次分配的調用棧合併,從而得到每個函數調用以及其所調用的函數所分配的內存大小。
- 火焰圖的最下方為函數棧的棧底,而最上方為棧頂;
- 火焰圖中每一個格子代表一個函數調用,格子下方為此函數的調用者(caller),格子上方為此函數所調用的子函數(callee);
- 格子的寬度代表此函數及其子函數所有分配內存的總量。當我們發現某些函數的格子較寬,説明此函數分配的內存較多。如果某些函數分配內存較多但是其子函數分配卻並不多(如下圖,火焰圖中存在較寬的棧頂,即 plateaus),説明此函數自身可能存在大量分配操作;
- 火焰圖中每個格子的顏色是隨機的;
- 在瀏覽器中打開火焰圖的 SVG 文件可以交互式地點擊進入每個函數進行更加具體的分析。
快 1 倍!加速火焰圖的生成
Jemalloc 返回的堆內存詳情包含了調用棧各個函數的地址,在生成火焰圖時需要將地址翻譯成文件名和行號,而這正是耗時最長的步驟。通常在 Linux 系統下,這項工作是由 GNU Binutils 中的 addr2line 工具完成的。為了加快火焰圖生成的速度,我們可以用 glimi-rs/addr2line 來替換 Binutils 的 addr2line 工具,從而可以獲得至少 1 倍的速度提升。
git clone https://github.com/gimli-rs/addr2line
cd addr2line
cargo build --release
sudo cp /usr/bin/addr2line /usr/bin/addr2line-bak
sudo cp target/release/examples/addr2line /usr/bin/addr2line
通過內存分配差值來捕捉內存泄漏
在常見的內存泄漏場景中,內存的使用量往往是緩慢增長的。因此我們在內存增長的過程中,分別截取兩個時間點的內存使用情況,兩者之差往往指示着可能出現內存泄漏的地方。
我們可以在初始時間點採集一次內存作為基準:
curl -s <greptimedb_ip>:4000/v1/prof/mem > base.hprof
當內存緩慢增長,疑似出現內存泄漏時,再一次採集內存:
curl -s <greptimedb_ip>:4000/v1/prof/mem > leak.hprof
然後以 base.hprof 作為基準分析內存佔用並生成火焰圖:
jeprof <path_to_greptime_binary> --base ./base.hprof ./leak.hprof --collapse | flamegraph.pl > leak.svg
在使用 --base 參數指定基準生成的火焰圖中,只會包含本次採集內存分配和基準之間的內存差值的分配情況,從而能夠更加清晰地看到內存使用量的增長到底是由哪些函數調用產生的。
關於 Greptime 的小知識:
Greptime 格睿科技於 2022 年創立,目前正在完善和打造時序數據庫 GreptimeDB,格睿雲 GreptimeCloud 和可觀測工具 GreptimeAI 這三款產品。
GreptimeDB 是一款用 Rust 語言編寫的時序數據庫,具有分佈式、開源、雲原生和兼容性強等特點,幫助企業實時讀寫、處理和分析時序數據的同時降低長期存儲成本;GreptimeCloud 可以為用户提供全託管的 DBaaS 服務,能夠與可觀測性、物聯網等領域高度結合;GreptimeAI 為 LLM 量身打造,提供成本、性能和生成過程的全鏈路監控。
GreptimeCloud 和 GreptimeAI 已正式公測,歡迎關注公眾號或官網瞭解最新動態!
官網:https://greptime.cn/
GitHub: https://github.com/GreptimeTeam/greptimedb
文檔:https://docs.greptime.cn/
Twitter: https://twitter.com/Greptime
Slack: https://greptime.com/slack
LinkedIn: https://www.linkedin.com/company/greptime/