Aller au contenu

MongoDB : index et performances

Comprendre pourquoi une requête est lente et la corriger, depuis mongosh. Les exemples utilisent les collections clients et commandes.

Un index accélère la recherche, au prix d’un peu d’espace et d’écritures plus lentes. Sans index, MongoDB parcourt toute la collection (COLLSCAN).

db.clients.createIndex({ email: 1 }, { unique: true }); // unique : refuse les doublons
db.commandes.createIndex({ client_id: 1 });
db.commandes.createIndex({ statut: 1, passee_le: -1 }); // index composé
db.clients.createIndex(
{ ville: 1 },
{ partialFilterExpression: { ville: { $exists: true } } },
); // index partiel
db.clients.createIndex({ nom: 'text' }); // index de recherche textuelle
db.clients.getIndexes();
db.clients.dropIndex('email_1');
db.clients.totalIndexSize();

1 = ordre croissant, -1 = décroissant. Les index sont nommés par défaut d’après leurs champs (email_1).

Type Usage
Simple, composé Égalité, comparaisons, tri
unique Garantir l’unicité d’une valeur
TTL (expireAfterSeconds) Suppression automatique des documents expirés (sessions, journaux)
Partiel N’indexe que les documents répondant à un filtre : index plus petit
Texte Recherche par mots ($text)
2dsphere Requêtes géographiques
db.sessions.createIndex({ cree_le: 1 }, { expireAfterSeconds: 3600 }); // supprime après 1 heure

Règle d’ordre des champs dans un index composé

Section intitulée « Règle d’ordre des champs dans un index composé »

Placer d’abord les champs filtrés par égalité, puis ceux utilisés pour le tri, puis ceux filtrés par intervalle (règle « ESR » : Equality, Sort, Range). L’index { statut: 1, passee_le: -1 } sert aux filtres sur statut, ou sur statut et passee_le, mais pas sur passee_le seul.

db.commandes.find({ statut: 'payee' }).explain('executionStats');

Éléments à regarder dans le résultat :

Champ Signification
winningPlan.stage COLLSCAN = parcours complet ; IXSCAN = utilisation d’un index (à l’intérieur de FETCH)
totalDocsExamined Documents lus : idéalement proche de nReturned
totalKeysExamined Entrées d’index lues
nReturned Documents renvoyés
executionTimeMillis Durée en millisecondes

Comparer avant et après la création d’un index :

db.commandes.find({ statut: 'payee' }).explain('executionStats').executionStats
.totalDocsExamined;
db.commandes.createIndex({ statut: 1 });
db.commandes.find({ statut: 'payee' }).explain('executionStats').executionStats
.totalDocsExamined;
db.commandes.aggregate([{ $indexStats: {} }]); // utilisation de chaque index (accès, date du dernier usage)

Supprimer les index jamais utilisés : ils ralentissent les écritures sans bénéfice.

db.setProfilingLevel(1, { slowms: 100 }); // enregistre les requêtes de plus de 100 ms
db.getProfilingStatus();
db.system.profile.find().sort({ ts: -1 }).limit(5);
db.system.profile
.find({ millis: { $gt: 200 } })
.sort({ millis: -1 })
.limit(5);
db.setProfilingLevel(0); // désactive

Le profileur écrit dans la collection system.profile de la base : le désactiver une fois l’analyse terminée.

db.currentOp({ active: true, secs_running: { $gt: 5 } }); // opérations actives depuis plus de 5 s
db.killOp(12345); // interrompt l'opération 12345
db.serverStatus().connections;
db.serverStatus().opcounters;
db.stats();
db.commandes.stats().size; // taille des données
db.commandes.stats().storageSize; // espace occupé sur le disque
db.commandes.stats().indexSizes;
db.commandes.countDocuments();
Fenêtre de terminal
mongostat --host 127.0.0.1 5 # activité du serveur toutes les 5 secondes (insertions, requêtes, connexions)
mongotop 5 # temps passé en lecture et écriture par collection

MongoDB utilise par défaut environ la moitié de la mémoire disponible (moins 1 Go) pour son cache. Pour le limiter, dans /etc/mongod.conf :

storage:
wiredTiger:
engineConfig:
cacheSizeGB: 2

Redémarrer ensuite : sudo systemctl restart mongod.

  1. explain("executionStats") : repérer COLLSCAN et un écart important entre totalDocsExamined et nReturned.
  2. Ajouter l’index sur les champs de filtre, de tri et de jointure ($lookup), en respectant la règle ESR.
  3. Placer $match et $sort au début des agrégations, pour utiliser les index.
  4. Limiter les champs renvoyés (projection) et paginer (limit).
  5. Éviter les documents ou tableaux qui grossissent sans fin.
  6. Surveiller avec le profileur et mongostat.