Как программно предварительно разделить ключ Shard на основе GUID с MongoDB

Допустим, я использую довольно стандартный 32-символьный шестнадцатеричный GUID, и я определил, что, поскольку он генерируется случайным образом для моих пользователей, он идеально подходит для использования в качестве ключа шарда для горизонтального масштабирования записей в коллекцию MongoDB, которую я буду хранить пользовательская информация в (и масштабирование записи - моя главная задача).

Я также знаю, что мне нужно начинать как минимум с 4 осколков из-за прогнозов трафика и некоторой тестовой работы, выполненной в тестовой среде.

Наконец, у меня есть хорошее представление о моем начальном размере данных (средний размер документа * количество первоначальных пользователей), который составляет около 120 ГБ.

Я хотел бы сделать начальную загрузку приятной и быстрой и максимально использовать все 4 осколка. Как мне предварительно разделить эти данные, чтобы я смог воспользоваться четырьмя осколками и минимизировать количество ходов, разбиений и т. Д., Которые должны произойти с осколками во время начальной загрузки данных?

1 ответ

Решение

Мы знаем начальный размер данных (120 ГБ) и знаем, что максимальный размер фрагмента по умолчанию в MongoDB составляет 64 МБ. Если мы разделим 64 МБ на 120 ГБ, мы получим 1920 - так что это минимальное количество порций, с которых мы должны начать. Так получилось, что 2048 - это степень 16, деленная на 2, и учитывая, что GUID (наш ключ шарда) основан на шестнадцатеричном коде, с этим гораздо легче справиться, чем с 1920 (см. Ниже).

ПРИМЕЧАНИЕ. Это предварительное разбиение должно быть выполнено до добавления каких-либо данных в коллекцию. Если вы используете команду enableSharding() в коллекции, содержащей данные, MongoDB разделит сами данные, и вы будете выполнять их, пока чанки уже существуют - это может привести к довольно странному распределению чанков, так что будьте осторожны.

Для целей этого ответа, давайте предположим, что база данных будет называться users и коллекция называется userInfo, Давайте также предположим, что GUID будет записан в _id поле. С этими параметрами мы бы подключились к mongos и выполните следующие команды:

// 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 ракушка

// 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() помощник:

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 блоков (плюс один дополнительный благодаря минимальным / максимальным фрагментам), но все они все еще в исходном осколке, потому что балансировщик выключен. Итак, давайте снова включим балансировщик:

sh.startBalancer();

Это немедленно начнет балансировать, и это будет относительно быстро, потому что все порции пусты, но это все еще займет некоторое время (намного медленнее, если оно конкурирует с миграциями из других коллекций). По истечении некоторого времени запустите sh.status() снова и там у вас (должно быть) это - 2048 фрагментов все красиво разделены на 4 фрагмента и готовы к начальной загрузке данных:

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" }

Теперь вы готовы начать загрузку данных, но для полной гарантии того, что пока не завершится загрузка данных, не будет разделений или миграций, вам нужно сделать еще одну вещь - отключить балансировщик и автоматическое разделение на время импорта:

  • Чтобы отключить всю балансировку, запустите эту команду из монго: sh.stopBalancer()
  • Если вы хотите оставить другие операции балансировки работающими, вы можете отключить их для определенной коллекции. Используя приведенное выше пространство имен в качестве примера: sh.disableBalancing("users.userInfo")
  • Чтобы отключить автоматическое разделение во время загрузки, вам нужно будет перезагрузить каждый mongos вы будете использовать для загрузки данных с --noAutoSplit вариант.

После завершения импорта, при необходимости, выполните шаги в обратном порядке (sh.startBalancer(), sh.enableBalancing("users.userInfo") и перезапустите mongos без --noAutoSplit) вернуть все к настройкам по умолчанию.

**

Обновление: оптимизация для скорости

**

Подход выше подходит, если вы не спешите. Когда дела обстоят так, и как вы обнаружите, если вы проверите это, балансировщик не очень быстр - даже с пустыми кусками. Следовательно, по мере того, как вы увеличиваете количество созданных кусков, тем больше времени уходит на баланс. Я видел, что это заняло более 30 минут, чтобы завершить балансировку 2048 блоков, хотя это будет варьироваться в зависимости от развертывания.

Это может быть приемлемо для тестирования или для относительно тихого кластера, но при работе в занятом кластере будет гораздо сложнее обеспечить отключение балансировщика и не требовать других обновлений. Итак, как мы можем ускорить процесс?

Ответ состоит в том, чтобы сделать некоторые ручные шаги рано, а затем разделить куски, как только они окажутся на своих соответствующих осколках. Обратите внимание, что это желательно только для определенных ключей сегмента (например, случайно распределенного UUID) или определенных шаблонов доступа к данным, поэтому будьте осторожны, чтобы в результате вы не получили плохое распределение данных.

Используя приведенный выше пример, у нас есть 4 шарда, поэтому вместо того, чтобы делать все разбиения, а затем балансировать, мы вместо этого делим на 4. Затем мы помещаем по одному фрагменту в каждый осколок, перемещая их вручную, и, наконец, разделяем эти фрагменты на требуемое количество.

Диапазоны в приведенном выше примере будут выглядеть так:

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

Это всего 4 команды для их создания, но, поскольку у нас это есть, почему бы не использовать цикл выше в упрощенной / модифицированной форме:

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

Вот как выглядит мышление сейчас - у нас есть 4 блока, все на 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 moveChunk команды:

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 }          

Давайте еще раз проверим и убедимся, что фрагменты находятся там, где мы ожидаем, что они будут:

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() следует подтвердить вещи:

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    

И вот он у вас есть - не надо ждать балансировщика, распределение уже ровное.

Другие вопросы по тегам