如何使用MongoDB以编程方式预分割基于GUID的分片键 [英] How to Programmatically Pre-Split a GUID Based Shard Key with MongoDB

查看:62
本文介绍了如何使用MongoDB以编程方式预分割基于GUID的分片键的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我使用的是相当标准的32个字符的十六进制 GUID ,我已经确定那是因为它是为我的用户随机生成的,因此非常适合用作水平扩展对将存储用户信息的MongoDB集合中写入的分片键(而我主要关注的是写入扩展).

Let's say I am using a fairly standard 32 character hex GUID, and I have determined that, because it is randomly generated for my users, it is perfect for use as a shard key to horizontally scale writes to the MongoDB collection that I will be storing the user information in (and write scaling is my primary concern).

我也知道,由于流量预测以及在测试环境中完成的一些基准测试工作,我将需要至少从4个分片开始.

I also know that I will need to start with at least 4 shards, because of traffic projections and some benchmark work done with a test environment.

最后,我对初始数据大小(平均文档大小*初始用户数)有一个不错的想法-大约为120GB.

Finally, I have a decent idea of my initial data size (average document size * number of initial users) - which comes to around ~120GB.

我想使初始加载更好且更快,并尽可能多地利用所有4个分片.如何预分割这些数据,以便我利用4个分片并最大程度地减少在初始数据加载期间需要在这些分片上进行的移动,分割等次数?

I'd like to make the initial load nice and fast and utilize all 4 shards as much as possible. How do I pre-split this data so that I take advantage of the 4 shards and minimize the number of moves, splits etc. that need to happen on the shards during the initial data load?

推荐答案

我们知道初始数据大小(120GB),并且知道MongoDB中默认的最大块大小

We know the intial data size (120GB) and we know the default maximium chunk size in MongoDB is 64MB. If we divide 64MB into 120GB we get 1920 - so that is the minimum number of chunks we should look to start with. As it happens 2048 happens to be a power of 16 divided by 2, and given that the GUID (our shard key) is hex based, that's a much easier number to deal with than 1920 (see below).

注意:必须在将所有数据添加到集合中之前进行此预分割.如果在包含数据的集合上使用enableSharding()命令,则MongoDB会自行拆分数据,然后在已有块的情况下运行该数据-可能会导致奇怪的块分布,因此请当心.

NOTE: This pre-splitting must be done before any data is added to the collection. If you use the enableSharding() command on a collection that contains data, MongoDB will split the data itself and you will then be running this while chunks already exist - that can lead to quite odd chunk distribution, so beware.

出于此答案的目的,我们假设数据库将被称为users,而集合将被称为userInfo.我们还假设GUID将被写入_id字段中.使用这些参数,我们将连接到mongos并运行以下命令:

For the purposes of this answer, let's assume that the database is going to be called users and the collection is called userInfo. Let's also assume that the GUID will be written into the _id field. With those parameters we would connect to a mongos and run the following commands:

// first switch to the users DB
use users;
// now enable sharding for the users DB
sh.enableSharding("users"); 
// enable sharding on the relevant collection
sh.shardCollection("users.userInfo", {"_id" : 1});
// finally, disable the balancer (see below for options on a per-collection basis)
// this prevents migrations from kicking off and interfering with the splits by competing for meta data locks
sh.stopBalancer(); 

现在,根据上述计算,我们需要将GUID范围分成2048个块.为此,我们至少需要3个十六进制数字(16 ^ 3 = 4096),并将其放置在范围的最高有效数字(即最左边的3个数字)中.同样,这应该从mongos shell

Now, per the calculation above, we need to split the GUID range into 2048 chunks. To do that we need at least 3 hex digits (16 ^ 3 = 4096) and we'll be putting them in the most significant digits (i.e. the 3 leftmost) for the ranges. Again, this should be run from a mongos shell

// Simply use a for loop for each digit
for ( var x=0; x < 16; x++ ){
  for( var y=0; y<16; y++ ) {
  // for the innermost loop we will increment by 2 to get 2048 total iterations
  // make this z++ for 4096 - that would give ~30MB chunks based on the original figures
    for ( var z=0; z<16; z+=2 ) {
    // now construct the GUID with zeroes for padding - handily the toString method takes an argument to specify the base
        var prefix = "" + x.toString(16) + y.toString(16) + z.toString(16) + "00000000000000000000000000000";
        // finally, use the split command to create the appropriate chunk
        db.adminCommand( { split : "users.userInfo" , middle : { _id : prefix } } );
    }
  }
}

完成后,让我们使用 sh.status()检查游戏状态助手:

Once that is done, let's check the state of play using the sh.status() helper:

mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "version" : 3,
        "minCompatibleVersion" : 3,
        "currentVersion" : 4,
        "clusterId" : ObjectId("527056b8f6985e1bcce4c4cb")
}
  shards:
        {  "_id" : "shard0000",  "host" : "localhost:30000" }
        {  "_id" : "shard0001",  "host" : "localhost:30001" }
        {  "_id" : "shard0002",  "host" : "localhost:30002" }
        {  "_id" : "shard0003",  "host" : "localhost:30003" }
  databases:
        {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
        {  "_id" : "users",  "partitioned" : true,  "primary" : "shard0001" }
                users.userInfo
                        shard key: { "_id" : 1 }
                        chunks:
                                shard0001       2049
                        too many chunks to print, use verbose if you want to force print

我们有2048个块(多亏了最小/最大块),但由于平衡器已关闭,它们仍在原始分片上.因此,让我们重新启用平衡器:

We have our 2048 chunks (plus one extra thanks to the min/max chunks), but they are all still on the original shard because the balancer is off. So, let's re-enable the balancer:

sh.startBalancer();

这将立即开始平衡,并且由于所有的块都是空的,所以它会相对较快,但是仍然需要一些时间(如果要与其他集合的迁移竞争则要慢得多).经过一段时间后,再次运行sh.status(),在那里(应该)拥有它-2048个块都很好地分成了4个分片,并准备进行初始数据加载:

This will immediately begin to balance out, and it will be relatively quick because all the chunks are empty, but it will still take a little while (much slower if it is competing with migrations from other collections). Once some time has elapsed, run sh.status() again and there you (should) have it - 2048 chunks all nicely split out across 4 shards and ready for an initial data load:

mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "version" : 3,
        "minCompatibleVersion" : 3,
        "currentVersion" : 4,
        "clusterId" : ObjectId("527056b8f6985e1bcce4c4cb")
}
  shards:
        {  "_id" : "shard0000",  "host" : "localhost:30000" }
        {  "_id" : "shard0001",  "host" : "localhost:30001" }
        {  "_id" : "shard0002",  "host" : "localhost:30002" }
        {  "_id" : "shard0003",  "host" : "localhost:30003" }
  databases:
        {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
        {  "_id" : "users",  "partitioned" : true,  "primary" : "shard0001" }
                users.userInfo
                        shard key: { "_id" : 1 }
                        chunks:
                                shard0000       512
                                shard0002       512
                                shard0003       512
                                shard0001       513
                        too many chunks to print, use verbose if you want to force print
        {  "_id" : "test",  "partitioned" : false,  "primary" : "shard0002" }

您现在可以开始加载数据了,但是要绝对保证在数据加载完成之前不会发生拆分或迁移,您需要再做一件事-关闭平衡器,并在导入期间自动拆分:

You are now ready to start loading data, but to absolutely guarantee that no splits or migrates happen until your data load is complete, you need to do one more thing - turn off the balancer and autosplitting for the duration of the import:

  • 要禁用所有平衡,请从mongos运行以下命令: sh.stopBalancer()
  • 如果要使其他平衡操作保持运行状态,可以对特定的集合禁用.以上面的命名空间为例: sh.disableBalancing("users.userInfo")
  • 要在加载期间关闭自动拆分,您将需要重新启动要用于通过mongos reference/program/mongos/#cmdoption--noAutoSplit> --noAutoSplit 选项.
  • To disable all balancing, run this command from the mongos: sh.stopBalancer()
  • If you want to leave other balancing operations running, you can disable on a specific collection. Using the namespace above as an example: sh.disableBalancing("users.userInfo")
  • To turn off auto splitting during the load, you will need to restart each mongos you will be using to load the data with the --noAutoSplit option.

一旦导入完成,请根据需要反转步骤(sh.startBalancer()sh.enableBalancing("users.userInfo"),然后在不使用--noAutoSplit的情况下重新启动mongos)以将所有内容恢复为默认设置.

Once the import is complete, reverse the steps as needed (sh.startBalancer(), sh.enableBalancing("users.userInfo"), and restart the mongos without --noAutoSplit) to return everything to the default settings.

**

**

如果您不着急的话,上面的方法很好.就目前情况而言,您将发现是否进行了测试,因此平衡器的运行速度并不快-即使有空块也是如此.因此,随着您增加创建的块的数量,平衡所需的时间也就越长.我已经看到完成2048个块的平衡需要花费30多分钟的时间,尽管具体情况取决于部署.

The approach above is fine if you are not in a hurry. As things stand, and as you will discover if you test this, the balancer is not very fast - even with empty chunks. Hence as you increase the number of chunks you create, the longer it is going to take to balance. I have seen it take more than 30 minutes to finish balancing 2048 chunks though this will vary depending on the deployment.

对于测试或相对安静的群集而言,这可能是好的,但是关闭平衡器并且不需要其他更新进行干预将很难确保在繁忙的群集上进行.那么,我们如何加快速度?

That might be OK for testing, or for a relatively quiet cluster, but having the balancer off and requiring no other updates interfere will be much harder to ensure on a busy cluster. So, how do we speed things up?

答案是尽早进行一些手动移动,然后将大块放在各自的碎片上后进行分割.请注意,这仅对于某些分片键(例如随机分布的UUID)或某些数据访问模式才是理想的,因此请注意,不要以结局而导致数据分布不佳.

The answer is to do some manual moves early, then split the chunks once they are on their respective shards. Note that this is only desirable with certain shard keys (like a randomly distributed UUID), or certain data access patterns, so be careful that you don't end up with poor data distribution as a result.

使用上面的示例,我们有4个分片,因此我们不进行所有拆分,而是进行平衡,而是拆分为4个.然后,我们通过手动移动每个碎片在每个碎片上放置一个块,最后将这些碎片拆分成所需的数字.

Using the example above we have 4 shards, so rather than doing all the splits, then balancing, we split into 4 instead. We then put one chunk on each shard by manually moving them, and then finally we split those chunks into the required number.

上面示例中的范围如下所示:

The ranges in the example above would look like this:

$min --> "40000000000000000000000000000000"
"40000000000000000000000000000000" --> "80000000000000000000000000000000"
"80000000000000000000000000000000" --> "c0000000000000000000000000000000"
"c0000000000000000000000000000000" --> $max     

创建这些命令只有4条命令,但是既然有了它,为什么不以简化/修改后的形式重复使用上面的循环:

It's only 4 commands to create these, but since we have it, why not re-use the loop above in a simplified/modified form:

for ( var x=4; x < 16; x+=4){
    var prefix = "" + x.toString(16) + "0000000000000000000000000000000";
    db.adminCommand( { split : "users.userInfo" , middle : { _id : prefix } } ); 
} 

这就是现在的想法-我们有4个块,全部都在shard0001上:

Here's how thinks look now - we have our 4 chunks, all on shard0001:

mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
    "_id" : 1,
    "version" : 4,
    "minCompatibleVersion" : 4,
    "currentVersion" : 5,
    "clusterId" : ObjectId("53467e59aea36af7b82a75c1")
}
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:30000" }
    {  "_id" : "shard0001",  "host" : "localhost:30001" }
    {  "_id" : "shard0002",  "host" : "localhost:30002" }
    {  "_id" : "shard0003",  "host" : "localhost:30003" }
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "test",  "partitioned" : false,  "primary" : "shard0001" }
    {  "_id" : "users",  "partitioned" : true,  "primary" : "shard0001" }
        users.userInfo
            shard key: { "_id" : 1 }
            chunks:
                shard0001   4
            { "_id" : { "$minKey" : 1 } } -->> { "_id" : "40000000000000000000000000000000" } on : shard0001 Timestamp(1, 1) 
            { "_id" : "40000000000000000000000000000000" } -->> { "_id" : "80000000000000000000000000000000" } on : shard0001 Timestamp(1, 3) 
            { "_id" : "80000000000000000000000000000000" } -->> { "_id" : "c0000000000000000000000000000000" } on : shard0001 Timestamp(1, 5) 
            { "_id" : "c0000000000000000000000000000000" } -->> { "_id" : { "$maxKey" : 1 } } on : shard0001 Timestamp(1, 6)                    

我们将保留$min块的位置,然后移动其他三个.您可以通过编程方式执行此操作,但是它确实取决于块的初始位置,分片的命名方式等.因此,我将暂时保留此手册,它并不繁琐-仅3个

We will leave the $min chunk where it is, and move the other three. You can do this programatically, but it does depend on where the chunks reside initially, how you have named your shards etc. so I will leave this manual for now, it is not too onerous - just 3 moveChunk commands:

mongos> sh.moveChunk("users.userInfo", {"_id" : "40000000000000000000000000000000"}, "shard0000")
{ "millis" : 1091, "ok" : 1 }
mongos> sh.moveChunk("users.userInfo", {"_id" : "80000000000000000000000000000000"}, "shard0002")
{ "millis" : 1078, "ok" : 1 }
mongos> sh.moveChunk("users.userInfo", {"_id" : "c0000000000000000000000000000000"}, "shard0003")
{ "millis" : 1083, "ok" : 1 }          

让我们仔细检查一下,并确保这些块位于我们期望的位置:

Let's double check, and make sure that the chunks are where we expect them to be:

mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
    "_id" : 1,
    "version" : 4,
    "minCompatibleVersion" : 4,
    "currentVersion" : 5,
    "clusterId" : ObjectId("53467e59aea36af7b82a75c1")
}
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:30000" }
    {  "_id" : "shard0001",  "host" : "localhost:30001" }
    {  "_id" : "shard0002",  "host" : "localhost:30002" }
    {  "_id" : "shard0003",  "host" : "localhost:30003" }
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "test",  "partitioned" : false,  "primary" : "shard0001" }
    {  "_id" : "users",  "partitioned" : true,  "primary" : "shard0001" }
        users.userInfo
            shard key: { "_id" : 1 }
            chunks:
                shard0001   1
                shard0000   1
                shard0002   1
                shard0003   1
            { "_id" : { "$minKey" : 1 } } -->> { "_id" : "40000000000000000000000000000000" } on : shard0001 Timestamp(4, 1) 
            { "_id" : "40000000000000000000000000000000" } -->> { "_id" : "80000000000000000000000000000000" } on : shard0000 Timestamp(2, 0) 
            { "_id" : "80000000000000000000000000000000" } -->> { "_id" : "c0000000000000000000000000000000" } on : shard0002 Timestamp(3, 0) 
            { "_id" : "c0000000000000000000000000000000" } -->> { "_id" : { "$maxKey" : 1 } } on : shard0003 Timestamp(4, 0)  

与我们上面建议的范围相匹配,所以看起来都不错.现在,在上方运行原始循环以将其分割"在每个分片上,循环结束后,我们应该具有均衡的分布.还有一个sh.status()应该可以确认:

That matches our proposed ranges above, so all looks good. Now run the original loop above to split them "in place" on each shard and we should have a balanced distribution as soon as the loop finishes. One more sh.status() should confirm things:

mongos> for ( var x=0; x < 16; x++ ){
...   for( var y=0; y<16; y++ ) {
...   // for the innermost loop we will increment by 2 to get 2048 total iterations
...   // make this z++ for 4096 - that would give ~30MB chunks based on the original figures
...     for ( var z=0; z<16; z+=2 ) {
...     // now construct the GUID with zeroes for padding - handily the toString method takes an argument to specify the base
...         var prefix = "" + x.toString(16) + y.toString(16) + z.toString(16) + "00000000000000000000000000000";
...         // finally, use the split command to create the appropriate chunk
...         db.adminCommand( { split : "users.userInfo" , middle : { _id : prefix } } );
...     }
...   }
... }          
{ "ok" : 1 }
mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
    "_id" : 1,
    "version" : 4,
    "minCompatibleVersion" : 4,
    "currentVersion" : 5,
    "clusterId" : ObjectId("53467e59aea36af7b82a75c1")
}
  shards:
    {  "_id" : "shard0000",  "host" : "localhost:30000" }
    {  "_id" : "shard0001",  "host" : "localhost:30001" }
    {  "_id" : "shard0002",  "host" : "localhost:30002" }
    {  "_id" : "shard0003",  "host" : "localhost:30003" }
  databases:
    {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
    {  "_id" : "test",  "partitioned" : false,  "primary" : "shard0001" }
    {  "_id" : "users",  "partitioned" : true,  "primary" : "shard0001" }
        users.userInfo
            shard key: { "_id" : 1 }
            chunks:
                shard0001   513
                shard0000   512
                shard0002   512
                shard0003   512
            too many chunks to print, use verbose if you want to force print    

在那里,您已经拥有了-无需等待平衡器,分配已经是均匀的.

And there you have it - no waiting for the balancer, the distribution is already even.

这篇关于如何使用MongoDB以编程方式预分割基于GUID的分片键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆