Sharding MongoDB : Guide pratique étape par étape

Le sharding est un processus de division de la grande échelle des ensembles de données en un bloc d’ensembles de données plus petits sur plusieurs instances MongoDB dans un environnement distribué.

Qu’est-ce que le partage ?

Le partage MongoDB nous fournit une solution évolutive pour stocker une grande quantité de données parmi le nombre de serveurs plutôt que de les stocker sur un seul serveur.

Concrètement, il n’est pas possible de stocker des données en croissance exponentielle sur une seule machine. L’interrogation d’une énorme quantité de données stockées sur un seul serveur peut entraîner une utilisation élevée des ressources et peut ne pas fournir un débit de lecture et d’écriture satisfaisant.

Fondamentalement, il existe deux types de méthodes de mise à l’échelle pour entreprendre la croissance des données avec le système :

La mise à l’échelle verticale fonctionne avec l’amélioration des performances d’un seul serveur en ajoutant des processeurs plus puissants, en mettant à niveau la RAM ou en ajoutant plus d’espace disque au système. Mais il y a les implications possibles de l’application de la mise à l’échelle verticale dans des cas d’utilisation pratiques avec la technologie et les configurations matérielles existantes.

La mise à l’échelle horizontale fonctionne avec l’ajout de serveurs supplémentaires et répartit la charge sur plusieurs serveurs. Étant donné que chaque machine gérera le sous-ensemble de l’ensemble de données, cela offre une meilleure efficacité et une solution rentable plutôt que de déployer le matériel haut de gamme. Mais cela nécessite une maintenance supplémentaire d’une infrastructure complexe avec un grand nombre de serveurs.

Le partitionnement Mongo DB fonctionne sur la technique de mise à l’échelle horizontale.

Partage de composants

Pour réaliser le sharding dans MongoDB, les composants suivants sont requis :

Shard est une instance Mongo pour gérer un sous-ensemble de données d’origine. Les fragments doivent être déployés dans le jeu de répliques.

Mongos est une instance Mongo et agit comme une interface entre une application cliente et un cluster partitionné. Il fonctionne comme un routeur de requêtes vers des fragments.

Config Server est une instance Mongo qui stocke les informations de métadonnées et les détails de configuration du cluster. MongoDB nécessite que le serveur de configuration soit déployé en tant que jeu de répliques.

Architecture de partage

Le cluster MongoDB se compose d’un certain nombre d’ensembles de répliques.

Chaque jeu de répliques se compose d’au moins 3 instances mongo ou plus. Un cluster fragmenté peut être composé de plusieurs instances de fragments mongo, et chaque instance de fragment fonctionne dans un ensemble de répliques de fragments. L’application interagit avec Mongos, qui à son tour communique avec les fragments. Par conséquent, dans Sharding, les applications n’interagissent jamais directement avec les nœuds de partition. Le routeur de requête distribue les sous-ensembles de données entre les nœuds de partition en fonction de la clé de partition.

Mise en œuvre du partage

Suivez les étapes ci-dessous pour le partage

Étape 1

  • Démarrez le serveur de configuration dans le jeu de réplicas et activez la réplication entre eux.

mongod –configsvr –port 27019 –replSet rs0 –dbpath C:datadata1 –bind_ip localhost

mongod –configsvr –port 27018 –replSet rs0 –dbpath C:datadata2 –bind_ip localhost

mongod –configsvr –port 27017 –replSet rs0 –dbpath C:datadata3 –bind_ip localhost

Étape 2

  • Initialisez le jeu de répliques sur l’un des serveurs de configuration.

rs.initiate( { _id : « rs0 », configsvr : vrai, membres : [   { _id: 0, host: “IP:27017” },   { _id: 1, host: “IP:27018” },   { _id: 2, host: “IP:27019” }    ] })

rs.initiate( { _id : "rs0",  configsvr: true,  members: [   { _id: 0, host: "IP:27017" },   { _id: 1, host: "IP:27018" },   { _id: 2, host: "IP:27019" }    ] })
{
        "ok" : 1,
        "$gleStats" : {
                "lastOpTime" : Timestamp(1593569257, 1),
                "electionId" : ObjectId("000000000000000000000000")
        },
        "lastCommittedOpTime" : Timestamp(0, 0),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593569257, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        },
        "operationTime" : Timestamp(1593569257, 1)
}

Étape 3

  • Démarrez le partitionnement des serveurs dans le jeu de répliques et activez la réplication entre eux.

mongod –shardsvr –port 27020 –replSet rs1 –dbpath C:datadata4 –bind_ip localhost

mongod –shardsvr –port 27021 –replSet rs1 –dbpath C:datadata5 –bind_ip localhost

mongod –shardsvr –port 27022 –replSet rs1 –dbpath C:datadata6 –bind_ip localhost

MongoDB initialise le premier serveur de partitionnement en tant que principal, pour déplacer l’utilisation du serveur de partitionnement principal movePrimary méthode.

Étape 4

  • Initialisez le jeu de répliques sur l’un des serveurs partitionnés.

rs.initiate( { _id : « rs0 », membres : [   { _id: 0, host: “IP:27020” },   { _id: 1, host: “IP:27021” },   { _id: 2, host: “IP:27022” }    ] })

rs.initiate( { _id : "rs0",  members: [   { _id: 0, host: "IP:27020" },   { _id: 1, host: "IP:27021" },   { _id: 2, host: "IP:27022" }    ] })
{
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593569748, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        },
        "operationTime" : Timestamp(1593569748, 1)
}

Étape 5

  • Démarrer les mangues pour le cluster fragmenté

mongos –port 40000 –configdb rs0/localhost:27019,localhost:27018, localhost:27017

Étape 6

  • Connectez le serveur de route mongo

mongo-port 40000

  • Maintenant, ajoutez des serveurs de partitionnement.

sh.addShard( « rs1/localhost:27020,localhost:27021,localhost:27022 »)

sh.addShard( "rs1/localhost:27020,localhost:27021,localhost:27022")
{
        "shardAdded" : "rs1",
        "ok" : 1,
        "operationTime" : Timestamp(1593570212, 2),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593570212, 2),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Étape 7

  • Sur mongo shell, activez le partitionnement sur la base de données et les collections.
  • Activer le partitionnement sur la base de données

sh.enableSharding(« geekFlareDB »)

sh.enableSharding("geekFlareDB")
{
        "ok" : 1,
        "operationTime" : Timestamp(1591630612, 1),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1591630612, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Étape 8

  • Pour partitionner la clé de partition de collection (décrite plus loin dans cet article) est nécessaire.

Syntaxe : sh.shardCollection(« dbName.collectionName », { « key » : 1 } )

sh.shardCollection("geekFlareDB.geekFlareCollection", { "key" : 1 } )
{
        "collectionsharded" : "geekFlareDB.geekFlareCollection",
        "collectionUUID" : UUID("0d024925-e46c-472a-bf1a-13a8967e97c1"),
        "ok" : 1,
        "operationTime" : Timestamp(1593570389, 3),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593570389, 3),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Notez que si la collection n’existe pas, créez-la comme suit.

db.createCollection("geekFlareCollection")
{
        "ok" : 1,
        "operationTime" : Timestamp(1593570344, 4),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593570344, 5),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Étape 9

Insérez des données dans la collection. Les journaux Mongo commenceront à croître et indiqueront qu’un équilibreur est en action et tente d’équilibrer les données entre les fragments.

Étape 10

La dernière étape consiste à vérifier l’état du sharding. L’état peut être vérifié en exécutant la commande ci-dessous sur le nœud de route Mongos.

Statut de partitionnement

Vérifiez l’état du partitionnement en exécutant la commande ci-dessous sur le nœud de route mongo.

sh.status()

mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "minCompatibleVersion" : 5,
        "currentVersion" : 6,
        "clusterId" : ObjectId("5ede66c22c3262378c706d21")
  }
  shards:
        {  "_id" : "rs1",  "host" : "rs1/localhost:27020,localhost:27021,localhost:27022",  "state" : 1 }
  active mongoses:
        "4.2.7" : 1
  autosplit:
        Currently enabled: yes
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  5
        Last reported error:  Could not find host matching read preference { mode: "primary" } for set rs1
        Time of Reported error:  Tue Jun 09 2020 15:25:03 GMT+0530 (India Standard Time)
        Migration Results for the last 24 hours:
                No recent migrations
  databases:
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                config.system.sessions
                        shard key: { "_id" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs1     1024
                        too many chunks to print, use verbose if you want to force print
        {  "_id" : "geekFlareDB",  "primary" : "rs1",  "partitioned" : true,  "version" : {  "uuid" : UUID("a770da01-1900-401e-9f34-35ce595a5d54"),  "lastMod" : 1 } }
                geekFlareDB.geekFlareCol
                        shard key: { "key" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs1     1
                        { "key" : { "$minKey" : 1 } } -->> { "key" : { "$maxKey" : 1 } } on : rs1 Timestamp(1, 0)
                geekFlareDB.geekFlareCollection
                        shard key: { "product" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs1     1
                        { "product" : { "$minKey" : 1 } } -->> { "product" : { "$maxKey" : 1 } } on : rs1 Timestamp(1, 0)
        {  "_id" : "test",  "primary" : "rs1",  "partitioned" : false,  "version" : {  "uuid" : UUID("fbc00f03-b5b5-4d13-9d09-259d7fdb7289"),  "lastMod" : 1 } }

mongos>

Diffusion des données

Le routeur Mongos répartit la charge entre les fragments en fonction de la clé de fragment et répartit uniformément les données ; l’équilibreur entre en action.

Le composant clé pour distribuer les données entre les fragments est

  • Un équilibreur joue un rôle dans l’équilibrage du sous-ensemble de données entre les nœuds partitionnés. L’équilibreur s’exécute lorsque le serveur Mongos commence à répartir les charges entre les fragments. Une fois démarré, Balancer a distribué les données plus uniformément. Pour vérifier l’état de l’équilibreur, exécutez sh.status() ou sh.getBalancerState() ou sh.isBalancerRunning().
mongos> sh.isBalancerRunning()
true
mongos>

OU

mongos> sh.getBalancerState()
true
mongos>

Après avoir inséré les données, nous avons pu remarquer une activité dans le démon Mongos indiquant qu'il déplace certains morceaux pour les fragments spécifiques, etc., c'est-à-dire que l'équilibreur sera en action pour essayer d'équilibrer les données entre les fragments. L'exécution de l'équilibreur peut entraîner des problèmes de performances ; par conséquent, il est suggéré de faire fonctionner l'équilibreur dans un certain fenêtre d'équilibrage.

mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "minCompatibleVersion" : 5,
        "currentVersion" : 6,
        "clusterId" : ObjectId("5efbeff98a8bbb2d27231674")
  }
  shards:
        {  "_id" : "rs1",  "host" : "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022",  "state" : 1 }
        {  "_id" : "rs2",  "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",  "state" : 1 }
  active mongoses:
        "4.2.7" : 1
  autosplit:
        Currently enabled: yes
  balancer:
        Currently enabled:  yes
        Currently running:  yes
        Failed balancer rounds in last 5 attempts:  5
        Last reported error:  Could not find host matching read preference { mode: "primary" } for set rs2
        Time of Reported error:  Wed Jul 01 2020 14:39:59 GMT+0530 (India Standard Time)
        Migration Results for the last 24 hours:
                1024 : Success
  databases:
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                config.system.sessions
                        shard key: { "_id" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs2     1024
                        too many chunks to print, use verbose if you want to force print
        {  "_id" : "geekFlareDB",  "primary" : "rs2",  "partitioned" : true,  "version" : {  "uuid" : UUID("a8b8dc5c-85b0-4481-bda1-00e53f6f35cd"),  "lastMod" : 1 } }
                geekFlareDB.geekFlareCollection
                        shard key: { "key" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs2     1
                        { "key" : { "$minKey" : 1 } } -->> { "key" : { "$maxKey" : 1 } } on : rs2 Timestamp(1, 0)
        {  "_id" : "test",  "primary" : "rs2",  "partitioned" : false,  "version" : {  "uuid" : UUID("a28d7504-1596-460e-9e09-0bdc6450028f"),  "lastMod" : 1 } }

mongos>
  • Shard Key détermine la logique de distribution des documents de la collection fragmentée entre les fragments. La clé de partition peut être un champ indexé ou un champ composé indexé qui doit être présent dans tous les documents de la collection à insérer. Les données seront partitionnées en morceaux, et chaque morceau sera associé à la clé de partition basée sur la plage. Sur la base de la plage de requêtes, le routeur décidera quel fragment stockera le morceau.

Shard Key peut être sélectionné en considérant cinq propriétés :

  • Cardinalité
  • Répartition en écriture
  • Lire la distribution
  • Ciblage de lecture
  • Lire la localité

Une clé de partition idéale permet à MongoDB de répartir uniformément la charge entre toutes les partitions. Choisir une bonne clé de partage est extrêmement important.

Image : MongoDB

Suppression du nœud de partition

Avant de supprimer des partitions du cluster, l'utilisateur doit assurer la migration sécurisée des données vers les partitions restantes. MongoDB s'occupe de drainer en toute sécurité les données vers d'autres nœuds de partition avant la suppression du nœud de partition requis.

Exécutez la commande ci-dessous pour supprimer le fragment requis.

Étape 1

Tout d'abord, nous devons déterminer le nom d'hôte du fragment à supprimer. La commande ci-dessous répertorie tous les fragments présents dans le cluster avec l'état du fragment.

db.adminCommand( { listShards : 1 } )

mongos> db.adminCommand( { listShards: 1 } )
{
        "shards" : [
                {
                        "_id" : "rs1",
                        "host" : "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022",
                        "state" : 1
                },
                {
                        "_id" : "rs2",
                        "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",
                        "state" : 1
                }
        ],
        "ok" : 1,
        "operationTime" : Timestamp(1593572866, 15),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593572866, 15),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Étape 2

Exécutez la commande ci-dessous pour supprimer le fragment requis du cluster. Une fois émis, l'équilibreur s'occupe de la suppression des morceaux du nœud de fragment drainant, puis équilibre la distribution des morceaux restants entre les nœuds de fragments restants.

db.adminCommand( { removeShard : "shardedReplicaNodes" } )

mongos> db.adminCommand( { removeShard: "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022" } )
{
        "msg" : "draining started successfully",
        "state" : "started",
        "shard" : "rs1",
        "note" : "you need to drop or movePrimary these databases",
        "dbsToMove" : [ ],
        "ok" : 1,
        "operationTime" : Timestamp(1593572385, 2),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593572385, 2),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Étape 3

Pour vérifier l'état du fragment de drainage, exécutez à nouveau la même commande.

db.adminCommand( { removeShard : "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022" } )

Nous devons attendre que le drainage des données soit terminé. Les champs msg et state indiqueront si le drainage des données est terminé ou non, comme suit

"msg" : "draining ongoing",
"state" : "ongoing",

Nous pouvons également vérifier le statut avec la commande sh.status(). Une fois supprimé, le nœud fragmenté ne sera pas reflété dans la sortie. Mais si le drainage est en cours, le nœud fragmenté viendra avec le statut de drainage comme vrai.

Étape 4

Continuez à vérifier l'état de la vidange avec la même commande ci-dessus, jusqu'à ce que le fragment requis soit complètement supprimé.
Une fois terminé, la sortie de la commande reflétera le message et l'état comme terminé.

"msg" : "removeshard completed successfully",
"state" : "completed",
"shard" : "rs1",
"ok" : 1,

Étape 5

Enfin, nous devons vérifier les fragments restants dans le cluster. Pour vérifier l'état, entrez sh.status() ou db.adminCommand( { listShards : 1 } )

mongos> db.adminCommand( { listShards: 1 } )
{
        "shards" : [
                {
                        "_id" : "rs2",
                        "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",
                        "state" : 1
                }
        ],
        "ok" : 1,
        "operationTime" : Timestamp(1593575215, 3),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593575215, 3),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

Ici, nous pouvons voir que le fragment supprimé n'est plus présent dans la liste des fragments.

Avantages du partage par rapport à la réplication

  • Dans la réplication, le nœud principal gère toutes les opérations d'écriture, tandis que les serveurs secondaires doivent conserver des copies de sauvegarde ou effectuer des opérations en lecture seule. Mais dans le partage avec des jeux de répliques, la charge est répartie entre plusieurs serveurs.
  • Un jeu de réplicas unique est limité à 12 nœuds, mais il n'y a aucune restriction sur le nombre de fragments.
  • La réplication nécessite un matériel haut de gamme ou une mise à l'échelle verticale pour gérer de grands ensembles de données, ce qui est trop coûteux par rapport à l'ajout de serveurs supplémentaires dans le partitionnement.
  • Dans la réplication, les performances de lecture peuvent être améliorées en ajoutant plus de serveurs esclaves/secondaires, tandis que, dans le partitionnement, les performances de lecture et d'écriture seront améliorées en ajoutant plus de nœuds de partitions.

Limitation du partage

  • Le cluster partagé ne prend pas en charge l'indexation unique sur les partitions tant que l'index unique n'est pas précédé d'une clé de partition complète.
  • Toutes les opérations de mise à jour de la collection partitionnée sur un ou plusieurs documents doivent contenir la clé partitionnée ou le champ _id dans la requête.
  • Les collections peuvent être partitionnées si leur taille ne dépasse pas le seuil spécifié. Ce seuil peut être estimé sur la base de la taille moyenne de toutes les clés de partition et de la taille configurée des blocs.
  • Le partitionnement comprend des limites opérationnelles sur la taille maximale de la collection ou le nombre de fractionnements.
  • Choisir les mauvaises clés de partition pour avoir des conséquences sur les performances.

Conclusion

MongoDB offre un partitionnement intégré pour implémenter une grande base de données sans compromettre les performances. J'espère que ce qui précède vous aidera à configurer le partage MongoDB. Ensuite, vous souhaiterez peut-être vous familiariser avec certaines des commandes MongoDB couramment utilisées.