MongoDB CPU利用率高,怎麼破?
https://help.aliyun.com/document_detail/62224.html
背景資訊
在使用MongoDB雲資料庫的時候您可能經常遇到一個問題:MongoDB CPU利用率很高,都快跑滿了,應該怎麼辦? 遇到這個問題,99.9999%的可能性是您使用上不合理導致。本文主要幫助您從應用的角度排查MongoDB CPU利用率高的問題。
分析資料庫正在執行的請求
您可以通過Mongo Shell連線資料庫,並執行db.currentOp()
命令,檢視資料庫當前正在執行的操作。如下是該命令的一個輸出示例,標識一個正在執行的操作。
{ "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" }, .... },
重點關注以下幾個欄位:
欄位 | 說明 |
---|---|
client | 請求是由哪個客戶端發起的。 |
opid | 操作的opid,有需要的話,可以通過db.killOp(opid) 直接終止該操作。 |
secs_running/microsecs_running | 這個值重點關注,代表請求執行的時間,如果這個值特別大,請看看請求是否合理。 |
query/ns | 這個欄位能看出是對哪個集合正在執行什麼操作。 |
lock* | - 還有一些跟鎖相關的引數,需要了解可以看官網文件,本文不做詳細介紹。 - db.currentOp文件請參見: |
這裡先要明確一下,您通過db.currentOp()
檢視正在執行的操作是否有耗時的請求正在執行。
比如您的業務平時CPU利用率不高,運維管理人員連到資料庫執行了一些需要全表掃描的操作,然後突然CPU利用率飆高,導致你的業務響應很慢,那麼就要重點關注下那些執行時間很長的操作。拿到對應請求的opid,執行db.killOp(opid)
終止對應請求。
如果您的應用一上線,cpu利用率就很高,而且一直持續,執行db.currentOp()
,結果也沒發現什麼異常請求,可以進行更深入的分析即:分析資料庫慢請求。
分析資料庫慢請求
MongoDB支援profiling功能,將請求的執行情況記錄到同DB下的system.profile集合裡,profiling有三種模式:
-
關閉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。
在開啟了慢請求profiling的情況下(MongoDB雲資料庫是預設開啟慢請求profiling的),我們對慢請求的內容進行分析,來找出可優化的點,常見的包括以下幾種場景:
-
全表掃描(關鍵字:COLLSCAN、 docsExamined)
-
全集合(表)掃描COLLSCAN,當一個查詢(或更新、刪除)請求需要全表掃描時,是非常耗CPU資源的,所以當你在system.profile集合或者日誌檔案發現COLLSCAN關鍵字時,很可能就是這些查詢佔用了你的CPU資源,如果這種請求比較頻繁,最好是針對查詢的欄位建立索引來優化。
-
一個查詢掃描了多少文件,可檢視system.profile裡的docsExamined的值,該值越大,請求CPU開銷越大。
-
-
不合理的索引(關鍵字:IXSCAN、keysExamined)
有時請求即使查詢使用了索引,執行也很慢,通常是因為索引建立不太合理(或者是匹配的結果本身就很多,這樣即使使用索引,請求開銷也不會優化很多)。如下所示,假設某個集合的資料,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開銷越大。
-
大量資料排序(關鍵字:SORT、hasSortStage)
當查詢請求裡包含排序的時候,如果排序無法通過索引滿足,MongoDB會在查詢結果中進行排序,而排序這個動作本身是非常耗CPU資源的,優化的方法仍然是建立索引,對經常需要排序的欄位,建立索引。
當您在system.profile集合或者日誌檔案發現SORT關鍵字時,就可以考慮通過索引來優化排序。當請求包含排序欄位時,system.profile裡的hasSortStage欄位會為true。
其他還有諸如建索引aggregationv等操作也可能非常耗CPU資源,但本質上也是上述幾種場景。建索引需要全表掃描,而vaggeregation也是遍歷、查詢、更新、排序等動作的組合。