排查MongoDB CPU使用率高的问题

在使用云数据库MongoDB的时候您可能会遇到MongoDB CPU使用率很高或者CPU使用率接近100%的问题,从而导致数据读写处理异常缓慢,影响正常业务。本文主要帮助您从应用的角度排查MongoDB CPU使用率高的问题。

分析MongoDB数据库正在执行的请求

  1. 通过Mongo Shell连接实例。

  2. 执行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

    识别当前操作的标识符。

    如果需要终止当前操作,您可以通过执行db.killOp(opid)终止。

    secs_running

    当前操作已经执行的时间,单位:秒。

    如果已经执行的时间较长,建议您查看请求是否合理。

    microsecs_running

    当前操作已经执行的时间,单位:微秒。

    如果已经执行的时间较长,建议您查看请求是否合理。

    ns

    当前操作的目标集合。

    op

    当前操作的类型,通常是查询、插入、更新和删除中的一种。

    locks

    跟锁相关的信息,详情请参见并发介绍

    说明

    db.currentOp()命令的更多信息,请参见db.currentOp()

您可以通过db.currentOp()命令查看当前正在执行的操作,分析是否有不正常耗时的请求正在执行。例如您的业务平时CPU使用率不高,运维管理人员连到MongoDB数据库执行了一些需要全表扫描的操作导致CPU使用率非常高,业务响应缓慢,此时需要重点关注执行时间非常耗时的操作。

说明

如果发现有异常的请求,您可以找到该请求对应的opid,执行db.killOp(opid)终止该请求。

db.killOp()命令的更多信息,请参见db.killOp()

分析MongoDB数据库的慢请求

如果您的应用刚刚上线,MongoDB实例的CPU使用率马上处于持续很高的状态,并且执行db.currentOp()命令后,在输出结果中未发现异常请求,您需要分析数据库的慢请求。

  1. 在控制台查看慢日志。如何查看,请参见查看慢日志

  2. 分析慢请求日志,查找引起MongoDB实例的CPU使用率升高的原因。

    以下为某个慢日志示例:

    {
      "atype": "slowOp",
      "param": {
        "op": "query",
        "ns": "abbott_analysis.uaidScanInfo",
        "query": {
          "find": "uaidScanInfo",
          "filter": {
            "dateType": 2,
            "companyCode": "GMP"
          },
          "ntoreturn": -1,
          "sort": {
            "scanDateTime": -1
          }
        },
        "keysExamined": 0,
        "docsExamined": 2181021,
        "hasSortStage": true,
        "cursorExhausted": true,
        "numYield": 17059,
        "locks": {
          "Global": {
            "acquireCount": {
              "r": {
                "$numberLong": "34120"
              }
            },
            "acquireWaitCount": {
              "r": {
                "$numberLong": "7"
              }
            },
            "timeAcquiringMicros": {
              "r": {
                "$numberLong": "3152"
              }
            }
          },
          "Database": {
            "acquireCount": {
              "r": {
                "$numberLong": "17060"
              }
            }
          },
          "Collection": {
            "acquireCount": {
              "r": {
                "$numberLong": "17060"
              }
            }
          }
        },
        "nreturned": 0,
        "responseLength": 20,
        "millis": 4878,
        "planSummary": "COLLSCAN"
      },
      "result": "OK"
    }

通常在慢请求日志中,您需要重点关注如下信息:

  • 全表扫描(关键字:COLLSCANdocsExamined

    • 全集合(表)扫描COLLSCAN

      当一个操作请求(如查询、更新、删除等)需要全表扫描时,将非常占用CPU资源。在查看慢请求日志时发现COLLSCAN关键字,很可能是这些查询占用了CPU资源。

      说明

      如果这种请求比较频繁,建议对查询的字段建立索引的方式来优化。

    • 通过查看docsExamined的值,可以查看到一个查询扫描了多少文档。该值越大,请求所占用的CPU开销越大。

  • 不合理的索引(关键字:IXSCANkeysExamined

    说明
    • 索引不是越多越好,索引过多会影响写入、更新的性能。

    • 如果您的应用偏向于写操作,索引可能会影响性能。

    通过查看keysExamined字段,可以查看到一个使用了索引的查询,扫描了多少条索引。该值越大,CPU开销越大。

    如果索引建立的不太合理,或者是匹配的结果很多,这样即使使用索引,请求开销也不会优化很多,执行的速度也会很慢。

    如下所示,假设某个集合的数据,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索引原理复合索引官方文档

  • 大量数据排序(关键字:SORThasSortStage

    当查询请求里包含排序的时候, 请求中的hasSortStage字段会为true。如果排序无法通过索引满足,MongoDB会在查询结果中进行排序,而排序这个动作将非常消耗CPU资源,这种情况需要对经常排序的字段建立索引的方式进行优化。

    说明

    当您在慢日志里发现SORT关键字时,可以考虑通过索引来优化排序。

其他还有诸如建立索引、aggregation(遍历、查询、更新、排序等动作的组合)等操作也可能非常耗CPU资源,但本质上也是上述几种场景。

您也可以将MongoDB实例接入至数据库自治服务DAS(Database Autonomy Service)。在DAS控制台中,您可以对MongoDB实例的实时性能、实时会话、慢日志、磁盘空间等信息进行监控和管理,详情请参见DAS操作流程

服务能力评估

经过上述分析数据库正在执行的请求和分析数据库慢请求两轮优化之后,整个数据库的查询相对合理,所有的请求都高效地使用了索引。如果经过两轮优化后,还存在CPU资源被占满的问题,则可能是实例的服务能力已经达到上限导致,建议您通过如下方法解决:

  1. 通过查看监控信息分析实例资源的使用状态,详情请参见查看监控信息基本监控高级监控

  2. MongoDB数据库进行测试,便于您了解在您的业务场景下,当前实例是否满足所需要的设备性能和服务能力。

如果您需要升级实例,可以参考变更配置变更副本集实例节点数进行操作。