MongoDB CPU 利用率高,怎麼破?
經常有用戶谘詢「MongoDB CPU 利用率很高,都快跑滿了」,應該怎麼辦?
遇到這個問題,99.9999% 的可能性是「用戶使用上不合理導致」,本文主要介紹從應用的角度如何排查 MongoDB CPU 利用率高的問題
Step1: 分析數據庫正在執行的請求
用戶可以通過 Mongo Shell 連接,並執行 db.currentOp()
命令,能看到數據庫當前正在執行的操作,如下是該命令的一個輸出示例,標識一個正在執行的操作。重點關注幾個字段
- client:請求是由哪個客戶端發起的?
- opid:操作的opid,有需要的話,可以通過 db.killOp(opid) 直接幹掉的操作
- secs_running/microsecs_running: 這個值重點關注,代表請求運行的時間,如果這個值特別大,就得注意了,看看請求是否合理
- query/ns: 這個能看出是對哪個集合正在執行什麼操作
- lock*:還有一些跟鎖相關的參數,需要了解可以看官網文檔,本文不做詳細介紹
{
"desc" : "conn632530",
"threadId" : "140298196924160",
"connectionId" : 632530,
"client" : "11.192.159.236:57052",
"active" : true,
"opid" : 1008837885,
"secs_running" : 0,
"microsecs_running" : NumberLong(70),
"op" : "update",
"ns" : "mygame.players",
"query" : {
"uid" : NumberLong(31577677)
},
"numYields" : 0,
"locks" : {
"Global" : "w",
"Database" : "w",
"Collection" : "w"
},
....
},
這裏先要明確一下,通過 db.currentOp() 查看正在執行的操作,目的到底是什麼?
並不是說我們要將正在執行的操作都列出來,然後通過 killOp
逐個幹掉;這一步的目的是要看一下,是否有「意料之外」的耗時請求正在執行。
比如你的業務平時 CPU 利用率不高,運維管理人員連到數據庫執行了一些需要全表掃描的操作,然後突然 CPU 利用率飆高,導致你的業務響應很慢,那麼就要重點關注下那些執行時間很長的操作。
一旦找到罪魁禍首,拿到對應請求的 opid,執行 db.killOp(opid)
將對應的請求幹掉。
如果你的應用一上線,cpu利用率就很高,而且一直持續,通過 db.currentOp
的結果也沒發現什麼異常請求,可以進入到 Step2 進行更深入的分析。
Step2:分析數據庫慢請求
MongoDB 支持 profiling 功能,將請求的執行情況記錄到同DB下的 system.profile
集合裏,profiling 有3種模式
- 關閉 profiling
- 針對所有請求開啟 profiling,將所有請求的執行都記錄到
system.profile
集合 - 針對慢請求 profiling,將超過一定閾值的請求,記錄到
system.profile
集合
默認請求下,MongoDB 的 profiling 功能是關閉,生產環境建議開啟,慢請求閾值可根據需要定製,如不確定,直接使用默認值100ms。
operationProfiling:
mode: slowOp
slowOpThresholdMs: 100
基於上述配置,MongoDB 會將超過 100ms 的請求記錄到對應DB 的 system.profile
集合裏,system.profile
默認是一個最多占用 1MB 空間的 capped collection。
查看最近3條 慢請求,{$natrual: -1} 代表按插入數序逆序
db.system.profile.find().sort({$natrual: -1}).limit(3)
在開啟了慢請求 profiling 的情況下(MongoDB 雲數據庫是默認開啟慢請求 profiling的),我們對慢請求的內容進行分析,來找出可優化的點,常見的包括。
CPU殺手1:全表掃描
全集合(表)掃描 COLLSCAN
,當一個查詢(或更新、刪除)請求需要全表掃描時,是非常耗CPU資源的,所以當你在 system.profile
集合 或者 日誌文件發現 COLLSCAN
關鍵字時,就得注意了,很可能就是這些查詢吃掉了你的 CPU 資源;確認一下,如果這種請求比較頻繁,最好是針對查詢的字段建立索引來優化。
一個查詢掃描了多少文檔,可查看 system.profile
裏的 docsExamined
的值,該值越大,請求CPU開銷越大。
關鍵字:COLLSCAN、 docsExamined
CPU殺手2:不合理的索引
有的時候,請求即使查詢走了索引,執行也很慢,通常是因為合理建立不太合理(或者是匹配的結果本身就很多,這樣即使走索引,請求開銷也不會優化很多)。
如下所示,假設某個集合的數據,x字段的取值很少(假設隻有1、2),而y字段的取值很豐富。
{ x: 1, y: 1 }
{ x: 1, y: 2 }
{ x: 1, y: 3 }
......
{ x: 1, y: 100000}
{ x: 2, y: 1 }
{ x: 2, y: 2 }
{ x: 2, y: 3 }
......
{ x: 1, y: 100000}
要服務 {x: 1: y: 2}
這樣的查詢
db.createIndex( {x: 1} ) 效果不好,因為x相同取值太多
db.createIndex( {x: 1, y: 1} ) 效果不好,因為x相同取值太多
db.createIndex( {y: 1 } ) 效果好,因為y相同取值很少
db.createIndex( {y: 1, x: 1 } ) 效果好,因為y相同取值少
至於{y: 1} 與 {y: 1, x: 1} 的區別,可參考MongoDB索引原理 及 複合索引官方文檔 自行理解。
一個走索引的查詢,掃描了多少條索引,可查看 system.profile
裏的 keysExamined
字段,該值越大,CPU 開銷越大。
關鍵字:IXSCAN、keysExamined
CPU殺手3:大量數據排序
當查詢請求裏包含排序的時候,如果排序無法通過索引滿足,MongoDB 會在內存李結果進行排序,而排序這個動作本身是非常耗 CPU 資源的,優化的方法仍然是建立索引,對經常需要排序的字段,建立索引。
當你在 system.profile
集合 或者 日誌文件發現 SORT
關鍵字時,就可以考慮通過索引來優化排序。當請求包含排序階段時, system.profile
裏的 hasSortStage
字段會為 true。
關鍵字:SORT、hasSortStage
其他還有諸如建索引,aggregationv等操作也可能非常耗 CPU 資源,但本質上也是上述幾種場景;建索引需要全表掃描,而vaggeregation 也是遍曆、查詢、更新、排序等動作的組合。
Step3: 服務能力評估
經過上述2步,你發現整個數據庫的查詢非常合理,所有的請求都是高效的走了索引,基本沒有優化的空間了,那麼很可能是你機器的服務能力已經達到上限了,應該升級配置了(或者通過 sharding 擴展)。
當然最好的情況時,提前對 MongoDB 進行測試,了解在你的場景下,對應的服務能力上限,以便及時擴容、升級,而不是到 CPU 資源用滿,業務已經完全撐不住的時候才去做評估。
最後更新:2017-04-07 21:25:10