MongoDB記憶體使用分析和優化

FeelTouch發表於2019-03-26

MongoDB記憶體佔用點

Mongod 程式啟動後,除了跟普通程式一樣,載入 binary、依賴的各種library 到記憶體,其作為一個DBMS,還需要負責客戶端連線管理,請求處理,資料庫後設資料、儲存引擎等很多工作,這些工作都涉及記憶體的分配與釋放,預設情況下,MongoDB 使用 Google tcmalloc 作為記憶體分配器,記憶體佔用的主要是「儲存引擎」與 「客戶端連線及請求的處理」。

儲存引擎

MongoDB 3.2 及以後,預設使用 WiredTiger 儲存引擎,可通過 cacheSizeGB 選項配置 WiredTiger 引擎使用記憶體的上限,一般建議配置在系統可用記憶體的60%左右(預設配置)。

舉個例子,如果 cacheSizeGB 配置為 10GB,可以認為 WiredTiger 引擎通過tcmalloc分配的記憶體總量不會超過10GB。為了控制記憶體的使用,WiredTiger 在記憶體使用接近一定閾值就會開始做淘汰,避免記憶體使用滿了阻塞使用者請求。

目前有4個可配置的引數來支援 wiredtiger 儲存引擎的 eviction 策略(一般不需要修改),其含義是:

引數 預設值 含義
eviction_target 80 當 cache used 超過 eviction_target,後臺evict執行緒開始淘汰 CLEAN PAGE
eviction_trigger 95 當 cache used 超過 eviction_trigger,使用者執行緒也開始淘汰 CLEAN PAGE
eviction_dirty_target 5 當 cache dirty 超過 eviction_dirty_target,後臺evict執行緒開始淘汰 DIRTY PAGE
eviction_dirty_trigger 20 當 cache dirty 超過 eviction_dirty_trigger, 使用者執行緒也開始淘汰 DIRTY PAGE

在這個規則下,一個正常執行的 MongoDB 例項,cache used 一般會在 0.8 * cacheSizeGB 及以下,偶爾超出問題不大;如果出現 used>=95% 或者 dirty>=20%,並一直持續,說明記憶體淘汰壓力很大,使用者的請求執行緒會阻塞參與page淘汰,請求延時就會增加,這時可以考慮「擴大記憶體」或者 「換更快的磁碟提升IO能力」。

TCP 連線及請求處理

MongoDB Driver 會跟 mongod 程式建立 tcp 連線,並在連線上傳送資料庫請求,接受應答,tcp 協議棧除了為連線維護socket後設資料,每個連線會有一個read buffer及write buffer,使用者收發網路包,buffer的大小通過如下sysctl系統引數配置,分別是buffer的最小值、預設值以及最大值,詳細解讀可以google。

net.ipv4.tcp_wmem = 8192  65536  16777216
net.ipv4.tcp_rmem = 8192  87380  16777216

redhat7(redhat6上並沒有匯出這麼詳細的資訊) 上通過 ss -m 可以檢視每個連線的buffer的資訊,如下是一個示例,讀寫 buffer 分別佔了 2357478bytes、2626560bytes,即均在2MB左右;500個類似的連線就會佔用掉 1GB 的記憶體;buffer 佔到多大,取決於連線上傳送/應答的資料包的大小、網路質量等,如果請求應答包都很小,這個buffer也不會漲到很大;如果包比較大,這個buffer就更容易漲的很大。

tcp    ESTAB      0      0                       127.0.0.1:51601                                 127.0.0.1:personal-agent
   skmem:(r0,rb2357478,t0,tb2626560,f0,w0,o0,bl0)

除了協議棧上的記憶體開銷,針對每個連線,Mongod 會起一個單獨的執行緒,專門負責處理這條連線上的請求,mongod 為處理連線請求的執行緒配置了最大1MB的執行緒棧,通常實際使用在幾十KB左右,通過 proc 檔案系統看到這些執行緒棧的實際開銷。 除了處理請求的執行緒,mongod 還有一系列的後臺執行緒,比如主備同步、定期重新整理 Journal、TTL、evict 等執行緒,預設每個執行緒最大ulimit -s(一般10MB)的執行緒棧,由於這批執行緒數量比較固定,佔的記憶體也比較可控。

# cat /proc/$pid/smaps

7f563a6b2000-7f563b0b2000 rw-p 00000000 00:00 0
Size:              10240 kB
Rss:                  12 kB
Pss:                  12 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:        12 kB
Referenced:           12 kB
Anonymous:            12 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB

執行緒在處理請求時,需要分配臨時buffer儲存接受到的資料包,為請求建立上下文(OperationContext),儲存中間的處理結果(如排序、aggration等)以及最終的應答結果等。

當有大量請求併發時,可能會觀察到 mongod 使用記憶體上漲,等請求降下來後又慢慢釋放的行為,這個主要是 tcmalloc 記憶體管理策略導致的,tcmalloc 為效能考慮,每個執行緒會有自己的 local free page cache,還有 central free page cache;記憶體申請時,按 local thread free page cache ==> central free page cache 查詢可用記憶體,找不到可用記憶體時才會從堆上申請;當釋放記憶體時,也會歸還到 cache 裡,tcmalloc 後臺慢慢再歸還給 OS, 預設情況下,tcmalloc 最多會 cache min(1GB,1/8 * system_memory) 的記憶體, 通過 setParameter.tcmallocMaxTotalThreadCacheBytesParameter 引數可以配置這個值,不過一般不建議修改,儘量在訪問層面做調優)

tcmalloc cache的管理策略,MongoDB 層暴露了幾個引數來調整,一般不需要調整,如果能清楚的理解tcmalloc原理及引數含義,可做針對性的調優;MongoDB tcmalloc 的記憶體狀態可以通過 db.serverStatus().tcmalloc 檢視,具體含義可以看 tcmalloc 的文件。重點可以關注下 total_free_bytes,這個值告訴你有多少記憶體是 tcmalloc 自己快取著,沒有歸還給 OS 的。

mymongo:PRIMARY> db.serverStatus().tcmalloc
{
    "generic" : {
        "current_allocated_bytes" : NumberLong("2545084352"),
        "heap_size" : NumberLong("2687029248")
    },
    "tcmalloc" : {
        "pageheap_free_bytes" : 34529280,
        "pageheap_unmapped_bytes" : 21135360,
        "max_total_thread_cache_bytes" : NumberLong(1073741824),
        "current_total_thread_cache_bytes" : 1057800,
        "total_free_bytes" : 86280256,
        "central_cache_free_bytes" : 84363448,
        "transfer_cache_free_bytes" : 859008,
        "thread_cache_free_bytes" : 1057800,
        "aggressive_memory_decommit" : 0,
        ...
    }
}

記憶體使用優化

合理配置 WiredTiger cacheSizeGB

  • 如果一個機器上只部署 Mongod,mongod 可以使用所有可用記憶體,則是用預設配置即可。
  • 如果機器上多個mongod混部,或者mongod跟其他的一些程式一起部署,則需要根據分給mongod的記憶體配額來配置 cacheSizeGB,按配額的60%左右配置即可。

控制併發連線數

TCP連線對 mongod 的記憶體開銷上面已經詳細分析了,很多同學對併發有一定誤解,認為「併發連線數越高,資料庫的QPS就越高」,實際上在大部分資料庫的網路模型裡,連線數過高都會使得後端記憶體壓力變大、上下文切換開銷變大,從而導致效能下降。

MongoDB driver 在連線 mongod 時,會維護一個連線池(通常預設100),當有大量的客戶端同時訪問同一個mongod時,就需要考慮減小每個客戶端連線池的大小。mongod 可以通過配置 net.maxIncomingConnections 配置項來限制最大的併發連線數量,防止資料庫壓力過載。

配置 SWAP

官方文件上的建議配置一下swap,避免mongod因為記憶體使用太多而OOM。

開啟 SWAP 與否各有優劣,SWAP開啟,在記憶體壓力大的時候,會利用SWAP磁碟空間來緩解記憶體壓力,此時整個資料庫服務會變慢,但具體變慢到什麼程度是不可控的。不開啟SWAP,當整體記憶體超過機器記憶體上線時就會觸發OOM killer把程式幹掉,實際上是在告訴你,可能需要擴充套件一下記憶體資源或是優化對資料庫的訪問了。

是否開啟SWAP,實際上是在「好死」與「賴活著」的選擇,個人覺得,對於一些重要的業務場景來說,首先應該為資料庫規劃足夠的記憶體,當記憶體不足時,「及時調整擴容」比「不可控的慢」更好。

其他

  • 儘量減少記憶體排序的場景,記憶體排序一般需要更多的臨時記憶體
  • 主備節點配置差距不要過大,備節點會維護一個buffer(預設最大256MB)用於儲存拉取到oplog,後臺從buffer裡取oplog不斷重放,當備同步慢的時候,這個buffer會持續使用最大記憶體。
  • 控制集合及索引的數量,減少databse管理後設資料的記憶體開銷;集合、索引太多,後設資料記憶體開銷是一方面的影響,更多的會影響啟動載入的效率、以及執行時的效能。

參考:https://yq.aliyun.com/articles/685044?spm=a2c4e.11153940.blogrightarea60553.9.2a0e2e23YE5Bln

相關文章