2004年7月24日,维拉潘做客搜狐接受专访,参观搜狐体育频道,并与搜狐网CEO张朝阳见面
MongoDB sharding 中moveChunk过程中chunkTooBig 原因分析
@Hisoka 描述的一个问题:
我当前正在为一组shard扩容
之前是3 shard,70亿数据,1.2T,2.4.3
当我将其扩容到6 shard之后发现几个问题:
通过mongostat查看mongos,发现每秒的写入量在500个左右,而新增加的shard分到的写入量在1~5个左右,也就是老的shard上的写入量依旧很大,而新shard的写入量很低,所以导致新写入数据的chunk无法均衡分散。
move chunk经常出现too big的问题,我查看了chunk size是32M,我把它调整到了64M后跑了一晚上,刚才看了下balancer依然会出现这个错误,更令人奇怪的是错误日志里报出too big的estimatedChunkSize实际上只有20M~50M,小于我的chunk size。
move chunk进行的非常非常缓慢,一般一晚上能move chunk在20个左右。我的每一条数据都很小,是:{"_id":BinData(x,xxxxxxxxx),stat:1}这样的东西。
我分析了下:
首选这3个问题都是与移块(moveChunk)有关。
第一 原来的shard写入高,新添加的shard写入低,是数据库块(Chunk)分布不均衡引起(新添加的shard数据量少,块少,config server里记录的信息就少,插入到新的shard当然也就少了),而不是相反。
第二 移块(moveChunk)过程中chunkTooBig问题(相关信息可看这个MongoDB 片键设计不合理导致chunkTooBig块太大不能移动(http://nosqldb.org/topic/5123313acd0cc40a0a051015) )。块大小默认是64M,如果遇到chunkTooBig的时候,调大chunk size是否能解决问题?查看mongos的实现并分析了下,答案是否定的。
chunk size是指的maxChunkSize,这个大小指的是一个块(chunk)的最大大小。
在说明balancer线程如何判断块大小之前,先说明几个概念:
maxChunkSize 默认64M totalRecs 每个chunk总记录数, d->stats.nrecords; // 直接统计出来 avgRecSize平均记录大小(单个document平均大小) d->stats.datasize / totalRecs;// 块总字节数/总记录数 maxRecsWhenFull 每个chunk最大允许的记录数 maxRecsWhenFull计算方式是这样的: 取MaxObjectPerChunk + 1与130*maxChunkSize / avgRecSize/100 二者中的小的。这个MaxObjectPerChunk =250000,1.3是为了估计块太大的一个富余量. estimatedChunkSize recCount * avgRecSize复制
那么什么时候会块太大(chunkTooBig)呢?
首选遍历一个块(不判断大小),当实际的记录数recCount>maxRecsWhenFull就判断该块太大(isLargeChunk)。
结果本例,如果估算的maxRecsWhenFull*1.3
>250001的时候,maxRecsWhenFull就为250001,实际的记录数recCount也极可能是大于250001的,这个时候就报chunkTooBig了。
如果此时调大maxChunkSize,130*maxChunkSize avgRecSize/100 会变大,maxRecsWhenFull仍然是250001,所以是不会有任何效果的。
第三 moveChunk很慢就更简单,因为mongos目前moveChunk是单线程的,单个mongos每次只能移动一个块。
从以上分析不难得知,此问题的根本原因还是片键不合理造成的。当然源码这样实现也有点不合理,片键不合理->块太大(且无法拆开)>块无法移动,造成数据分布不均衡,进而数据写入不均衡,进一步加剧了数据分布不均衡。
附相关函数:
main-> _main()->runMongosServer(_isUpgradeSwitchSet)->start(opts)-> balancer.go()->void Balancer::run()->_doBalanceRound && _moveChunks-> bool Chunk::moveAndCommit->runCommand moveChunk-> class MoveChunkCommand::run-> migrateFromStatus.storeCurrentLocs&& _recvChunkStart-> class RecvChunkStartCommand::run->migrateThread-> migrateStatus.go()-> MigrateStatus::go-> _go ->_migrateClone ->migrateFromStatus.clone( errmsg, result )复制