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

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

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

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

Я хочу сделать начальную загрузку красивой и быстрой и использовать все 4 осколки как можно больше. Как предварительно разбить эти данные, чтобы я использовал 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 кусков (плюс один дополнительный благодаря min/max chunks), но они все еще находятся на первоначальном осколке, потому что балансир выключен. Итак, позвольте снова включить балансировку:

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

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

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

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

**

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

**

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

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

Ответ заключается в том, чтобы сделать некоторые ручные шаги раньше, а затем разделить куски, когда они находятся на их соответствующих осколках. Обратите внимание, что это желательно только с определенными ключами осколков (например, случайно распределенным 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    

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