代码改变世界

MongoDB中merge空的chunks

  abce  阅读(275)  评论(0编辑  收藏  举报

chunk的维护

我们知道,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进行合并了。

合并chunks

假设我们将其和后面的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,检查他们的大小。如果我们发现 空的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进程。

 

相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2020-04-18 MySQL没有选对正确的索引怎么办
2020-04-18 OceanBase的产品简介
2017-04-18 12C -- ORA-65048 ORA-65048
2017-04-18 12C -- 创建RMAN备份用户
2016-04-18 DG - 将physical standby置于read-only模式
2016-04-18 set ver on/off
点击右上角即可分享
微信分享提示