D'excellentes performances de base de données sont importantes lorsque vous développez des applications avec MongoDB. Parfois, le processus global de diffusion des données peut se dégrader pour plusieurs raisons, dont certaines incluent :
- Modèles de conception de schéma inappropriés
- Utilisation inappropriée ou absence d'utilisation des stratégies d'indexation
- Matériel inadéquat
- Délai de réplication
- Techniques d'interrogation peu performantes
Certains de ces revers peuvent vous obliger à augmenter les ressources matérielles, d'autres non. Par exemple, des structures de requête médiocres peuvent entraîner un temps de traitement long de la requête, ce qui entraîne un décalage du réplica et peut-être même une perte de données. Dans ce cas, on peut penser que la mémoire de stockage n'est peut-être pas suffisante et qu'elle doit probablement être augmentée. Cet article décrit les procédures les plus appropriées que vous pouvez utiliser pour améliorer les performances de votre base de données MongoDB.
Conception de schéma
Fondamentalement, les deux relations de schéma les plus couramment utilisées sont...
- Un à quelques
- Un à plusieurs
Alors que la conception de schéma la plus efficace est la relation un-à-plusieurs, chacun a ses propres avantages et limites.
Un à quelques
Dans ce cas, pour un champ donné, il y a des documents intégrés mais ils ne sont pas indexés avec l'identité de l'objet.
Voici un exemple simple :
{
userName: "Brian Henry",
Email : "[email protected]",
grades: [
{subject: ‘Mathematics’, grade: ‘A’},
{subject: English, grade: ‘B’},
]
}
L'un des avantages de l'utilisation de cette relation est que vous pouvez obtenir les documents intégrés avec une seule requête. Cependant, du point de vue de l'interrogation, vous ne pouvez pas accéder à un seul document incorporé. Donc, si vous n'allez pas référencer les documents intégrés séparément, il sera optimal d'utiliser cette conception de schéma.
Un à plusieurs
Pour cette relation, les données d'une base de données sont liées aux données d'une autre base de données. Par exemple, vous pouvez avoir une base de données pour les utilisateurs et une autre pour les publications. Ainsi, si un utilisateur publie un message, il est enregistré avec l'identifiant de l'utilisateur.
Schéma des utilisateurs
{
Full_name: “John Doh”,
User_id: 1518787459607.0
}
Schéma des articles
{
"_id" : ObjectId("5aa136f0789cf124388c1955"),
"postTime" : "16:13",
"postDate" : "8/3/2018",
"postOwnerNames" : "John Doh",
"postOwner" : 1518787459607.0,
"postId" : "1520514800139"
}
L'avantage de cette conception de schéma est que les documents sont considérés comme autonomes (peuvent être sélectionnés séparément). Un autre avantage est que cette conception permet aux utilisateurs de différents identifiants de partager des informations à partir du schéma des messages (d'où le nom One-to-Many) et peut parfois être un schéma "N-to-N" - essentiellement sans utiliser de jointure de table. La limitation de cette conception de schéma est que vous devez effectuer au moins deux requêtes pour extraire ou sélectionner des données dans la deuxième collection.
La manière de modéliser les données dépendra donc du modèle d'accès de l'application. En plus de cela, vous devez tenir compte de la conception du schéma dont nous avons discuté ci-dessus.
Techniques d'optimisation pour la conception de schémas
-
Utilisez autant que possible l'incorporation de documents, car cela réduit le nombre de requêtes que vous devez exécuter pour un ensemble de données particulier.
-
N'utilisez pas la dénormalisation pour les documents fréquemment mis à jour. Si un champ doit être fréquemment mis à jour, il y aura alors la tâche de trouver toutes les instances qui doivent être mises à jour. Cela entraînera un traitement lent des requêtes, écrasant ainsi même les mérites associés à la dénormalisation.
-
S'il est nécessaire de récupérer un document séparément, il n'est pas nécessaire d'utiliser l'intégration car les requêtes complexes telles que le pipelining agrégé prennent plus de temps à s'exécuter.
-
Si le tableau de documents à incorporer est suffisamment grand, ne les intégrez pas. La croissance du tableau doit au moins avoir une limite liée.
Indexation correcte
Il s'agit de la partie la plus critique du réglage des performances et nécessite une compréhension approfondie des requêtes d'application, du rapport lectures / écritures et de la quantité de mémoire libre dont dispose votre système. Si vous utilisez un index, la requête analysera l'index et non la collection.
Un excellent index est celui qui implique tous les champs scannés par une requête. C'est ce qu'on appelle un index composé.
Pour créer un index unique pour un champ, vous pouvez utiliser ce code :
db.collection.createIndex({“fields”: 1})
Pour un index composé, pour créer l'indexation :
db.collection.createIndex({“filed1”: 1, “field2”: 1})
Outre une interrogation plus rapide grâce à l'utilisation de l'indexation, il existe un avantage supplémentaire d'autres opérations telles que le tri, les échantillons et la limite. Par exemple, si je conçois mon schéma comme {f : 1, m : 1}, je peux effectuer une opération supplémentaire en plus de rechercher comme
db.collection.find( {f: 1} ).sort( {m: 1} )
La lecture de données depuis la RAM est plus efficace que la lecture des mêmes données depuis le disque. Pour cette raison, il est toujours conseillé de s'assurer que votre index tient entièrement dans la RAM. Pour obtenir l'indexSize actuel de votre collection, exécutez la commande :
db.collection.totalIndexSize()
Vous obtiendrez une valeur comme 36864 octets. Cette valeur ne doit pas non plus représenter un pourcentage important de la taille globale de la RAM, car vous devez répondre aux besoins de l'ensemble de travail du serveur.
Une requête efficace doit également améliorer la sélectivité. La sélectivité peut être définie comme la capacité d'une requête à affiner le résultat à l'aide de l'index. Pour être plus sécant, vos requêtes doivent limiter le nombre de documents possibles avec le champ indexé. La sélectivité est principalement associée à un indice composé qui comprend un champ de faible sélectivité et un autre champ. Par exemple, si vous disposez de ces données :
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 7, b: "cd", c: 58 }
{ _id: ObjectId(), a: 8, b: "kt", c: 33 }
La requête {a :7, b :"cd"} parcourra 2 documents pour renvoyer 1 document correspondant. Cependant, si les données pour la valeur a sont uniformément réparties, c'est-à-dire
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 8, b: "cd", c: 58 }
{ _id: ObjectId(), a: 9, b: "kt", c: 33 }
La requête {a :7, b :"cd"} parcourra 1 document et renverra ce document. Par conséquent, cela prendra moins de temps que la première structure de données.
ClusterControlConsole unique pour l'ensemble de votre infrastructure de base de donnéesDécouvrez les autres nouveautés de ClusterControlInstallez ClusterControl GRATUITEMENTProvisionnement des ressources
Une mémoire de stockage, une RAM et d'autres paramètres de fonctionnement inadéquats peuvent dégrader considérablement les performances d'une MongoDB. Par exemple, si le nombre de connexions utilisateur est très important, cela empêchera l'application serveur de traiter les demandes en temps opportun. Comme indiqué dans Éléments clés à surveiller dans MongoDB, vous pouvez obtenir un aperçu des ressources limitées dont vous disposez et comment vous pouvez les adapter à vos spécifications. Pour un grand nombre de demandes d'application simultanées, le système de base de données sera débordé pour répondre à la demande.
Délai de réplication
Parfois, vous pouvez remarquer que certaines données manquent dans votre base de données ou lorsque vous supprimez quelque chose, elles réapparaissent. Même si vous pouvez avoir un schéma bien conçu, une indexation appropriée et suffisamment de ressources, au début, votre application fonctionnera sans problème, mais à un moment donné, vous remarquerez les problèmes mentionnés par ce dernier. MongoDB s'appuie sur le concept de réplication dans lequel les données sont copiées de manière redondante pour répondre à certains critères de conception. Une hypothèse avec ceci est que le processus est instantané. Cependant, un certain retard peut survenir, peut-être en raison d'une panne de réseau ou d'erreurs non gérées. En un mot, il y aura un grand écart entre le temps avec lequel une opération est traitée sur le nœud primaire et le temps où elle sera appliquée sur le nœud secondaire.
Revers avec retards de réplique
-
Données incohérentes. Ceci est particulièrement associé aux opérations de lecture qui sont réparties sur les secondaires.
-
Si l'écart de décalage est suffisamment large, de nombreuses données non répliquées peuvent se trouver sur le nœud principal et devront être réconciliées dans le nœud secondaire. À un moment donné, cela peut être impossible, en particulier lorsque le nœud principal ne peut pas être récupéré.
-
L'échec de la récupération du nœud principal peut forcer l'utilisateur à exécuter un nœud avec des données qui ne sont pas à jour et, par conséquent, peut supprimer toute la base de données afin de permettre au nœud principal de récupérer.
Causes de la défaillance du nœud secondaire
-
Puissance primaire supérieure au secondaire en ce qui concerne les spécifications du processeur, des IOPS du disque et des E/S réseau.
-
Opérations d'écriture complexes. Par exemple une commande comme
db.collection.update( { a: 7} , {$set: {m: 4} }, {multi: true} )
Le nœud principal enregistrera cette opération dans l'oplog assez rapidement. Cependant, pour le nœud secondaire, il doit récupérer ces opérations, lire dans la RAM tous les index et pages de données afin de répondre à certaines spécifications de critères telles que l'identifiant. Puisqu'il doit le faire assez rapidement pour maintenir le débit avec le nœud principal qui effectue l'opération, si le nombre d'opérations est suffisamment important, il y aura un décalage attendu.
-
Verrouillage du secondaire lors d'une sauvegarde. Dans ce cas, nous pouvons oublier de désactiver le primaire et continuerons donc ses opérations normalement. Au moment où le verrou sera relâché, le décalage de réplication aura un écart important, en particulier lorsqu'il s'agit d'une énorme quantité de sauvegarde de données.
-
Construction de l'index. Si un index se crée dans le nœud secondaire, toutes les autres opérations qui lui sont associées sont bloquées. Si l'index est de longue durée, le problème de décalage de réplication sera rencontré.
-
Secondaire non connecté. Parfois, le nœud secondaire peut échouer en raison de déconnexions du réseau, ce qui entraîne un décalage de réplication lorsqu'il est reconnecté.
Comment minimiser le décalage de réplication
-
Utilisez des index uniques en plus de votre collection ayant le champ _id. Ceci afin d'éviter que le processus de réplication échoue complètement.
-
Envisagez d'autres types de sauvegarde tels que des instantanés ponctuels et de système de fichiers qui ne nécessitent pas nécessairement de verrouillage.
-
Évitez de créer des index volumineux car ils provoquent une opération de blocage en arrière-plan.
-
Rendre le secondaire suffisamment puissant. Si l'opération d'écriture est légère, l'utilisation de secondaires sous-alimentés sera économique. Mais, pour les charges d'écriture importantes, le nœud secondaire peut être en retard sur le nœud principal. Pour être plus sécant, le secondaire doit avoir suffisamment de bande passante pour aider à lire les oplogs assez rapidement afin de maintenir son débit avec le nœud principal.
Techniques de requête efficaces
Outre la création de requêtes indexées et l'utilisation de la sélectivité des requêtes comme indiqué ci-dessus, il existe d'autres concepts que vous pouvez utiliser pour accélérer et rendre vos requêtes efficaces.
Optimiser vos requêtes
-
Utilisation d'une requête couverte. Une requête couverte est une requête qui est toujours complètement satisfaite par un index et qui n'a donc pas besoin d'examiner un document. La requête couverte doit donc avoir tous les champs dans le cadre de l'index et par conséquent le résultat doit contenir tous ces champs.
Prenons cet exemple :
{_id: 1, product: { price: 50 }
Si nous créons un index pour cette collection comme
{“product.price”: 1}
En considérant une opération de recherche, alors cet index couvrira cette requête ;
db.collection.find( {“product.price”: 50}, {“product.price”: 1, _id: 0} )
et renvoyez uniquement le champ et la valeur product.price.
-
Pour les documents intégrés, utilisez la notation par points (.). La notation par points facilite l'accès aux éléments d'un tableau et aux champs du document intégré.
Accéder à un tableau :
{ prices: [12, 40, 100, 50, 40] }
Pour spécifier le quatrième élément par exemple, vous pouvez écrire cette commande :
“prices.3”
Accéder à un tableau d'objet :
{ vehicles: [{name: toyota, quantity: 50}, {name: bmw, quantity: 100}, {name: subaru, quantity: 300} }
Pour spécifier le champ de nom dans le tableau des véhicules, vous pouvez utiliser cette commande
“vehicles.name”
-
Vérifiez si une requête est couverte. Pour ce faire, utilisez db.collection.explain(). Cette fonction fournira des informations sur l'exécution d'autres opérations, par ex. db.collection.explain().aggregate(). Pour en savoir plus sur la fonction d'explication, vous pouvez consulter expliquer().
En général, la technique suprême en matière d'interrogation consiste à utiliser des index. Interroger uniquement un index est beaucoup plus rapide que d'interroger des documents en dehors de l'index. Ils peuvent tenir en mémoire et donc être disponibles en RAM plutôt qu'en disque. Cela rend le facile et assez rapide pour les récupérer de la mémoire.