MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Comment pré-diviser par programme une clé de partition basée sur GUID avec MongoDB

Nous connaissons la taille initiale des données (120 Go) et nous savons que la taille de bloc maximale par défaut dans MongoDB est de 64 Mo. Si nous divisons 64 Mo en 120 Go, nous obtenons 1920 - c'est donc le nombre minimum de morceaux que nous devrions regarder pour commencer. Il se trouve que 2048 est une puissance de 16 divisé par 2, et étant donné que le GUID (notre clé de fragment) est basé sur l'hexagone, c'est un nombre beaucoup plus facile à gérer que 1920 (voir ci-dessous).

REMARQUE : Ce pré-split doit être fait avant toutes les données sont ajoutées à la collection. Si vous utilisez la commande enableSharding() sur une collection qui contient des données, MongoDB divisera les données elles-mêmes et vous exécuterez alors ceci alors que des morceaux existent déjà - cela peut conduire à une distribution de morceaux assez étrange, alors méfiez-vous.

Pour les besoins de cette réponse, supposons que la base de données s'appellera users et la collection s'appelle userInfo . Supposons également que le GUID sera écrit dans le _id domaine. Avec ces paramètres, nous nous connecterions à un mongos et exécutez les commandes suivantes :

// 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(); 

Maintenant, selon le calcul ci-dessus, nous devons diviser la plage GUID en 2048 morceaux. Pour ce faire, nous avons besoin d'au moins 3 chiffres hexadécimaux (16 ^ 3 =4096) et nous les mettrons dans les chiffres les plus significatifs (c'est-à-dire les 3 les plus à gauche) pour les plages. Encore une fois, cela devrait être exécuté à partir d'un mongos coquille

// 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 } } );
    }
  }
}

Une fois cela fait, vérifions l'état du jeu en utilisant le sh.status() aide :

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

Nous avons nos 2048 morceaux (plus un supplémentaire grâce aux morceaux min/max), mais ils sont tous toujours sur le fragment d'origine car l'équilibreur est désactivé. Alors, réactivons l'équilibreur :

sh.startBalancer();

Cela commencera immédiatement à s'équilibrer, et ce sera relativement rapide car tous les morceaux sont vides, mais cela prendra encore un peu de temps (beaucoup plus lent s'il est en concurrence avec des migrations d'autres collections). Une fois qu'un certain temps s'est écoulé, exécutez sh.status() encore une fois et là vous (devriez) l'avoir - 2048 morceaux tous bien répartis sur 4 fragments et prêts pour un chargement de données initial :

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

Vous êtes maintenant prêt à commencer à charger des données, mais pour garantir absolument qu'aucune division ou migration ne se produise tant que le chargement de vos données n'est pas terminé, vous devez faire une dernière chose :désactiver l'équilibreur et le fractionnement automatique pendant la durée de l'importation :

  • Pour désactiver tout équilibrage, exécutez cette commande depuis les mongos :sh.stopBalancer()
  • Si vous souhaitez laisser d'autres opérations d'équilibrage en cours d'exécution, vous pouvez les désactiver sur une collection spécifique. En utilisant l'espace de noms ci-dessus comme exemple :sh.disableBalancing("users.userInfo")
  • Pour désactiver le fractionnement automatique pendant le chargement, vous devrez redémarrer chaque mongos vous utiliserez pour charger les données avec le --noAutoSplit option.

Une fois l'importation terminée, inversez les étapes si nécessaire (sh.startBalancer() , sh.enableBalancing("users.userInfo") , et redémarrez le mongos sans --noAutoSplit ) pour tout remettre aux paramètres par défaut.

**

Mise à jour :Optimisation de la vitesse

**

L'approche ci-dessus convient si vous n'êtes pas pressé. Dans l'état actuel des choses, et comme vous le découvrirez si vous testez cela, l'équilibreur n'est pas très rapide - même avec des morceaux vides. Par conséquent, plus vous augmentez le nombre de morceaux que vous créez, plus il faudra de temps pour équilibrer. J'ai vu qu'il fallait plus de 30 minutes pour terminer l'équilibrage de 2 048 morceaux, bien que cela varie en fonction du déploiement.

Cela peut convenir pour les tests ou pour un cluster relativement silencieux, mais il sera beaucoup plus difficile de s'assurer que l'équilibreur est désactivé et qu'aucune autre mise à jour n'interfère sur un cluster occupé. Alors, comment accélérer les choses ?

La réponse est de faire quelques mouvements manuels tôt, puis de diviser les morceaux une fois qu'ils sont sur leurs fragments respectifs. Notez que cela n'est souhaitable qu'avec certaines clés de partition (comme un UUID distribué de manière aléatoire) ou certains modèles d'accès aux données. Veillez donc à ne pas vous retrouver avec une mauvaise distribution des données.

En utilisant l'exemple ci-dessus, nous avons 4 fragments, donc plutôt que de faire toutes les divisions, puis d'équilibrer, nous nous sommes divisés en 4 à la place. Nous plaçons ensuite un morceau sur chaque fragment en les déplaçant manuellement, puis nous divisons finalement ces morceaux en le nombre requis.

Les plages de l'exemple ci-dessus ressembleraient à ceci :

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

Il n'y a que 4 commandes pour les créer, mais puisque nous l'avons, pourquoi ne pas réutiliser la boucle ci-dessus sous une forme simplifiée/modifiée :

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

Voici à quoi ressemblent les pensées maintenant - nous avons nos 4 morceaux, tous sur 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)                    

Nous laisserons le $min morceau où il est, et déplacez les trois autres. Vous pouvez le faire par programmation, mais cela dépend de l'emplacement initial des morceaux, de la façon dont vous avez nommé vos fragments, etc. Je vais donc laisser ce manuel pour l'instant, ce n'est pas trop lourd - juste 3 moveChunk commandes :

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 }          

Vérifions une deuxième fois et assurons-nous que les morceaux sont là où nous nous attendons à ce qu'ils soient :

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)  

Cela correspond à nos gammes proposées ci-dessus, donc tout semble bon. Exécutez maintenant la boucle d'origine ci-dessus pour les diviser "en place" sur chaque fragment et nous devrions avoir une distribution équilibrée dès que la boucle se termine. Un autre sh.status() devrait confirmer les choses :

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    

Et voilà, pas d'attente pour l'équilibreur, la distribution est déjà régulière.