MongoDB中merge空的chunks
2022-04-18 22:45 abce 阅读(261) 评论(0) 编辑 收藏 举报我们知道,MongoDB中有一个autoSplitter进程,在chunk变得太大的时候,就会对chunk进行分区。同时还有一个balancer进程,负责移动chunks,保证在分片之间平均分布。所以,随着数据的增长,chunks会被分区,并可能被移动到别的分片上。
但是,在我们删除数据的时候,会发生什么呢?有些chunks可能就变的很空。如果我们删除了很多数据,就会有一定数量的chunks变得很空。这对于带有TTL索引的分片集合就会是个很大的问题。
潜在的问题
其中一个潜在的问题就是,当有大量的空的chunks时,分片环境数据的分布就会变得不平衡。balancer进程会保证每个分片上的chunks数量是均衡的,但是balancer进程并没有考虑到空的chunks的场景。所以,你可能会遇到,整个集群看似是均衡的,但实际上不是,有些分片上的数据会远远多于别的分片。
为了解决这个问题,首先要找出哪些chunks是空的。
找出空的chunks
假设有个集合通过org_id进行分片。并假设结合目前的chunks为:
minKey –> 1
1 -–> 5
5 —-> 10
10 –> 15
15 —-> 20
….
我们可以使用dataSize命令来检测chunk的大小。
例如,检查第三个chunk上有多少的documents,我们可以:
db.runCommand({ dataSize: "mydatabase.clients", keyPattern: { org_id: 1 }, min: { org_id: 5 }, max: { org_id: 10 } })
会返回类似下面的结果:
{
"size" : 0,
"numObjects" : 0,
"millis" : 30,
"ok" : 1,
"operationTime" : Timestamp(1641829163, 2),
"$clusterTime" : {
"clusterTime" : Timestamp(1641829163, 3),
"signature" : {
"hash" : BinData(0,"LbBPsTEahzG/v7I6oe7iyvLr/pU="),
"keyId" : NumberLong("7016744225173049401")
}
}
}
如果size是0,我们就可以知道,这是空的chunk。我们就可以考虑将该空的chunk和其后面(range 10 → 15)或者前面的(range 1 → 5)chunk进行合并了。
假设我们将其和后面的chunk进行合并:
db.adminCommand( {
mergeChunks: "database.collection",
bounds: [ { "field" : "5" },
{ "field" : "15" } ]
} )
新的chunks范围就会变成:
minKey –> 1
1 —-> 5
5 —-> 15
15 —-> 20
….
如果我们要merge的两个chunks不在同一个分片,那么,我们要先执行moveChunk操作。
按照上面的逻辑,我们可以根据分片键的顺序,迭代检查所有的chunks,检查他们的大小。如果我们发现 空的chunks,就和该chunk的上一个chunk进行merge。如果不是在相同的分片上,就先移动到一起。下面的脚本就可以输出所有需要的命令:
var mergeChunkInfo = function(ns){
var chunks = db.getSiblingDB("config").chunks.find({"ns" : ns}).sort({min:1}).noCursorTimeout();
//some counters for overall stats at the end
var totalChunks = 0;
var totalMerges = 0;
var totalMoves = 0;
var previousChunk = {};
var previousChunkInfo = {};
var ChunkJustChanged = false;
chunks.forEach(
function printChunkInfo(currentChunk) {
var db1 = db.getSiblingDB(currentChunk.ns.split(".")[0])
var key = db.getSiblingDB("config").collections.findOne({_id:currentChunk.ns}).key;
db1.getMongo().setReadPref("secondary");
var currentChunkInfo = db1.runCommand({datasize:currentChunk.ns, keyPattern:key, min:currentChunk.min, max:currentChunk.max, estimate:true });
totalChunks++;
// if the current chunk is empty and the chunk before it was not merged in the previous iteration (or was the first chunk) we have candidates for merging
if(currentChunkInfo.size == 0 && !ChunkJustChanged) {
// if the chunks are contiguous
if(JSON.stringify(previousChunk.max) == JSON.stringify(currentChunk.min) ) {
// if they belong to the same shard, merge with the previous chunk
if(previousChunk.shard.toString() == currentChunk.shard.toString() ) {
print('db.runCommand( { mergeChunks: "' + currentChunk.ns.toString() + '",' + ' bounds: [ ' + JSON.stringify(previousChunk.min) + ',' + JSON.stringify(currentChunk.max) + ' ] })');
// after a merge or move, we don't consider the current chunk for the next iteration. We skip to the next chunk.
ChunkJustChanged=true;
totalMerges++;
}
// if they contiguous but are on different shards, we need to have both chunks to the same shard before merging, so move the current one and don't merge for now
else {
print('db.runCommand( { moveChunk: "' + currentChunk.ns.toString() + '",' + ' bounds: [ ' + JSON.stringify(currentChunk.min) + ',' + JSON.stringify(currentChunk.max) + ' ], to: "' + previousChunk.shard.toString() + '" });');
// after a merge or move, we don't consider the current chunk for the next iteration. We skip to the next chunk.
ChunkJustChanged=true;
totalMoves++;
}
}
else {
// chunks are not contiguous (this shouldn't happen unless this is the first iteration)
previousChunk=currentChunk;
previousChunkInfo=currentChunkInfo;
ChunkJustChanged=false;
}
}
else {
// if the current chunk is not empty or we already operated with the previous chunk let's continue with the next chunk pair
previousChunk=currentChunk;
previousChunkInfo=currentChunkInfo;
ChunkJustChanged=false;
}
}
)
print("***********Summary Chunk Information***********");
print("Total Chunks: "+totalChunks);
print("Total Move Commands to Run: "+totalMoves);
print("Total Merge Commands to Run: "+totalMerges);
}
可以在mongo shell中执行:
mergeChunkInfo("mydb.mycollection")
这个脚本会生成需要merge chunks需要的所有命令。运行生成的命令之后,会将空的chunks的数量减半,多次执行就会逐渐减少了空的chunks。
很多人都意识到了巨大的chunk的问题,现在我们也看到了空的chunk在某些场景下也会产生问题。在执行修改chunk的时候(比如对chunks进行merge),最好停止balancer进程。这样不会产生冲突。别忘了,操作结束后重启开启balancer进程。