往期分享
RDS MySQL
RDS PostgreSQL
RDS SQL Server
Redis
概述
阿里雲數據庫MongoDB的內存使用率是一個非常重要的監控指標,然而MongoDB的內存使用率並非是簡單的越小越好,而突發的高內存使用率也需要引起用戶足夠的關注,因為它往往是業務側的變動引起,突發的內存飆升容易引起實例OOM。
MongoDB 進程啟動後,除了跟普通進程一樣,加載 binary、依賴的各種library 到內存,其作為一個DBMS,還需要負責客戶端連接管理、請求處理、數據庫元數據、存儲引擎等很多工作,這些工作都涉及內存的分配與釋放。默認情況下,MongoDB使用Google tcmalloc作為內存分配器,內存佔用的大頭主要是"Wiredtiger存儲引擎"與 "客戶端連接及請求的處理"。
本文將由淺入深幫您查看、分析和優化雲數據庫MongoDB的內存使用。
查看內存使用
部署架構為副本集模式下,提供有多種查看內存使用的方法,您可以根據自身需求,由淺入深瞭解MongoDB的內存使用情況。
部署架構為分片集群模式下,各個Shard的內存使用與副本集保持一致;Config Server僅僅存儲配置元數據,基本上不會造成內存瓶頸,一般可以忽略;Mongos路由節點的內存使用往往與聚合結果集,連接數大小,元數據大小有關。
監控圖分析
MongoDB副本集由多種角色組成,一個角色可能對應一個或多個物理節點。阿里雲MongoDB對用戶暴露Primary和Secondary節點,另外還提供有隻讀實例的角色。可以通過點擊"監控信息",選擇對應的角色查看MongoDB內存有關的監控情況,如下圖:
除了總體內存使用率外,MongoDB的WiredTiger引擎的內存使用也至關重要,下圖展示了WiredTiger引擎的內存使用情況,其中"maximum bytes configured"即當前配置的引擎cache的總大小,與配置文件中的cacheSizeGB保持一致;"bytes_read_into_cache"表示每秒從磁盤將數據加載到內存的大小 ,"bytes_written_from_cache"表示每秒從cache中刷新髒頁到磁盤的大小,這兩個值越大,反應內存使用越吃緊,磁盤壓力相應也會越大。
命令行查看
除了使用阿里雲控制檯提供的監控圖查看以外,您也可以直接使用MongoDB Shell命令查看和分析內存佔用情況,如下示例:
PRIMARY> db.serverStatus().mem { "bits" : 64, "resident" : 13116, "virtual" : 20706, "supported" : true } //resident表示該mongod物理節點佔用的物理內存大小,單位為M //virtual表示該mongod物理節點佔用的虛擬內存大小,單位為M
另外,MongoDB serverStatus中自帶了大量的關於wiredTiger引擎和tcmalloc的內存使用使用詳情,可以通過的db.serverStatus().wiredTiger.cache和db.serverStatus().tcmalloc進行查看,後文我們會重點展開講解。
更多serverStatus的信息展示詳情建議參考:https://docs.mongodb.com/manual/reference/command/serverStatus/
內存使用詳細分析
引擎內存使用
MongoDB使用tcmalloc作為內存分配器,其中WiredTiger引擎佔用的Cache是其中最大的一部分,引擎內存最大使用內存由配置參數cachesize決定。為了兼容性能和安全性,阿里雲數據庫MongoDB為cachesize設置的大小默認為申請內存的60%左右,由於存在向上取整等因素,詳細的cachesize大小設置可參照下表:
class_code | 規格大小 | 實際大小 | Wt cacheSizeGB |
dds.mongo.small | 1024 | 2048 | 1 |
dds.mongo.mid | 2048 | 4096 | 1 |
dds.mongo.standard | 4096 | 7168 | 2 |
dds.mongo.large | 8192 | 12288 | 5 |
dds.mongo.xlarge | 16384 | 24576 | 10 |
dds.mongo.2xlarge | 32768 | 49152 | 20 |
dds.mongo.4xlarge | 65536 | 98304 | 40 |
dds.mongo.monopolize | 225280 | 225280 | 96 |
dds.mongo.2xmonopolize | 450560 | 450560 | 264 |
mongo.x8.medium | 16384 | 16384 | 10 |
mongo.x8.large | 32768 | 32768 | 20 |
mongo.x8.xlarge | 65536 | 65536 | 40 |
mongo.x8.2xlarge | 131072 | 131072 | 77 |
mongo.x8.4xlarge | 262144 | 262144 | 154 |
dds.sn4.8xlarge.3 | 131072 | 131072 | 64 |
通常情況下,即使數據量遠超過cachesize的大小,wiredTiger也不會將cachesize耗盡,如果超過了cachesize配置大小的95%,那表示系統性能已經處於較為危險的狀態。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淘汰,請求延時就會增加,這時可以考慮"擴大內存"或者"擴大IOPS"。
查看當前wiredTigerd引擎佔用的內存大小
查看當前wiredTiger引擎的cache dirty比例
您可以通過mongostat或者阿里雲數據庫自治服務DAS實時查看當前的cache dirty,目前阿里雲數據庫MongoDB暫不支持cache dirty歷史情況查看。
更多mongostat的使用方式可以參考:https://docs.mongodb.com/v4.2/reference/program/mongostat/
連接和請求佔用的內存
如果實例的連接數很大,可能會消耗相當一部分的內存,這是因為:
-
- 每個連接,後端啟動一個線程處理這個連接上的請求,每個線程最多1MB的線程棧開銷,平時一般在幾十KB - 幾百KB 之間。
- 每個tcp連接在內核層面有read、write buffer,極端情況下可能漲到16MB,由tcp內核參數tcp_rmem和tcp_wmem等確定,這塊的內存使用用戶無需關心。但併發連接越多,默認套接字緩存越大,則tcp佔用內存越大。
- 每接收到一個請求,會有個請求上下文,整個過程中可能分配很多臨時buffer,比如請求包、應答包、從引擎數據的buffer、排序的臨時buffer等,這些在請求結束都會釋放,但這個釋放只是說歸還給內存分配器 tcmalloc,tcmalloc優先會還到自己的cache裡;然後逐步再歸還給操作系統。所以很多情況下,內存使用率高的原因是tcmalloc未及時歸還內存至操作系統,這一塊最大可能達到數十GB。
關於tcmalloc未歸還OS的內存大小,可以通過以下命令查看:
tcmalloc cache大小=pageheap_free_bytes + total_free_byte
關於更多mongodb tcmalloc的更多內容參考:https://mongoing.com/archives/34751
元數據信息佔用的內存
MongoDB的database、collection、index等內存元數據等,如果集合和index數量很多,這一塊佔用的內存也不容忽視。尤其在MongoDB4.0以前的版本,全量邏輯備份期間可能打開非常多的文件句柄並且未能及時歸還OS導致內存快速上漲,或者低版本的MongoDB在大量刪除collection後可能未能刪除文件句柄導致內存洩漏。
阿里雲MongoDB建議庫表數量控制在10W以內,並使用MongoDB4.0以上的內核版本,更多關於這塊的詳細分析參考:
https://jira.mongodb.org/browse/WT-4336
創建index過程中的內存消耗
正常的業務數據寫入情況下,Secondary會維持一個最大約256M的buffer用於數據回放。在Create Index方面,當Primary創建index完成後,Secondary節點回放過程中可能消耗更多的內存。在MongoDB4.2以前,在Primary上通過非background的方式create index,後端回放創建index是串行的,最多可能消耗500M內存;而MongoDB4.2以後默認廢棄了background選項,允許Secondary並行回放create index,那就會消耗更多的內存,多個index同時build時可能導致實例OOM。
更多關於create index期間可能造成的內存消耗參考:
https://docs.mongodb.com/manual/core/index-creation/#index-build-impact-on-database-performance
https://docs.mongodb.com/manual/core/index-creation/#index-build-process
PlanCache內存佔用
在某些場景下,比如一個SQL可能存在的執行計劃非常多,這時plancache可能會消耗比較多的內存,在高版本MongoDB中可以通過以下命令查看PlanCache佔用的內存大小,默認大小未Byte。
mgset-xxx:PRIMARY> db.serverStatus().metrics.query.planCacheTotalSizeEstimateBytes
NumberLong(750695)
更多內容參考我們給官方提的bug鏈接:https://jira.mongodb.org/browse/SERVER-48400
內存使用的通用優化思路
首先需要強調一點,內存優化並非是為了儘可能減少內存使用,而是在保證系統性能完全沒問題的前提下,內存使用儘可能穩定和夠用,從而在機器資源和性能中達到一個最佳的折衷。
阿里雲MongoDB幫用戶指定了比較合適的CacheSize大小,不建議也無法修改該值。
控制併發連接數,這個是最直接有效的方法,根據性能測試結果,100個長連接足以壓滿數據庫,默認 MongoDB driver也是跟後端建立100的連接池。當連接到客戶端(ECS機器)很多時,就需要降低每個客戶端的連接池大小,一般建議跟整個數據庫建立的長連接控制在1000以內,連接太多,一是內存開銷,另外多線程上下文的開銷也會增加,影響請求處理延時。
降低單次請求的內存開銷,比如通過建索引,減少 COLLSCAN、內存排序等。
另外,在連接數合適的情況下內存佔用持續走高,建議升級內存配置,從而避免可能存在OOM和瘋狂的evict導致系統性能急劇下滑。
最後,如果您在使用阿里雲MongoDB過程中遇到更多可能存在內存洩漏的場景,可以與技術支持人員聯繫。