Cinq meilleures pratiques pour écrire des requêtes MongoDB hautement efficaces

Améliorez la vitesse de votre application MongoDB en maîtrisant cinq techniques essentielles d'optimisation des requêtes. Apprenez à utiliser efficacement l'indexation, à minimiser l'analyse des documents grâce à une projection stratégique, à éviter les analyses coûteuses de collections complètes et à optimiser les opérations de tri pour des performances de lecture supérieures dans votre base de données NoSQL.

33 vues

Cinq bonnes pratiques pour écrire des requêtes MongoDB très efficaces

MongoDB, en tant que base de données de documents NoSQL de premier plan, offre une immense flexibilité et évolutivité. Cependant, une croissance incontrôlée et des requêtes mal écrites peuvent rapidement entraîner d'importants goulots d'étranglement de performances, surtout à mesure que les volumes de données augmentent. L'optimisation des performances de lecture est cruciale pour maintenir une application rapide et réactive. Cet article présente cinq pratiques essentielles pour écrire des requêtes MongoDB très efficaces, en se concentrant sur la minimisation des E/S disque, l'utilisation efficace des index et la rationalisation de la récupération des données.

L'adoption de ces pratiques – axées sur la minimisation des documents scannés, la récupération sélective des données et l'évitement des balayages complets de collection – améliorera considérablement la vitesse et l'utilisation des ressources de vos opérations de base de données.

1. Indexez stratégiquement pour supporter vos requêtes

Le facteur le plus important dans les performances des requêtes est la présence et l'utilisation correcte des index. Un index permet au planificateur de requêtes de localiser rapidement les documents correspondants sans avoir à scanner chaque document d'une collection (un "COLLSCAN").

Comment fonctionne l'indexation

MongoDB utilise des index pour satisfaire les prédicats de requête (la partie filter de votre requête). Si une requête utilise des champs qui font partie d'un index, MongoDB peut utiliser cet index pour affiner rapidement l'ensemble de résultats.

Bonne pratique : Analysez toujours vos modèles de requêtes courants. Si vous interrogez ou triez fréquemment sur les champs A, B et C, envisagez de créer un index composé sur { A: 1, B: 1, C: 1 }.

Éviter les balayages non indexés

Si une requête ne peut pas utiliser un index, MongoDB utilise par défaut un balayage de collection (COLLSCAN), qui lit chaque document de la collection. C'est extrêmement lent sur de grands ensembles de données.

Astuce : Utilisez la méthode explain('executionStats') sur votre requête pour vérifier le winningPlan et totalKeysExamined par rapport à totalDocsExamined. Une grande disparité indique souvent une mauvaise utilisation de l'index ou un index manquant.

// Exemple : Vérification des performances d'une requête
db.users.find({ status: "active" }).explain('executionStats')

2. Tirez parti de la projection pour limiter les champs retournés

Lorsque vous exécutez une requête, MongoDB retourne par défaut l'intégralité du document correspondant. Dans de nombreuses applications, vous n'avez besoin que de quelques champs (par exemple, pour afficher une liste de noms). Récupérer des champs volumineux inutiles (comme des tableaux intégrés ou de grands blocs de texte) augmente la latence réseau, l'utilisation de la mémoire sur le serveur de base de données et la consommation de mémoire côté client.

La projection vous permet de spécifier exactement les champs qui doivent être retournés.

Syntaxe de la projection

Utilisez le deuxième argument dans la méthode find() pour spécifier les champs à inclure (1) ou à exclure (0).

  • _id est inclus par défaut, sauf s'il est explicitement exclu (_id: 0).
// Inefficace : Retourne l'intégralité du document utilisateur
db.users.find({ organizationId: "XYZ" })

// Efficace : Ne retourne que le nom et l'e-mail de l'utilisateur
db.users.find(
    { organizationId: "XYZ" },
    { name: 1, email: 1, _id: 0 } // Inclut le nom et l'e-mail, exclut _id
)

Avertissement : La projection fonctionne mieux lorsqu'elle est combinée avec des champs indexés. Si la requête nécessite toujours un balayage complet, la projection des champs ne fait qu'économiser de la bande passante réseau, mais n'améliore pas le temps de recherche initial.

3. Évitez les opérations qui forcent les balayages complets de collection

Certaines opérations de requête sont intrinsèquement difficiles ou impossibles à satisfaire pour MongoDB en utilisant des index standard, conduisant souvent à des balayages complets de collection coûteux, même lorsque des index existent.

Évitez les jokers en début d'expression régulière

Les index sont structurés hiérarchiquement (comme un index de livre organisé alphabétiquement). Une expression régulière qui commence par un joker (.*) ne peut pas utiliser un index car le point de départ du terme de recherche est inconnu.

  • Inefficace (Forçant un balayage) : db.products.find({ sku: /^ABC/ }) (Peut utiliser un index)
  • Très inefficace (Forçant un balayage) : db.products.find({ sku: /.*CDE$/ }) (Ne peut pas utiliser un index efficacement)

Astuce : Si vous devez rechercher à l'intérieur de valeurs de chaîne, envisagez d'utiliser les Index de Texte de MongoDB pour des capacités de recherche plein texte, ou normalisez votre structure de données pour supporter les recherches par préfixe.

Soyez prudent avec les requêtes sur des champs non indexés

Comme mentionné précédemment, interroger des champs non indexés force un balayage. Soyez particulièrement attentif aux requêtes complexes impliquant des clauses $where ou l'évaluation de fonctions JavaScript, car celles-ci entraînent presque toujours un balayage de chaque document.

4. Optimiser les opérations de tri (requêtes couvertes)

Le tri des résultats à l'aide de la méthode .sort() exige de MongoDB soit de récupérer tous les documents correspondants et de les trier en mémoire (si l'ensemble est petit), soit d'utiliser un plan d'exécution trié par index (si un index supporte l'ordre de tri).

Si MongoDB ne peut pas utiliser un index pour le tri, il peut renvoyer une erreur si l'ensemble de résultats est trop grand pour un tri en mémoire (limite de mémoire par défaut de 100 Mo).

Bonne pratique : Utilisez des requêtes couvertes pour le tri

Une requête couverte est une requête où tous les champs impliqués dans le prédicat de requête, la projection et l'opération de tri sont contenus dans un seul index. Lorsqu'une requête est couverte, MongoDB n'a jamais besoin de regarder les documents réels — il obtient tout ce dont il a besoin directement de la structure de l'index elle-même.

// Supposons un index : { category: 1, price: -1 }

// Requête couverte efficace :
db.inventory.find(
    { category: "Electronics" }, // Champ de requête dans l'index
    { price: 1, _id: 0 }          // Champ de projection dans l'index
).sort({ price: -1 })            // Champ de tri dans l'index

5. Préférez les mises à jour atomiques et les opérations d'écriture

Bien que cet article se concentre sur les performances de lecture, les écritures efficaces contribuent de manière significative à la santé globale de la base de données en réduisant le verrouillage et la contention. Les mises à jour doivent être aussi ciblées que possible.

Utilisez des opérateurs de mise à jour au lieu de remplacer des documents entiers

Lorsque vous modifiez un document, utilisez des opérateurs de mise à jour spécifiques comme $set, $inc ou $push plutôt que de lire le document, de le modifier côté client et de réécrire l'intégralité du document.

Inefficace : Lire le document entier -> Modifier dans l'application -> Réécrire le document entier.

Efficace : Utiliser des opérateurs atomiques pour ne modifier que les champs nécessaires.

// Mise à jour efficace : Incrémente atomiquement le compteur sans toucher les autres champs
db.metrics.updateOne(
    { metricName: "login_attempts" },
    { $inc: { count: 1 } }
)

En utilisant des opérateurs atomiques, vous minimisez le risque de conflits d'écriture et réduisez les données transférées sur le réseau.

Résumé et prochaines étapes

L'écriture de requêtes MongoDB très efficaces repose sur la coopération entre la logique de votre application et l'utilisation des index par le moteur de base de données. En adhérant à ces cinq bonnes pratiques, vous pouvez garantir que vos lectures sont rapides, évolutives et économes en ressources :

  1. Indexez stratégiquement : Assurez-vous que des index existent pour vos filtres de requête et critères de tri courants.
  2. Utilisez la projection : Ne récupérez que les champs dont vous avez absolument besoin.
  3. Évitez les balayages : Éloignez-vous des jokers en début d'expression régulière et des clauses $where.
  4. Optimisez le tri : Visez des requêtes couvertes où l'index contient tous les champs nécessaires pour la requête, la projection et le tri.
  5. Préférez les écritures atomiques : Utilisez des opérateurs comme $set pour minimiser la surcharge lors des mises à jour.

Examinez régulièrement vos journaux de requêtes lentes et utilisez explain() pour vérifier que vos requêtes utilisent les index que vous avez créés. L'optimisation des performances est un processus continu, mais ces pratiques constituent une base solide pour un déploiement MongoDB très performant.