Cours
Comme pour toute base de données, MongoDB incluse, les index sont essentiels au processus de développement. Sans eux, à mesure que votre base grandit, les performances chutent. Avec des index pertinents, la croissance des données s’accompagne d’un accès qui reste optimal.
Dans cet article, nous allons passer en revue les différents types d’index disponibles dans MongoDB, partager quelques conseils pour en tirer le meilleur parti, et expliquer comment vérifier que les index que vous créez sont réellement utilisés. Si vous débutez avec MongoDB, nous vous recommandons le cours Introduction to MongoDB.
Où les index MongoDB sont-ils cruciaux et pourquoi ?
Quand on parle d’index, on parle de performance. Les index font la différence entre parcourir chaque document d’une collection pour vérifier s’il correspond à la requête, et ne lire qu’un sous-ensemble ciblé de documents correspondants. Si la collection est petite, les performances seront correctes dans les deux cas. Mais avec 100 000 documents dont 100 seulement correspondent à la requête, sans index adapté il faudra du temps pour balayer 100 000 documents afin d’en extraire 100.
MongoDB propose de nombreux types d’index :
- Champ unique : un index sur un champ précis du document. L’index par défaut sur le champ
_idest un index à champ unique. - Composé : un index basé sur plusieurs champs d’un document, où l’ordre des champs utilisés dans la requête et dans l’index est important.
- Multiclés : un index utilisé sur des champs de type tableau.
- Générique (wildcard) : un index pour des champs dont le nom peut varier légèrement d’un document à l’autre. Ses performances sont inférieures à celles d’autres index.
- Géospatial : un index qui accélère les requêtes géospatiales utilisant des coordonnées de latitude et longitude.
- Haché : un index particulièrement adapté aux clés de partition (shard keys).
Pour en savoir plus sur les différents types d’index, consultez la documentation MongoDB.
La plupart du temps, vous utiliserez des index à champ unique, composés ou multiclés, mais au final, tout dépend de l’application, des requêtes et des données.
Les index sont importants pour toute interaction qui récupère des documents. Cela ne se limite pas aux opérations find ou findOne. Lors des mises à jour et suppressions, vous utilisez également un critère de correspondance qui sert à localiser les documents avant d’appliquer l’opération.
Les opérateurs find et findOne ne sont pas les seuls à bénéficier d’un index. Si vous utilisez l’opérateur $match en première étape d’un pipeline d’agrégation, un index accélère considérablement l’exécution.
Indexation MongoDB : conseils et astuces pour réussir
Créer des index au hasard peut être aussi inefficace que de ne pas en avoir. Voici quelques points à considérer pour bâtir une application bien plus efficiente.
Égalité, tri, intervalle (ESR) pour les index composés
La règle d’or pour un index composé consiste à déterminer l’usage de chaque champ dans la requête et à respecter la bonne pratique ESR.
Que signifie ESR ? Décomposons :
- Égalité : des champs comparés à l’identique, pas de manière approximative. Par exemple, une correspondance exacte sur le nom d’une personne.
- Tri : des champs utilisés pour trier en ordre croissant ou décroissant.
- Intervalle : des champs filtrés par une plage de valeurs, et non par égalité. Par exemple, avec les opérateurs
$ltou$gt, on récupère des documents dans un intervalle, pas une valeur précise.
Pour mieux visualiser, regardez la requête suivante :
db.employee.find(
{
"department": "Developer Relations",
"salary": {
"$lt": 80000
}
}
).sort(
{
"name": 1
}
);
Cette requête applique une égalité sur department, un intervalle sur salary, puis trie par name.
En suivant ESR, l’index ressemblerait à ceci :
db.employee.createIndex({
"department": 1,
"name": 1,
"salary": 1
});
On respecte ESR pour éviter les tris en mémoire et réduire l’espace de recherche, afin d’exécuter la requête plus efficacement.
Requêtes couvertes
L’indexation réduit déjà le volume de documents à analyser et améliore les performances. Pour aller plus loin, explorez les requêtes couvertes. Avec une requête couverte, tous les résultats proviennent directement de l’index, sans lecture des documents de la collection.
Quelques conditions peuvent toutefois limiter leur usage :
- Les critères de filtrage et de tri doivent correspondre exactement à la définition de l’index.
- Les champs projetés dans le résultat doivent aussi être inclus dans l’index.
- En sharding, la clé de partition doit faire partie de l’index.
Concrètement, qu’est-ce que cela signifie ?
Prenons l’index suivant :
db.department.createIndex({
"country": 1,
"salary": 1
});
Ici, nous indexons les documents par country puis salary. Nous pouvons ensuite exécuter cette requête :
db.department.find(
{
"country": "US"
},
{
"country": 1,
"salary": 1
}
);
Cette requête, avec projection, utilisera l’index, mais ne sera pas une requête couverte. Le champ _id est renvoyé par défaut dans MongoDB, et il n’est pas présent dans notre index. Il faut donc soit modifier l’index, soit ajuster la requête. En modifiant la requête :
db.department.find(
{
"country": "US"
},
{
"country": 1,
"salary": 1,
"_id": 0
}
);
Retenez que pour une requête couverte, l’index doit être valide et tous les champs demandés doivent y figurer. Sinon, vous ne faites qu’identifier des documents via l’index, sans tirer les résultats directement de l’index.
Vous pouvez vérifier si une requête est couverte en utilisant l’argument executionStats avec l’opérateur explain mentionné plus bas. Le champ totalDocsExamined vous l’indiquera : s’il vaut zéro, la requête est couverte.
Réduire ou éliminer les index superflus
Les index ont un coût en ressources serveur. Chacun doit être maintenu, ce qui consomme CPU, RAM et disque. Plus vous créez d’index, plus vous ajoutez de surcharge, pouvant entraîner des coûts plus élevés ou des performances dégradées si le serveur est sous-dimensionné. Cette dégradation touche aussi les écritures et suppressions, pas seulement les lectures, car MongoDB mobilise davantage de ressources.
Il est pertinent de créer des index uniquement pour les requêtes fréquentes. Une requête exécutée une fois par mois a-t-elle vraiment besoin d’un index ? Probablement pas. Celle qui s’exécute toutes les 10 secondes ? Absolument !
Découvrez d’autres bonnes pratiques d’indexation sur le blog MongoDB.
Votre index MongoDB fonctionne-t-il vraiment ?
Ce n’est pas parce que vous avez créé un index que votre requête l’utilise. Avec MongoDB, vous pouvez diagnostiquer quel index est utilisé pour une requête donnée.
Prenons l’index suivant :
db.people.createIndex({
"name": 1,
"is_active": 1
});
Ici, nous indexons les champs name et is_active. Exécutons maintenant la requête suivante :
db.people.find({
"is_active": true
});
Cette requête s’exécutera avec ou sans index, et sera rapide si le jeu de données est petit. Pour vérifier si elle utilise notre index, modifions-la ainsi :
db.people.find({
"is_active": true
}).explain();
Cette fois, nous appelons explain pour obtenir le plan d’exécution. Dans ce plan, on peut voir :
winningPlan: {
isCached: false,
stage: 'COLLSCAN',
filter: { is_active: { '$eq': true } },
direction: 'forward'
}
Pour cette requête, MongoDB a effectué un « COLLSCAN », ce qui signifie qu’aucun index n’a été utilisé, même si un des champs indexés figurait dans la requête. En la changeant ainsi :
db.people.find({
"name": "Nic Raboy",
"is_active": true
}).explain();
Le résultat d’explain change :
winningPlan: {
isCached: false,
stage: 'FETCH',
inputStage: {
stage: 'IXSCAN',
keyPattern: { name: 1, is_active: 1 },
indexName: 'name_1_is_active_1',
isMultiKey: false,
multiKeyPaths: { name: [], is_active: [] },
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: 'forward',
indexBounds: {
name: [ '["Nic Raboy", "Nic Raboy"]' ],
is_active: [ '[true, true]' ]
}
}
},
Ici, le plan indique « IXSCAN » et précise le nom de l’index utilisé, name_1_is_active_1, nommé automatiquement à la création.
Pour une opération update, le principe est similaire, à ceci près que explain se place ailleurs.
Une opération update pourrait ressembler à ceci :
db.people.explain().update(
{
“name”: “Nic Raboy”,
“Is_active”: true
},
{
“$set”: { “is_active”: false }
}
);
Remarquez que dans cet exemple, explain précède la fonction update.
Si l’index attendu est bien utilisé lors de la mise à jour, vous verrez « IXSCAN » ainsi que le nom de l’index. Plus d’informations sur les opérations d’update et l’usage des index.
Autre méthode pour consulter le plan d’exécution : utiliser runCommand sur la base. Moins lisible, mais très puissant :
db.runCommand({
"explain": {
"update": "people",
"updates": [
{
"q": {
"name": "Nic Raboy",
"is_active": true
},
"u": {
"$set": { "is_active": false }
}
}
]
},
"verbosity": "allPlansExecution"
});
Ce qui précède fournit les mêmes informations que l’usage d’explain avec update.
Si vous n’êtes pas à l’aise avec la création d’index, ou si vos requêtes sont plus lentes que prévu, examinez les détails d’exécution.
Conclusion
Vous venez de voir quelques bonnes pratiques d’indexation avec MongoDB. Sans couvrir tous les cas possibles, ces repères constituent une base solide pour offrir à vos utilisateurs la meilleure expérience possible avec vos données.
En résumé :
- Appliquez la règle égalité, tri, intervalle (ESR) autant que possible.
- Lorsque c’est possible, visez une requête couverte.
- Ne multipliez pas les index inutilement : identifiez les requêtes les plus utilisées et le volume de données qu’elles traitent.
- Appuyez-vous sur l’opérateur
explainpour tester vos index et leurs performances.
Cette liste n’est pas exhaustive. D’autres facteurs entrent en jeu, comme les niveaux de cardinalité.
Pour aller plus loin, consultez la documentation MongoDB. Si vous utilisez une offre payante sur MongoDB Atlas, pensez aux outils Performance Advisor intégrés pour recommander index et requêtes.
FAQ sur l’indexation MongoDB
Combien d’index dois-je créer dans une collection MongoDB ?
Ça dépend. Trouvez le bon équilibre pour votre usage : privilégiez l’indexation des requêtes exécutées fréquemment plutôt que ponctuellement.
J’ai créé quelques index, mais mes requêtes restent lentes. Pourquoi ?
Êtes-vous certain que vos requêtes utilisent réellement les index créés ? Utilisez l’opérateur explain ou les outils de recommandation de MongoDB Atlas pour diagnostiquer.
Pour la règle ESR, l’ordre des champs dans l’index doit-il être exactement égalité, puis tri, puis intervalle ?
MongoDB peut parfois utiliser un index même si l’ordre diffère, mais l’efficacité est maximale lorsque l’ordre des clés de l’index suit la séquence de la requête : d’abord les filtres par égalité, puis le tri, et enfin l’intervalle.
Quels sont les différents modes d’« explain » ?
Par défaut, explain() s’exécute en mode « queryPlanner ». Vous pouvez aussi utiliser les modes « executionStats » et « allPlansExecution ».

Nic Raboy est responsable des relations avec les développeurs chez MongoDB, où il dirige une équipe de développeurs Python, Java, C# et PHP qui créent un contenu génial pour aider les développeurs à réussir à inclure MongoDB dans leurs projets. Il a de l'expérience avec Golang et JavaScript et écrit souvent sur ses aventures de développement.
