Как программно предварительно разделить ключ 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
И вот он у вас есть - не надо ждать балансировщика, распределение уже ровное.