華為雲資料庫GaussDB(for Cassandra)揭祕第二期:記憶體異常增長的排查經歷

華為雲開發者社群發表於2021-06-25
摘要:華為雲資料庫GaussDB(for Cassandra) 是一款基於計算儲存分離架構,相容Cassandra生態的雲原生NoSQL資料庫;它依靠共享儲存池實現了強一致,保證資料的安全可靠。

本文分享自華為雲社群《華為雲資料庫GaussDB(for Cassandra)揭祕第二期:記憶體異常增長的排查經歷》,原文作者:Cassandra官方 。

背景介紹

華為雲資料庫GaussDB(for Cassandra) 是一款基於計算儲存分離架構,相容Cassandra生態的雲原生NoSQL資料庫;它依靠共享儲存池實現了強一致,保證資料的安全可靠。核心特點是:存算分離、低成本、高效能。

問題描述

GaussDB(for Cassandra)自研架構下遇到一些挑戰性問題,比如cpu過高,記憶體洩漏,記憶體異常增長,時延高等問題,這些也都是開發過程中遇到的典型問題。分析記憶體異常增長是一個比較大的挑戰,記憶體的異常增長對於程式來說是一個致命的問題,因為其可能觸發OOM,程式異常當機,業務中斷等結果,所以對記憶體進行合理的規劃使用及控制就顯得尤為重要。通過調整cache容量,bloom過濾器大小,以及memtable大小等等,實現效能提升,讀寫時延改善等效果。

線上下測試過程中發現核心在長時間執行後,記憶體只增不減,出現異常增長的情況,懷疑可能存在記憶體洩漏。

分析&驗證

首先根據記憶體使用,將記憶體分為堆內和堆外兩個部分,分別進行該兩塊記憶體的分析。確定有問題的記憶體是堆外記憶體,進一步對堆外記憶體分析。引入更高效的記憶體管理工具tcmalloc,解決記憶體異常增長問題。下面為具體分析驗證過程。

確定記憶體異常區域

使用jdk的jmap命令和Cassandra的監控(配置jvm.memory.*監控項)等方法,每隔1min採集jvm的堆內記憶體及程式整體記憶體。

啟動測試用例,直到核心的整體記憶體達到上限。分析採集到的堆內記憶體和程式記憶體變化曲線,發現其堆內記憶體仍保持相對穩定,未出現一直持續上漲,但期間核心的整體記憶體仍然在持續上漲,兩者的增長曲線不符。即問題應該發生在堆外記憶體。

堆外記憶體分析驗證

glibc記憶體管理

使用pmap命令列印程式的記憶體地址空間分佈,發現有大量的64MB的記憶體塊和許多記憶體碎片,該現象與glibc的記憶體分配方式有關。堆外記憶體的使用和程式整體的記憶體增長趨勢相近,初步懷疑該問題是由堆外記憶體導致。加之glibc歸還記憶體的條件苛刻,即記憶體不易及時釋放,記憶體碎片多,猜測問題和gblic有關係。當記憶體碎片過多,空閒記憶體浪費嚴重,最終程式記憶體的最大使用量會出現超過預期計劃最大值的可能,甚至出現OOM。

tcmalloc記憶體管理

引入tcmalloc記憶體管理器,代替glibc的ptmalloc記憶體管理方式。減少過多的記憶體碎片,提高記憶體使用效率,本次分析驗證採用gperftools-2.7原始碼進行tcmalloc的編譯。執行相同的測試用例,發現記憶體仍在持續上漲,但是上漲幅度較之前降低,通過pmap列印出該記憶體地址分佈情況,發現之前的小記憶體塊和記憶體碎片顯著減小,說明該工具有一定優化效果,印證了前面提到記憶體碎片過多的猜測。

但是記憶體異常增長的問題仍然存在,有點像是tcmalloc的回收不及時或者不回收導致。實際上tcmalloc的記憶體回收是比較 "reluctant" 的,主要是為了當再次需要記憶體申請時可以直接使用,減少系統呼叫次數,提高效能。基於此原因,下來進行手動呼叫其釋放記憶體介面releasefreememory。發現效果不明顯,原因暫時未知(可能確實存在沒待釋放的空閒記憶體)。

手動觸發tcmalloc的releasefreememory介面

為驗證該問題,通過設定cache容量的方式進行。

  1. 先設定cache的容量為6GB,然後將讀請求壓起來,使cache的6GB容量填滿
  2. 修改cache的容量為2GB,為快速是記憶體釋放,手動呼叫tcmalloc的releasefreememory介面,發現沒有效果,推測採用tcmalloc之後,記憶體仍然一直上漲不下跌的原因可能與該介面的有關。
  3. 在releasefreememory介面內部的多個地方記錄日誌,然後啟動程式再次測試,發現一處報錯是在進行系統呼叫madvise時有出現失敗。
  • 程式碼位置:

華為雲資料庫GaussDB(for Cassandra)揭祕第二期:記憶體異常增長的排查經歷

報錯日誌資訊:

華為雲資料庫GaussDB(for Cassandra)揭祕第二期:記憶體異常增長的排查經歷

  1. 通過該處的呼叫失敗,分析程式碼。發現tcmalloc的記憶體釋放邏輯是“round-robin”,即中間有一個span釋放失敗,則後續待釋放的span被終止,releasefreememory邏輯呼叫結束。這個就和前面的現象吻合,執行完releasefreememory介面後基本沒有效果,發現每次都是在釋放了幾十MB時,因為該介面的呼叫失敗導致釋放邏輯終止。
  2. 再次分析該系統呼叫madvise失敗原因。通過給核心的該方法打patch,發現其失敗原因是因為傳入的地址塊對應的記憶體狀態是LOCKED狀態。導致系統呼叫失敗,報錯為非法引數。
  3. 記憶體為LOCKED狀態,和該狀態相關的有程式碼呼叫mlock系統方法、系統的ulimit配置。分析相關程式碼未發現異常點。查詢系統ulimit配置,發現max locked memory 為unlimited。修改其配置為16MB,重啟Cassandra程式,再次測試,發現記憶體釋放效果顯著。
  4. 繼續執行測試,發現記憶體持續上漲的情況消失。在業務持續存在的情況下,記憶體會上漲到最高,不再上漲,保持平穩,符合記憶體計劃使用量。業務壓力減少甚至停止後,記憶體出現緩慢下降趨勢。

解決&總結

  1. 引入tcmalloc工具,優化記憶體管理。比較優秀的記憶體管理器有Google的tcmalloc和Facebook的jemalloc等
  2. 修改系統的max locked memory引數配置。

合理分配程式需要使用記憶體的最大值,並預留一定容量,對於不符合預期增長的記憶體需要進一步分析。記憶體相關問題和程式相關性較強。系統的關鍵配置需謹慎,要評估其影響。同時排查了類似的所有配置。

增加releasefreememory的命令,後端進行呼叫,優化tcmalloc hold記憶體不釋放問題。不過releasefreememory命令的執行會鎖整個pageHeap,可能導致記憶體分配請求被hang,所以需要小心執行。

後端增加可動態配置tcmalloc_release_rate的引數,來調整tcmalloc將記憶體交還給作業系統的頻率。該值的合理範圍是[0-10],0表示永遠不交還,值越大,表示交還的頻率越高,預設值是1

結語

本文通過分析開發過程中遇到的記憶體增長問題,使用更優秀的記憶體管理工具,以及更細粒度的記憶體監控,更直觀的監控資料庫執行期間的記憶體狀態,確保資料庫平穩高效能執行。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章