MongoDB 如何使用内存?为什么内存满了?
最近接到多個MongoDB內(nèi)存方面的線上case及社區(qū)問題咨詢,主要集中在:
- 為什么我的 MongoDB 使用了 XX GB 內(nèi)存?
- 一個機(jī)器上部署多個 Mongod 實例/進(jìn)程,WiredTiger cache 應(yīng)該如何配置?
- MongoDB 是否應(yīng)該使用 SWAP 空間來降低內(nèi)存壓力?
MongoDB 內(nèi)存用在哪?
Mongod 進(jìn)程啟動后,除了跟普通進(jìn)程一樣,加載 binary、依賴的各種library 到內(nèi)存,其作為一個DBMS,還需要負(fù)責(zé)客戶端連接管理,請求處理,數(shù)據(jù)庫元數(shù)據(jù)、存儲引擎等很多工作,這些工作都涉及內(nèi)存的分配與釋放,默認(rèn)情況下,MongoDB 使用 Google tcmalloc 作為內(nèi)存分配器,內(nèi)存占用的大頭主要是「存儲引擎」與 「客戶端連接及請求的處理」。
存儲引擎 Cache
MongoDB 3.2 及以后,默認(rèn)使用 WiredTiger 存儲引擎,可通過?cacheSizeGB?選項配置 WiredTiger 引擎使用內(nèi)存的上限,一般建議配置在系統(tǒng)可用內(nèi)存的60%左右(默認(rèn)配置)。
舉個例子,如果?cacheSizeGB?配置為 10GB,可以認(rèn)為 WiredTiger 引擎通過tcmalloc分配的內(nèi)存總量不會超過10GB。為了控制內(nèi)存的使用,WiredTiger 在內(nèi)存使用接近一定閾值就會開始做淘汰,避免內(nèi)存使用滿了阻塞用戶請求。
目前有4個可配置的參數(shù)來支持 wiredtiger 存儲引擎的 eviction 策略(一般不需要修改),其含義是:
| eviction_target | 80 | 當(dāng) cache used 超過?eviction_target,后臺evict線程開始淘汰 CLEAN PAGE |
| eviction_trigger | 95 | 當(dāng) cache used 超過?eviction_trigger,用戶線程也開始淘汰 CLEAN PAGE |
| eviction_dirty_target | 5 | 當(dāng) cache dirty 超過?eviction_dirty_target,后臺evict線程開始淘汰 DIRTY PAGE |
| eviction_dirty_trigger | 20 | 當(dāng) cache dirty 超過?eviction_dirty_trigger, 用戶線程也開始淘汰 DIRTY PAGE |
在這個規(guī)則下,一個正常運行的 MongoDB 實例,cache used 一般會在?0.8 * cacheSizeGB?及以下,偶爾超出問題不大;如果出現(xiàn) used>=95% 或者 dirty>=20%,并一直持續(xù),說明內(nèi)存淘汰壓力很大,用戶的請求線程會阻塞參與page淘汰,請求延時就會增加,這時可以考慮「擴(kuò)大內(nèi)存」或者 「換更快的磁盤提升IO能力」。
TCP 連接及請求處理
MongoDB Driver 會跟 mongod 進(jìn)程建立 tcp 連接,并在連接上發(fā)送數(shù)據(jù)庫請求,接受應(yīng)答,tcp 協(xié)議棧除了為連接維護(hù)socket元數(shù)據(jù)為,每個連接會有一個read buffer及write buffer,用戶收發(fā)網(wǎng)絡(luò)包,buffer的大小通過如下sysctl系統(tǒng)參數(shù)配置,分別是buffer的最小值、默認(rèn)值以及最大值,詳細(xì)解讀可以google。
net.ipv4.tcp_wmem = 8192 65536 16777216 net.ipv4.tcp_rmem = 8192 87380 16777216redhat7(redhat6上并沒有導(dǎo)出這么詳細(xì)的信息) 上通過?ss -m?可以查看每個連接的buffer的信息,如下是一個示例,讀寫 buffer 分別占了 2357478bytes、2626560bytes,即均在2MB左右;500個類似的連接就會占用掉 1GB 的內(nèi)存;buffer 占到多大,取決于連接上發(fā)送/應(yīng)答的數(shù)據(jù)包的大小、網(wǎng)絡(luò)質(zhì)量等,如果請求應(yīng)答包都很小,這個buffer也不會漲到很大;如果包比較大,這個buffer就更容易漲的很大。
tcp ESTAB 0 0 127.0.0.1:51601 127.0.0.1:personal-agentskmem:(r0,rb2357478,t0,tb2626560,f0,w0,o0,bl0)除了協(xié)議棧上的內(nèi)存開銷,針對每個連接,Mongod 會起一個單獨的線程,專門負(fù)責(zé)處理這條連接上的請求,mongod 為處理連接請求的線程配置了最大1MB的線程棧,通常實際使用在幾十KB左右,通過 proc 文件系統(tǒng)看到這些線程棧的實際開銷。 除了處理請求的線程,mongod 還有一系列的后臺線程,比如主備同步、定期刷新 Journal、TTL、evict 等線程,默認(rèn)每個線程最大ulimit -s(一般10MB)的線程棧,由于這批線程數(shù)量比較固定,占的內(nèi)存也比較可控。
# cat /proc/$pid/smaps7f563a6b2000-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存儲接受到的數(shù)據(jù)包,為請求建立上下文(OperationContext),存儲中間的處理結(jié)果(如排序、aggration等)以及最終的應(yīng)答結(jié)果等。
當(dāng)有大量請求并發(fā)時,可能會觀察到 mongod 使用內(nèi)存上漲,等請求降下來后又慢慢釋放的行為,這個主要是 tcmalloc 內(nèi)存管理策略導(dǎo)致的,tcmalloc 為性能考慮,每個線程會有自己的 local free page cache,還有 central free page cache;內(nèi)存申請時,按 local thread free page cache ==> central free page cache 查找可用內(nèi)存,找不到可用內(nèi)存時才會從堆上申請;當(dāng)釋放內(nèi)存時,也會歸還到 cache 里,tcmalloc 后臺慢慢再歸還給 OS, 默認(rèn)情況下,tcmalloc 最多會 cache min(1GB,1/8 * system_memory) 的內(nèi)存, 通過?setParameter.tcmallocMaxTotalThreadCacheBytesParameter?參數(shù)可以配置這個值,不過一般不建議修改,盡量在訪問層面做調(diào)優(yōu))
tcmalloc cache的管理策略,MongoDB 層暴露了幾個參數(shù)來調(diào)整,一般不需要調(diào)整,如果能清楚的理解tcmalloc原理及參數(shù)含義,可做針對性的調(diào)優(yōu);MongoDB tcmalloc 的內(nèi)存狀態(tài)可以通過?db.serverStatus().tcmalloc?查看,具體含義可以看 tcmalloc 的文檔。重點可以關(guān)注下?total_free_bytes,這個值告訴你有多少內(nèi)存是 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,...} }如何控制內(nèi)存使用?
合理配置 WiredTiger cacheSizeGB
- 如果一個機(jī)器上只部署 Mongod,mongod 可以使用所有可用內(nèi)存,則是用默認(rèn)配置即可。
- 如果機(jī)器上多個mongod混部,或者mongod跟其他的一些進(jìn)程一起部署,則需要根據(jù)分給mongod的內(nèi)存配額來配置?cacheSizeGB,按配額的60%左右配置即可。
控制并發(fā)連接數(shù)
TCP連接對 mongod 的內(nèi)存開銷上面已經(jīng)詳細(xì)分析了,很多同學(xué)對并發(fā)有一定誤解,認(rèn)為「并發(fā)連接數(shù)越高,數(shù)據(jù)庫的QPS就越高」,實際上在大部分?jǐn)?shù)據(jù)庫的網(wǎng)絡(luò)模型里,連接數(shù)過高都會使得后端內(nèi)存壓力變大、上下文切換開銷變大,從而導(dǎo)致性能下降。
MongoDB driver 在連接 mongod 時,會維護(hù)一個連接池(通常默認(rèn)100),當(dāng)有大量的客戶端同時訪問同一個mongod時,就需要考慮減小每個客戶端連接池的大小。mongod 可以通過配置?net.maxIncomingConnections?配置項來限制最大的并發(fā)連接數(shù)量,防止數(shù)據(jù)庫壓力過載。
是否應(yīng)該配置 SWAP
官方文檔上的建議如下,意思是配置一下swap,避免mongod因為內(nèi)存使用太多而OOM。
For the WiredTiger storage engine, given sufficient memory pressure, WiredTiger may store data in swap space.Assign swap space for your systems. Allocating swap space can avoid issues with memory contention and can prevent the OOM Killer on Linux systems from killing mongod.開啟 SWAP 與否各有優(yōu)劣,SWAP開啟,在內(nèi)存壓力大的時候,會利用SWAP磁盤空間來緩解內(nèi)存壓力,此時整個數(shù)據(jù)庫服務(wù)會變慢,但具體變慢到什么程度是不可控的。不開啟SWAP,當(dāng)整體內(nèi)存超過機(jī)器內(nèi)存上線時就會觸發(fā)OOM killer把進(jìn)程干掉,實際上是在告訴你,可能需要擴(kuò)展一下內(nèi)存資源或是優(yōu)化對數(shù)據(jù)庫的訪問了。
是否開啟SWAP,實際上是在「好死」與「賴活著」的選擇,個人覺得,對于一些重要的業(yè)務(wù)場景來說,首先應(yīng)該為數(shù)據(jù)庫規(guī)劃足夠的內(nèi)存,當(dāng)內(nèi)存不足時,「及時調(diào)整擴(kuò)容」比「不可控的慢」更好。
其他
- 盡量減少內(nèi)存排序的場景,內(nèi)存排序一般需要更多的臨時內(nèi)存
- 主備節(jié)點配置差距不要過大,備節(jié)點會維護(hù)一個buffer(默認(rèn)最大256MB)用于存儲拉取到oplog,后臺從buffer里取oplog不斷重放,當(dāng)備同步慢的時候,這個buffer會持續(xù)使用最大內(nèi)存。
- 控制集合及索引的數(shù)量,減少databse管理元數(shù)據(jù)的內(nèi)存開銷;集合、索引太多,元數(shù)據(jù)內(nèi)存開銷是一方面的影響,更多的會影響啟動加載的效率、以及運行時的性能。
?
原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的MongoDB 如何使用内存?为什么内存满了?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于TensorFlow.js的Java
- 下一篇: 基于泛型编程的序列化实现方法