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

Améliorez les performances des requêtes MongoDB grâce à de meilleurs index, projections, évitement de scans, planification de tri et mises à jour ciblées.

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

Les requêtes MongoDB peuvent sembler rapides en développement, puis ralentir considérablement une fois les collections volumineuses. Des requêtes MongoDB très efficaces dépendent de l'alignement de vos index sur les modèles d'accès réels, du retour uniquement des champs utiles et de l'évitement des opérations qui imposent des scans importants.

Ces cinq pratiques vous aident à maintenir des lectures prévisibles et à réduire le travail inutile sur le serveur.

1. Indexer stratégiquement pour soutenir vos requêtes

Le facteur le plus important pour la performance 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 les 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 réduire rapidement l'ensemble des 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 scans non indexés

Si une requête ne peut pas utiliser un index, MongoDB utilise par défaut un Collection Scan (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 le rapport totalKeysExamined vs totalDocsExamined. Un grand écart 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. Tirer parti de la projection pour limiter les champs retournés

Lorsque vous exécutez une requête, MongoDB renvoie par défaut l'intégralité du document correspondant. Dans de nombreuses applications, vous n'avez besoin que de quelques champs (par exemple, afficher une liste de noms). Récupérer des champs volumineux inutiles (comme des tableaux imbriqué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 mémoire du 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 de 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 : Renvoie l'intégralité du document utilisateur
db.users.find({ organizationId: "XYZ" })

// Efficace : Renvoie uniquement le nom et l'email de l'utilisateur
db.users.find(
    { organizationId: "XYZ" },
    { name: 1, email: 1, _id: 0 } // Inclure name et email, exclure _id
)

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

3. Éviter les opérations qui imposent des scans complets de collection

Certaines opérations de requête sont intrinsèquement difficiles ou impossibles à satisfaire pour MongoDB en utilisant des index standard, ce qui entraîne souvent des scans complets coûteux même lorsque des index existent.

Éviter les wildcards en tête dans les expressions régulières

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

  • Généralement compatible avec les index : db.products.find({ sku: /^ABC/ })
  • Généralement coûteux : db.products.find({ sku: /.*CDE$/ })

Astuce : Si vous devez effectuer une recherche dans des valeurs de chaîne, envisagez d'utiliser les Index de Texte de MongoDB pour des capacités de recherche en texte intégral, ou normalisez votre structure de données pour prendre en charge les recherches par préfixe.

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

Comme mentionné précédemment, interroger des champs qui ne sont pas indexés force un scan. Méfiez-vous particulièrement des requêtes complexes impliquant des clauses $where ou l'évaluation de fonctions JavaScript, car celles-ci entraînent presque toujours un scan de chaque document.

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

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

Si MongoDB ne peut pas utiliser un index pour le tri, il peut avoir besoin d'un tri bloquant en mémoire et peut échouer lorsque le tri dépasse la limite de mémoire du serveur pour les opérations de tri bloquant.

Bonne pratique : Utiliser 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 consulter les documents réels — il obtient tout ce dont il a besoin directement à partir de la structure de l'index.

// 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. Privilégier les mises à jour atomiques et les opérations d'écriture

Bien que cet article se concentre sur les performances en lecture, des é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.

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

Lors de la modification d'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 modifier uniquement les champs nécessaires.

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

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

Point clé à retenir

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

  1. Indexer stratégiquement : Assurez-vous que des index existent pour vos filtres de requête et critères de tri courants.
  2. Utiliser la projection : Ne récupérez que les champs dont vous avez absolument besoin.
  3. Éviter les scans : Évitez les wildcards en tête dans les regex et les clauses $where.
  4. Optimiser 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. Privilégier 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 valider 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.