mongodb shard模式插入速度过慢问题追查
(创建时间:2012年08月21日 19:17:00)
Jangogo : 

环境:1个mongos,1个config server,2个shard(即mongod)

现象:通过mongos向集群中插入几十万条数据,开始没发现问题,吃过午饭后还没有插入完成,发现数据库item数增长缓慢,查看其中一个shard日志,有“Fri Jun 15 12:09:16 [conn4] warning: Finding the split vector for vds.log over { time: 1.0 } keyCount: 8 numSplits: 1 lookedAt: 50960 took 102ms”内容,并且随着时间增长,took的时间越来越长。执行insert的shard消耗CPU过多。

打算使用mongodb做点事情,刚刚开始就出现问题,确实令人沮丧。没办法,攻城师就得迎难而上,尝试解决。

原来,shard模式下,mongodb每次insert和update后,都会调用一个CunckPtr的API splitIfShould(strategy_shard.cpp:164,330 c->splitIfShould( o.objsize() );)。

splitIfShould->singleSplit->pickMedianKey()发送splitVector命令到相应chunk的shard,计算其splitKeys,然后splitIfShould进行分割chunk的操作。其实就是判断chunk是否过大,过大则分割的过程,问题当然就出现在这一过程。

mongos发送的splitVector命令由mongod的SplitVector::run()方法解析(D_split.cpp:256)。chunk分割的依据由maxSplitPoints、maxChunkObjects、maxChunkSize等几个参数共同决定,如果读者发现自己的mongodb的chunk有过小或split过于频繁等现象,可以考虑调节这几个参数进行尝试。

其实SplitVector的思路比较简单,如果chunk的size小于maxChunkSize直接返回。

否则先计算出chunk内的对象平均大小,然后根据maxChunkSize计算出预估在keyCount点进行分割:

const long long avgRecSize = dataSize / recCount;

long long keyCount = maxChunkSize / (2 * avgRecSize);

如果maxChunkObjects < keyCount,keyCount = maxChunkObjects。之后,mongodb遍历其索引(shard模式对mongodb有一个新的要求,就是其shard key必须要在mongod中建立索引,我想,原因之一就是协助SplitVector功能吧)找到keyCount对应的item的key,保存在splitKeys中,继续遍历到2*keyCount对应的item的key,保存在splitKeys中,继续3*keyCount。。。最后返回splitKeys完事。

到目前为止,如果在正常情况下,没什么问题。那触发问题的因素是什么呢?

就是当存在大量item key相同的时候,mongodb是要保证相同key的items保存在同一个chunk中的,

//If a key appears more times than entries allowed on a chunk, we issue a warning and

// split on the following key.

所以在上述步骤中,如果keyCount对应的item的key与chunk的第一个item的key一样时,mongodb就会持续遍历,直到找到不同的key,如果一直找不到就要遍历整个chunk了,最后才退出循环。这也解释了为什么SplitVector耗时随着items增多逐渐增加。

另外阅读代码时,发现另外一个细节,如果在splitVector命令中,设置了force:1,那在第一次找不到合适的splitKey时,程序会keyCount = currCount / 2,然后继续重头开始查找splitKey环节,周而复始,会陷入死循环。

通过上述的分析,我们在使用mongodb shard模式时要注意,相同shard key的item数一定要注意,不要让其超过maxChunkSize/平均item size / 2个,否则会触发这一问题。

maxChunkSizeBytes & maxSplitPoints & maxChunkObjects 说明

默认取值:

chunk.cpp:49

int Chunk::MaxChunkSize = 1024 * 1024 * 64;

int Chunk::MaxObjectPerChunk = 250000;

chunk.cpp:205

const int maxPoints = 2;

其中maxChunkSizeBytes的取值比较特殊一些,可以通过mongos启动时–chunkSize参数设置。另外,其取值也不是仅仅采纳设置的值而已,而是由ChunkManager::getCurrentDesiredChunkSize()函数指定,如果chunk num <= 1,则取1024,如果 < 3,则取512k,如果 < 10,则取16M(Chunk::MaxChunkSize / 4),如果 < 20,则取32M(Chunk::MaxChunkSize / 2),其他则取64M,即Chunk::MaxChunkSize。

其他两个参数若想修改,只能改代码了。

文档中心