Dans le fichier journal de production mongod, nous pouvons voir beaucoup de requêtes lentes. La plupart d'entre eux n'utilisent pas le meilleur indice et par conséquent le meilleur plan d'exécution n'est pas utilisé. Cependant, lorsque j'exécute moi-même les mêmes requêtes dans mongo Shell, l'index correct est utilisé. Alors pourquoi pour la même requête, nous n'avons pas le même plan d'exécution?
Version Mongodb: 4.0.2 (autonome)
Extrait du fichier journal mongod:
2018-12-28T13:55:28.282+0100 I COMMAND [conn1032115] command mydb.products command: find { find: "products", filter: { origins_tags: "italia" }, sort: { last_modified_t: -1 }, skip: 320, limit: 20, singleBatch: false, maxTimeMS: 0, tailable: false, noCursorTimeout: false, awaitData: false, allowPartialResults: false, $readPreference: { mode: "secondaryPreferred" }, $db: "mydb" } planSummary: IXSCAN { last_modified_t: -1 } keysExamined:721542 docsExamined:721542 cursorExhausted:1 numYields:5637 nreturned:3 reslen:23886 locks:{ Global: { acquireCount: { r: 5638 } }, Database: { acquireCount: { r: 5638 } }, Collection: { acquireCount: { r: 5638 } } } protocol:op_query 5166ms
Nous pouvons voir les informations suivantes:
Cependant, ces index existent dans la collection:
db.products.createIndex({"origins_tags": 1,"sortkey": -1}, { background: true })
db.products.createIndex({"last_modified_t": -1}, { background: true })
Voici le plan d'exécution optimisé (celui attendu):
Nous pouvons donc voir une énorme différence!
ces index existent dans la collection:
db.products.createIndex({"origins_tags": 1,"sortkey": -1}, { background: true }) db.products.createIndex({"last_modified_t": -1}, { background: true })
L'optimiseur de requêtes MongoDB choisit le plan de requête le plus efficace en fonction des index forme de la requête (combinaison de prédicat, tri et projection) et des index candidats. S'il existe plusieurs plans candidats pour une forme de requête donnée, une évaluation d'essai sera exécutée pour déterminer quel index renvoie le lot initial de résultats (101 documents) avec le moins de "travail" mesuré. Le plan gagnant sera mis en cache et réévalué périodiquement si les performances des requêtes ou d'autres circonstances changent (par exemple, l'ajout ou la suppression d'index).
Étant donné qu'aucun des index n'est idéal pour votre requête, la sélection d'index peut varier en fonction de l'index qui renvoie le lot initial de résultats plus rapidement lors de l'évaluation du plan:
origins_tags
Comme critère de filtrage, mais il nécessite une extraction de document et un tri en mémoire (une étape de requête bloquante avec limite de 32 Mo ) afin de renvoyer les résultats commandé par last_modified
. Le travail requis pour utiliser cet index pour la requête donnée dépendra du nombre total de documents correspondants: chaque document correspondant doit être récupéré pour le tri. Le pire des cas serait une requête qui doit effectuer un tri en mémoire de plus de 32 Mo de données et entraîner une exception.origins_tags
. Le travail requis pour utiliser cet index dépend de la rapidité avec laquelle les correspondances sont trouvées: cet index peut diffuser des correspondances à mesure qu'elles sont trouvées et s'arrêter dès qu'il y a suffisamment de correspondances. Le pire des cas serait une analyse complète de la collection pour confirmer qu'il n'y a pas d'autres documents correspondants.Si l'évaluation des deux plans aboutit à une égalité (les deux semblent effectuer le même travail pour renvoyer les résultats initiaux pour une forme de requête donnée), le plan qui ne nécessite pas de tri en mémoire l'emportera.
Vous pouvez voir les détails de l'évaluation du plan en expliquant une requête avec le mode allPlansExecution
: db.products.find({...}).explain('allPlansExecution')
.
keysExamined: 721542 docsExamined: 721542 (en gros, toute la collection est examinée)
C'est le pire des cas pour le deuxième index: il y a 323 correspondances sur 721 542 documents et vous avez demandé les résultats 320-340 (via skip: 20
, limit: 20
).
Je ne comprends pas comment le deuxième index peut donner des résultats plus rapides lors de l'évaluation du plan
L'évaluation du plan ne prend en compte que le plan candidat qui renvoie le lot initial de résultats (101 documents) avec moins d'effort global. Le planificateur de requêtes n'exécute pas les plans jusqu'à leur achèvement pendant l'évaluation ou ne conserve aucune métrique sur la distribution des valeurs clés. Le plan de requête mis en cache aurait été basé sur une comparaison où le deuxième index n'avait pas à effectuer l'analyse de collecte.
Lorsque mongo choisit un mauvais plan, il devrait pouvoir voir que le temps d'exécution n'est pas bon et il ne devrait pas choisir le même plan pour les prochaines requêtes.
Les plans mis en cache sont réévalués si les performances diminuent, conformément au diagramme Query Plans dans la documentation MongoDB. Cependant, si vous avez plusieurs plans candidats sans gagnant déterministe, il est possible de choisir un plan de requête plus rapide pour les résultats initiaux (ou la même forme de requête avec des valeurs différentes), mais sous-optimal pour les résultats ultérieurs.
Pour résoudre ce problème, vous devez ajouter un index composé prenant en charge vos critères de filtrage et de tri :
db.products.createIndex({"origins_tags": 1,"last_modified_t": -1}, { background: true })
Alternativement (et moins idéalement), vous pourriez fournir un indice pour forcer la requête à utiliser le premier index. Notez que l'indication ignorera tous les futurs index ajoutés qui pourraient être plus idéaux et échouera également avec une exception si le tri en mémoire doit fonctionner avec plus de 32 Mo de données.
L'ajout de l'indice composé suggéré entraînerait le rapport le plus efficace entre les clés et les documents examinés par rapport au nombre de résultats renvoyés.