根据索引in查询, 扫描索引3条 keysExamined 3, 返回文档数1条 nReturned 1, 耗时 millis 452毫秒, execStats中executionTimeMillisEstimate都是0.
问题1: executionTimeMillisEstimate都是0. 为啥整个查询却消耗452毫秒.
问题2: 走了索引,只扫描索引3条,返回文档数1条,消耗452毫秒,怎么优化或者怎么调整它.
问题3: 在其它的执行计划中我看到inputStage.stage.FETCH操作耗时也很高,在200~1000毫秒.FETCH根据索引取下文档为啥还要这么多时间呢.
求助 @Mongoing中文社区 @bguo
{
"op": "query",
"ns": "webDevice",
"query": {
"find": "webDevice",
"filter": {
"lid": {
"$in": [
"40CnwyHkVmnA9kbScMLNLneaxuS4Tcj",
"140CnwyHkVmnA9kbScMLNLneaxuS4Tcj"
]
}
},
"projection": {
"$sortKey": {
"$meta": "sortKey"
}
},
"sort": {
"createTime": -1
},
"limit": 1,
"shardVersion": [
{
"$timestamp": {
"t": 106,
"i": 0
}
},
{
"$oid": "59b0039e9b5e66530435be05"
}
]
},
"keysExamined": 3,
"docsExamined": 1,
"hasSortStage": true,
"cursorExhausted": true,
"keyUpdates": 0,
"writeConflicts": 0,
"numYield": 0,
"locks": {
"Global": {
"acquireCount": {
"r": 2
}
},
"Database": {
"acquireCount": {
"r": 1
}
},
"Collection": {
"acquireCount": {
"r": 1
}
}
},
"nreturned": 1,
"responseLength": 1267,
"protocol": "op_command",
"millis": 452,
"execStats": {
"stage": "CACHED_PLAN",
"nReturned": 1,
"executionTimeMillisEstimate": 0,
"works": 2,
"advanced": 1,
"needTime": 0,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"inputStage": {
"stage": "PROJECTION",
"nReturned": 1,
"executionTimeMillisEstimate": 0,
"works": 5,
"advanced": 1,
"needTime": 4,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"transformBy": {
"$sortKey": {
"$meta": "sortKey"
}
},
"inputStage": {
"stage": "SORT",
"nReturned": 1,
"executionTimeMillisEstimate": 0,
"works": 5,
"advanced": 1,
"needTime": 4,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"sortPattern": {
"createTime": -1
},
"memUsage": 1031,
"memLimit": 33554432,
"limitAmount": 1,
"inputStage": {
"stage": "SORT_KEY_GENERATOR",
"nReturned": 0,
"executionTimeMillisEstimate": 0,
"works": 4,
"advanced": 0,
"needTime": 2,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"inputStage": {
"stage": "SHARDING_FILTER",
"nReturned": 1,
"executionTimeMillisEstimate": 0,
"works": 3,
"advanced": 1,
"needTime": 1,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"chunkSkips": 0,
"inputStage": {
"stage": "FETCH",
"nReturned": 1,
"executionTimeMillisEstimate": 0,
"works": 3,
"advanced": 1,
"needTime": 1,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"docsExamined": 1,
"alreadyHasObj": 0,
"inputStage": {
"stage": "IXSCAN",
"nReturned": 1,
"executionTimeMillisEstimate": 0,
"works": 3,
"advanced": 1,
"needTime": 1,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"keyPattern": {
"lid": -1
},
"indexName": "lid_-1",
"isMultiKey": false,
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": 1,
"direction": "forward",
"indexBounds": {
"lid": [
"[\"40CnwyHkVmnA9kbScMLNLneaxuS4Tcj\", \"40CnwyHkVmnA9kbScMLNLneaxuS4Tcj\"]",
"[\"140CnwyHkVmnA9kbScMLNLneaxuS4Tcj\", \"140CnwyHkVmnA9kbScMLNLneaxuS4Tcj\"]"
]
},
"keysExamined": 3,
"dupsTested": 0,
"dupsDropped": 0,
"seenInvalidated": 0
}
}
}
}
}
}
},
"ts": {
"$date": 1514285478923
},
"client": "10.105.122.126",
"allUsers": [
{
"user": "__system",
"db": "local"
}
],
"user": "__system@local",
"_id": "c044e94198e245f3e61b39d230feb393-20171226105118923-200109374"
}
补充说明
我补充一下我的环境: 机器是8核16内存. 机器上部署有5个mongodb实例(在docker容器里面),1个mongos,1个config,3个shard(1个主,1个从,1个arbiter).
以下是docker的内存使用情况.
cpu使用情况 | 内存使用情况 | |
---|---|---|
arbiter实例 | 1.83% | 80.18MiB / 15.51GiB |
shard2从 | 3.09% | 5.306GiB / 15.51GiB |
config | 1.81% | 1.449GiB / 15.51GiB |
shard3主 | 2.56% | 5.025GiB / 15.51GiB |
mongos | 0.37% | 188.3MiB / 15.51GiB |
其中config,shard主,shard从.3个实例都设置了CacheSizeGB为3.
目前从资源使用情况来看,CPU使用率都很低,内存config虽然限制了3GB,但整个docker容器只用了1.5GB的内存.
- 疑问1: 按照你说的方式调小cacheSizeGB,应该怎么调整比较合理
- 疑问2: config只用了1.5GB内存(限制了CacheSizeGB3GB).可以说明索引都加载到了内存中吗?
- 疑问3: mongodb remove数据后,查询却越来越慢是什么情况?
下次记得把原始查询也发出来,我们看着更方便些。从执行计划来反推,查询大约是
因为命中索引,这个查询获取数据的速度实际上比较快,你也提到在一段时间内第二次查询就快了,这是一个很明显的特点,代表内存不足。MongoDB和其他数据库一样,都会使用空闲内存缓冲索引和数据,当内存不足时就会使用LRU算法清理旧数据换入新数据再继续查询。因为这个过程涉及到磁盘数据交换,速度会大大降低。发生这种情况有一个特点,就是一段时间内第二次执行时速度就快了(因为数据已经在内存中)。但是如果过一段时间再执行,速度又会变慢(因为又被换出内存了)。所以你的情况实际上就是受限于硬件。
既然如此最直接的解决办法就是:
如果不能从硬件方面解决,有一点可以尝试就是用CPU换内存。做法是尽可能调小
cacheSizeGB
(是的没错,是调小)。空闲出来的内存操作系统会用来缓冲磁盘数据,而磁盘数据是经过压缩的,体积更小,因此可以缓冲更多数据到内存中。但作为交换,在使用这些数据时需要经过CPU再进行一次解压从而额外消耗CPU。即使这样,效果也比从磁盘读取要好很多。整个过程是自动进行的,你需要做的只是调小cacheSizeGB
。这种做法可以在一定程度上缓解内存不足的问题,但不是万能的:
补充回答
基于你的新的疑问,以下几点补充:
我在上面有提到,这样做的效果有限,是受制于压缩率的限制。所以多容纳的数据实际就是压缩的数据。比如1G的内存能放多少数据?
所以调到多少,其实要看你想往内存里放多少数据,而这往往是一个不确定的数值。这里会有另外一个概念叫做工作集(working set),就是你经常用到的那些数据。比如你的数据库共有100GB数据,但是你经常用到的部分只有10GB,那么你的内存只要能装下10GB数据,应用在大部分时候就可以非常快,剩下的情况忽略就好了。
基于上面这些分析,你应该可以算一下,你的工作集有多大,数据压缩率有多大,那么需要把
cacheSizeGB
调到多小才能容纳下工作集(又或者调到多小都不可能容纳下工作集)。你的情况是内存不足CPU空闲,所以如果懒得算,直接把cacheSizeGB
调到最小值就好了。这说明config数据库(元数据)的索引和数据都加载到内存中了。注意数据的索引肯定是在shard中,与config无关。而且大头是在shard上面。
顺便提一下,如果不是实验目的,根本没必要分这么多片。因为在一台机器上分再多片,硬件资源也只有这么多,对于性能没有什么意义,反而还会有额外的传输开销。
remove之后跟查询没有本质上的联系,可能只是凑巧发生在一起。如果你有足够的证据觉得这两者确实有联系,请另开问题描述清楚问题的上下文以及你发现的情况,必要的时候也可以求助于MongoDB JIRA。如果一定要做一个无根据的猜测,我觉得可能是remove时导致热数据被换出内存(注意remove也需要先找到满足条件的数据然后才能删除),引起后面的查询需要重新从磁盘上加载数据造成的。