開發與維運

【巡檢問題分析與最佳實踐】MongoDB 內存高問題

往期分享

RDS MySQL

RDS MySQL 實例空間問題

RDS MySQL 內存使用問題

RDS MySQL 活躍線程數高問題

RDS MySQL 慢SQL問題

RDS MySQL 實例IO高問題

RDS MySQL 小版本升級最佳實踐

RDS PostgreSQL

RDS PostgreSQL 實例IO高問題

RDS PostgreSQL 慢SQL問題

RDS PostgreSQL CPU高問題

RDS SQL Server

RDS SQL Server 磁盤IO吞吐高問題

RDS SQL Server CPU高問題

RDS SQL Server 空間使用問題

Redis

Redis 流控問題

Redis 內存高問題

Redis CPU高問題

概述

阿里雲數據庫MongoDB的內存使用率是一個非常重要的監控指標,然而MongoDB的內存使用率並非是簡單的越小越好,而突發的高內存使用率也需要引起用戶足夠的關注,因為它往往是業務側的變動引起,突發的內存飆升容易引起實例OOM。

MongoDB 進程啟動後,除了跟普通進程一樣,加載 binary、依賴的各種library 到內存,其作為一個DBMS,還需要負責客戶端連接管理、請求處理、數據庫元數據、存儲引擎等很多工作,這些工作都涉及內存的分配與釋放。默認情況下,MongoDB使用Google tcmalloc作為內存分配器,內存佔用的大頭主要是"Wiredtiger存儲引擎"與 "客戶端連接及請求的處理"。

本文將由淺入深幫您查看、分析和優化雲數據庫MongoDB的內存使用。

查看內存使用

部署架構為副本集模式下,提供有多種查看內存使用的方法,您可以根據自身需求,由淺入深瞭解MongoDB的內存使用情況。

部署架構為分片集群模式下,各個Shard的內存使用與副本集保持一致;Config Server僅僅存儲配置元數據,基本上不會造成內存瓶頸,一般可以忽略;Mongos路由節點的內存使用往往與聚合結果集,連接數大小,元數據大小有關。

監控圖分析

MongoDB副本集由多種角色組成,一個角色可能對應一個或多個物理節點。阿里雲MongoDB對用戶暴露Primary和Secondary節點,另外還提供有隻讀實例的角色。可以通過點擊"監控信息",選擇對應的角色查看MongoDB內存有關的監控情況,如下圖:

image.png

除了總體內存使用率外,MongoDB的WiredTiger引擎的內存使用也至關重要,下圖展示了WiredTiger引擎的內存使用情況,其中"maximum bytes configured"即當前配置的引擎cache的總大小,與配置文件中的cacheSizeGB保持一致;"bytes_read_into_cache"表示每秒從磁盤將數據加載到內存的大小 ,"bytes_written_from_cache"表示每秒從cache中刷新髒頁到磁盤的大小,這兩個值越大,反應內存使用越吃緊,磁盤壓力相應也會越大。

image.png

命令行查看

除了使用阿里雲控制檯提供的監控圖查看以外,您也可以直接使用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引擎佔用的內存大小

image.png

查看當前wiredTiger引擎的cache dirty比例

您可以通過mongostat或者阿里雲數據庫自治服務DAS實時查看當前的cache dirty,目前阿里雲數據庫MongoDB暫不支持cache dirty歷史情況查看。

image.png

更多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

image.png

關於更多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過程中遇到更多可能存在內存洩漏的場景,可以與技術支持人員聯繫。

Leave a Reply

Your email address will not be published. Required fields are marked *