Sharding MongoDB : Guide pratique étape par étape



Le Sharding : une approche de partitionnement des données dans MongoDB

Le sharding, ou partitionnement, est un mécanisme qui permet de diviser un large ensemble de données en sous-ensembles plus restreints. Ces sous-ensembles sont ensuite répartis sur plusieurs instances MongoDB au sein d’un environnement distribué.

Qu’est-ce que le Sharding ?

Le sharding dans MongoDB offre une solution d’évolution horizontale pour la gestion de vastes quantités de données. Au lieu de stocker toutes les informations sur un unique serveur, le sharding répartit la charge sur plusieurs machines.

En pratique, il est irréaliste de stocker des données en croissance exponentielle sur une seule machine. Les requêtes ciblant d’énormes volumes de données stockées sur un seul serveur peuvent entraîner une forte consommation de ressources et des débits de lecture/écriture insatisfaisants.

Il existe principalement deux approches pour faire face à la croissance des données :

La mise à l’échelle verticale consiste à améliorer les performances d’un serveur unique en augmentant sa puissance de calcul (processeurs plus performants), sa mémoire vive (RAM) ou son espace de stockage. Cependant, cette approche a ses limites pratiques en termes de coûts et de technologies disponibles.

La mise à l’échelle horizontale, quant à elle, implique l’ajout de serveurs supplémentaires et la répartition de la charge sur ces différentes machines. Chaque serveur gère alors un sous-ensemble des données, ce qui offre une meilleure efficacité et une solution plus économique comparée à l’utilisation de matériel haut de gamme. Cette méthode nécessite toutefois une gestion accrue d’une infrastructure complexe avec un grand nombre de serveurs.

Le sharding de MongoDB s’appuie sur la technique de mise à l’échelle horizontale.

Composants du Sharding

Pour mettre en œuvre le sharding dans MongoDB, les composants suivants sont nécessaires :

  • Shard : Il s’agit d’une instance MongoDB qui gère un sous-ensemble des données initiales. Les shards doivent être déployés dans des ensembles de réplication.
  • Mongos : Cette instance MongoDB sert d’interface entre les applications clientes et le cluster partitionné. Elle agit comme un routeur, dirigeant les requêtes vers les shards appropriés.
  • Serveur de Configuration (Config Server) : Cette instance MongoDB stocke les métadonnées et les détails de configuration du cluster. MongoDB exige que le serveur de configuration soit déployé sous forme d’ensemble de réplication.

Architecture du Sharding

Un cluster MongoDB partitionné est constitué de plusieurs ensembles de réplication.

Chaque ensemble de réplication comprend au minimum trois instances MongoDB. Un cluster partitionné peut être constitué de plusieurs instances de shards MongoDB, chacune fonctionnant dans un ensemble de réplication. L’application interagit avec Mongos, qui communique ensuite avec les shards. Ainsi, dans un environnement partitionné, les applications ne dialoguent jamais directement avec les nœuds de partitions. Le routeur distribue les sous-ensembles de données entre les nœuds de partition, en fonction de la clé de partition.

Implémentation du Sharding

Voici les étapes à suivre pour configurer le sharding:

Étape 1

  • Démarrer les serveurs de configuration dans un ensemble de réplication et activer 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

  • Initialiser l’ensemble de réplication 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émarrer les serveurs de partitionnement (shards) dans un ensemble de réplication et activer 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 comme étant le principal. Pour déplacer l’utilisation du serveur de partitionnement principal, utiliser la méthode movePrimary.

Étape 4

  • Initialiser l’ensemble de réplication sur l’un des serveurs de partitionnement.

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 instances Mongos pour le cluster partitionné.

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

Étape 6

  • Se connecter au serveur routeur mongo.

mongo-port 40000

  • Ajouter les 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

  • Dans le shell mongo, activer 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 une collection, une clé de partition (shard key), dont nous parlerons plus loin, 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, il faut la créer comme ceci :

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 à indiquer qu’un équilibreur est en activité pour répartir les données entre les shards.

Étape 10

Enfin, il faut vérifier le statut du sharding. Cela peut être fait en exécutant la commande ci-dessous sur le nœud de routage Mongos.

Statut du Sharding

Vérifier le statut du sharding en exécutant la commande suivante sur le nœud de routage 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>

Répartition des Données

Le routeur Mongos répartit la charge entre les shards en fonction de la clé de shard et distribue uniformément les données. L’équilibreur entre en action pour ajuster cette répartition si nécessaire.

Voici les éléments clés pour la distribution des données entre les shards :

  • L’équilibreur joue un rôle essentiel dans la répartition des données entre les nœuds de partition. Il entre en action lorsque Mongos démarre afin de distribuer les charges entre les shards. Une fois démarré, l’équilibreur distribue les données plus uniformément. Pour vérifier son état, vous pouvez exécuter sh.status(), sh.getBalancerState() ou sh.isBalancerRunning().
mongos> sh.isBalancerRunning()
true
mongos>

OU

mongos> sh.getBalancerState()
true
mongos>

Après l’insertion des données, l’activité dans le démon Mongos indique le déplacement des morceaux vers des shards spécifiques. L’équilibreur est en action pour tenter d’équilibrer la distribution des données. L’exécution de l’équilibreur peut avoir des conséquences sur les performances. Il est donc conseillé de le faire fonctionner durant des fenêtres d’équilibrage planifiées.

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>
  • La clé de partition détermine comment les documents d’une collection partitionnée seront distribués entre les shards. Il peut s’agir d’un champ indexé ou d’un champ composite indexé qui doit être présent dans tous les documents à insérer. Les données sont divisées en morceaux, et chaque morceau est associé à une clé de partition, en fonction de sa plage de valeurs. Le routeur décide quel shard doit stocker un morceau en fonction des plages de clés de partition.

La sélection d’une clé de partition doit prendre en compte cinq critères :

  • Cardinalité
  • Répartition en écriture
  • Répartition en lecture
  • Ciblage des lectures
  • Localité des lectures

Une clé de partition idéale permet à MongoDB de répartir uniformément la charge entre tous les shards. Il est donc crucial de choisir la bonne clé de partition.

Supprimer un Nœud de Partition

Avant de supprimer des shards d’un cluster, il est impératif de s’assurer que les données sont migrées en toute sécurité vers les shards restants. MongoDB gère le déplacement sécurisé des données vers d’autres nœuds avant la suppression d’un nœud.

Exécuter la commande suivante pour supprimer un shard :

Étape 1

Il faut d’abord déterminer le nom d’hôte du shard à supprimer. La commande suivante liste tous les shards présents dans le cluster avec leur état :

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écuter la commande suivante pour supprimer le shard souhaité du cluster. L’équilibreur gère la suppression des morceaux du shard en cours de suppression, puis rééquilibre la distribution des morceaux entre les shards 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 de la vidange du shard, exécuter à nouveau la même commande.

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

Il faut attendre que la vidange des données soit terminée. Les champs msg et state indiqueront si la vidange est terminée ou non :

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

Il est également possible de vérifier le statut via la commande sh.status(). Si la vidange est en cours, le shard apparaîtra avec un statut de drainage égal à true.

Étape 4

Continuer à vérifier l’état de la vidange avec la même commande jusqu’à ce que le shard souhaité soit complètement supprimé.
Une fois terminée, la commande indiquera le message et l’état suivants :

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

Étape 5

Enfin, il faut vérifier les shards restants dans le cluster. Pour vérifier l’état, utiliser la commande 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)
                }
        }
}

On constate que le shard supprimé n’apparaît plus dans la liste.

Avantages du Sharding par Rapport à la Réplication

  • En réplication, le nœud primaire gère toutes les opérations d’écriture, tandis que les serveurs secondaires maintiennent des copies de sauvegarde ou exécutent des opérations en lecture seule. Dans le sharding avec ensembles de réplication, la charge est répartie sur plusieurs serveurs.
  • Un ensemble de réplication unique est limité à 12 nœuds, tandis qu’il n’y a pas de limite au nombre de shards.
  • La réplication nécessite du matériel haut de gamme ou une mise à l’échelle verticale pour gérer de grands ensembles de données, ce qui est coûteux par rapport à l’ajout de serveurs dans le partitionnement.
  • Dans la réplication, les performances de lecture sont améliorées en ajoutant plus de serveurs secondaires. Dans le partitionnement, les performances de lecture et d’écriture sont améliorées en ajoutant des nœuds de partition.

Limitations du Sharding

  • Le cluster partitionné ne prend pas en charge l’indexation unique sur les shards, à moins que l’index unique ne soit préfixé par la clé de partition complète.
  • Toute opération de mise à jour sur une collection partitionnée doit inclure la clé de partition ou le champ _id dans la requête.
  • Les collections ne peuvent être partitionnées que si leur taille est inférieure à un seuil spécifique. Ce seuil dépend de la taille moyenne des clés de partition et de la taille configurée des morceaux.
  • Le partitionnement comprend des limites opérationnelles sur la taille maximale des collections ou le nombre de fractionnements.
  • Un mauvais choix de clé de partition peut entraîner des problèmes de performances.

Conclusion

MongoDB offre une solution de partitionnement intégrée pour gérer les grandes bases de données sans compromettre les performances. Cet article devrait vous aider à configurer le sharding avec MongoDB. Vous pourriez ensuite vous familiariser avec les commandes MongoDB les plus courantes.