记录一次 MongoDB aggregate的性能优化经历

在一台配置为2核4G的阿里云服务器上,硬盘是普通的云盘(即SATA盘),除mongoDB外,运行了若干个java应用,单节点mysql和redis,mongo的实际可用内存在1.5G左右。单表数据200万条的时候,一个聚合函数响应时间约为6秒,页面端每秒请求一次,因为响应不够及时,页面刷新不及时,服务端堆积了大量的mongo aggregate请求,系统可用内存不足,直接致使了溢出,mongo服务被动shutdown。java


mongod(ZN5mongo15printStackTraceERSo+0x41) [0x55bd3a2dd321]
mongod(ZN5mongo29reportOutOfMemoryErrorAndExitEv+0x84) [0x55bd3a2dc954]
mongod(ZN5mongo12mongoReallocEPvm+0x21) [0x55bd3a2d22b1]
mongod(ZN5mongo11BufBuilderINS21SharedBufferAllocatorEE15growreallocateEi+0x83) [0x55bd38981833]
mongod(ZN5mongo3rpc17OpMsgReplyBuilder22getInPlaceReplyBuilderEm+0x80) [0x55bd39d4b740]
mongod(+0xAB9609) [0x55bd389be609]
mongod(+0xABBA59) [0x55bd389c0a59]
mysql


下面是聚合的脚本,很简单,就是统计某辆车多个状态码的最新值(经过$first实现)。redis

db.getCollection("vinMsgOut").aggregate([
  {"$match": {"vinCode": "LSGKR53L3HA149563"}},
  {"$sort": {"postTime" : -1}},
  {"$group":  {
      "_id": "$messageType",
      "resultValue": {"$first": "$resultValue"}
      }
  }
],{ allowDiskUse: true })

第一反应是增长过滤条件及增长索引。
结合业务,增长时间条件过滤,将$match改成:sql

{"$match": {"vinCode": "LSGKR53L3HA149563", "createTime": {$gt: ISODate("2020-03-01T06:30:12.038Z")}}}服务器

再分别为vinCode和createTime建立索引,执行,依旧是6秒多。。。
将$sort的字段改为索引字段createTime,
{"$sort": {"createTime" : -1}}
再次执行,时间依旧是6秒多。。。ide

因为系统可分配内存有限,存储引擎已经默认是最快的wiredTiger,磁盘也无法更给力,只能从业务上再着手。考虑到这些最新状态的出现,通常都是同一个时间段,状态码只有几百个,若是sort以后,只从pipe取其中一部分进行group,会不会更快些?带着这个疑问,我加了一条limit。函数

db.getCollection("vinMsgOut").aggregate([
  {"$match": {"vinCode": "LSGKR53L3HA149563", "createTime": {$gt: ISODate("2020-03-01T06:30:12.038Z")}}},
  {"$sort": {"createTime" : -1}},
  {"$limit": 1000},
  {"$group":  {
      "_id": "$messageType",
      "resultValue": {"$first": "$resultValue"}
      }
  }
],{ allowDiskUse: true })

结果是秒回!post

去掉$match中的createTime条件,依旧秒回!这是否意味着createTime索引并无起做用?带着疑问,将createTime索引删掉,返现时间变成5秒,因此createTime的索引是有用的,用在$sort而已。综上,完成了整个查询的优化,总结下来就是:优化

  1. $match条件须要增长索引,若是是多个,最好用组合索引;
  2. $sort的字段也须要增长索引;
  3. $group的_id也须要增长索引;
  4. limit能够大幅度下降时耗。
相关文章
相关标签/搜索