MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Como pré-dividir programaticamente uma chave de fragmentação baseada em GUID com o MongoDB


Sabemos o tamanho inicial dos dados (120 GB) e sabemos que o tamanho máximo padrão do bloco no MongoDB é de 64 MB. Se dividirmos 64 MB em 120 GB, obteremos 1920 - então esse é o número mínimo de pedaços que devemos procurar para começar. Acontece que 2048 é uma potência de 16 dividida por 2, e dado que o GUID (nossa chave de fragmentação) é baseado em hexadecimal, esse é um número muito mais fácil de lidar do que 1920 (veja abaixo).

OBSERVAÇÃO: Esta pré-divisão deve ser feita antes quaisquer dados são adicionados à coleção. Se você usar o comando enableSharding() em uma coleção que contém dados, o MongoDB dividirá os dados em si e você estará executando isso enquanto os pedaços já existirem - isso pode levar a uma distribuição de pedaços bastante estranha, portanto, cuidado.

Para os propósitos desta resposta, vamos supor que o banco de dados será chamado de users e a coleção é chamada userInfo . Vamos supor também que o GUID será escrito no _id campo. Com esses parâmetros nos conectaríamos a um mongos e execute os seguintes comandos:
// 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(); 

Agora, de acordo com o cálculo acima, precisamos dividir o intervalo GUID em 2048 partes. Para fazer isso, precisamos de pelo menos 3 dígitos hexadecimais (16 ^ 3 =4096) e os colocaremos nos dígitos mais significativos (ou seja, os 3 mais à esquerda) para os intervalos. Novamente, isso deve ser executado a partir de um mongos Concha
// 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 } } );
    }
  }
}

Feito isso, vamos verificar o estado do jogo usando o sh.status() ajudante:
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

Temos nossos 2048 fragmentos (mais um extra graças aos fragmentos min/max), mas todos eles ainda estão no fragmento original porque o balanceador está desligado. Então, vamos reativar o balanceador:
sh.startBalancer();

Isso começará a se equilibrar imediatamente e será relativamente rápido porque todos os pedaços estão vazios, mas ainda levará um pouco de tempo (muito mais lento se estiver competindo com migrações de outras coleções). Depois de algum tempo, execute sh.status() novamente e lá você (deveria) tê-lo - 2048 pedaços todos bem divididos em 4 fragmentos e prontos para um carregamento de dados inicial:
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" }

Agora você está pronto para começar a carregar os dados, mas para garantir absolutamente que nenhuma divisão ou migração ocorra até que o carregamento de dados seja concluído, você precisa fazer mais uma coisa - desativar o balanceador e a divisão automática durante a importação:
  • Para desabilitar todo o balanceamento, execute este comando do mongos:sh.stopBalancer()
  • Se você quiser deixar outras operações de balanceamento em execução, desative uma coleção específica. Usando o namespace acima como exemplo:sh.disableBalancing("users.userInfo")
  • Para desativar a divisão automática durante o carregamento, você precisará reiniciar cada mongos você usará para carregar os dados com o --noAutoSplit opção.

Quando a importação estiver concluída, inverta as etapas conforme necessário (sh.startBalancer() , sh.enableBalancing("users.userInfo") , e reinicie o mongos sem --noAutoSplit ) para retornar tudo às configurações padrão.

**

Atualização:otimizando para velocidade


**

A abordagem acima é boa se você não estiver com pressa. Do jeito que as coisas estão, e como você descobrirá se testar isso, o balanceador não é muito rápido - mesmo com pedaços vazios. Portanto, à medida que você aumenta o número de pedaços que cria, mais tempo levará para equilibrar. Eu vi levar mais de 30 minutos para concluir o balanceamento de 2.048 partes, embora isso varie dependendo da implantação.

Isso pode ser bom para testes ou para um cluster relativamente silencioso, mas desligar o balanceador e não exigir nenhuma interferência de outras atualizações será muito mais difícil de garantir em um cluster ocupado. Então, como podemos acelerar as coisas?

A resposta é fazer alguns movimentos manuais cedo e depois dividir os pedaços assim que estiverem em seus respectivos fragmentos. Observe que isso só é desejável com determinadas chaves de fragmentação (como um UUID distribuído aleatoriamente) ou determinados padrões de acesso a dados, portanto, tome cuidado para não acabar com uma distribuição de dados ruim como resultado.

Usando o exemplo acima temos 4 shards, então ao invés de fazer todas as divisões e balancear, nós dividimos em 4. Em seguida, colocamos um pedaço em cada fragmento movendo-os manualmente e, finalmente, dividimos esses pedaços no número necessário.

Os intervalos no exemplo acima ficariam assim:
$min --> "40000000000000000000000000000000"
"40000000000000000000000000000000" --> "80000000000000000000000000000000"
"80000000000000000000000000000000" --> "c0000000000000000000000000000000"
"c0000000000000000000000000000000" --> $max     

São apenas 4 comandos para criá-los, mas já que temos, por que não reutilizar o loop acima de uma forma simplificada/modificada:
for ( var x=4; x < 16; x+=4){
    var prefix = "" + x.toString(16) + "0000000000000000000000000000000";
    db.adminCommand( { split : "users.userInfo" , middle : { _id : prefix } } ); 
} 

Veja como as ideias estão agora - temos nossos 4 pedaços, todos em 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)                    

Vamos deixar o $min pedaço onde está, e mova os outros três. Você pode fazer isso programaticamente, mas depende de onde os pedaços residem inicialmente, como você nomeou seus fragmentos etc. comandos:
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 }          

Vamos verificar novamente e garantir que os pedaços estejam onde esperamos que estejam:
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)  

Isso corresponde aos nossos intervalos propostos acima, então tudo parece bom. Agora execute o loop original acima para dividi-los "no lugar" em cada shard e devemos ter uma distribuição balanceada assim que o loop terminar. Mais um sh.status() deve confirmar as coisas:
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    

E aí está - sem esperar pelo balanceador, a distribuição já está equilibrada.