記錄一次 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能夠大幅度下降時耗。
相關文章
相關標籤/搜索