J'ai plus de 300 000 disques dans une collection à Mongo.
Quand j'exécute cette requête très simple:
db.myCollection.find().limit(5);
Cela ne prend que quelques millisecondes.
Mais quand j'utilise skip dans la requête:
db.myCollection.find().skip(200000).limit(5)
Il ne retournera rien… Il dure quelques minutes et ne renvoie rien.
Comment le rendre meilleur?
De MongoDB documentation :
Coûts de téléappel
Malheureusement, sauter peut être (très) coûteux et obliger le serveur à marcher dès le début de la collecte, ou index, pour atteindre la position offset/ignorer avant de pouvoir retourner la page de données (limite). Au fur et à mesure que le numéro de page augmente, le saut devient de plus en plus lent et de plus en plus gourmand en ressources processeur, et peut-être même IO, avec de plus grandes collections.
La pagination basée sur une plage offre une meilleure utilisation des index mais ne vous permet pas de passer facilement à une page spécifique.
Vous devez vous poser une question: combien de fois avez-vous besoin de 40000ème page? Voir aussi this article;
Une approche de ce problème, si vous avez de grandes quantités de documents et que vous les affichez dans triés ordre (je ne sais pas à quel point skip
est utile si vous ne l'êtes pas) serait d'utiliser la clé trier pour sélectionner la page suivante de résultats.
Donc si vous commencez par
db.myCollection.find().limit(100).sort({created_date:true});
puis extrayez la date de création du last document renvoyé par le curseur dans une variable max_created_date_from_last_result
, vous pouvez obtenir la page suivante avec la requête beaucoup plus efficace (en supposant que vous avez un index sur created_date
)
db.myCollection.find({created_date : { $gt : max_created_date_from_last_result } }).limit(100).sort({created_date:true});
J'ai trouvé très performant de combiner les deux concepts (un skip + limit et un find + limit). Le problème avec skip + limit est une mauvaise performance lorsque vous avez beaucoup de documents (en particulier des documents plus volumineux). Le problème avec find + limit est que vous ne pouvez pas accéder à une page arbitraire. Je veux pouvoir paginer sans le faire de manière séquentielle.
Les démarches que je fais sont:
Cela ressemble à peu près à ceci si je veux obtenir la page 5432 de 16 enregistrements (en javascript):
let page = 5432;
let page_size = 16;
let skip_size = page * page_size;
let retval = await db.collection(...).find().sort({ "_id": 1 }).project({ "_id": 1 }).skip(skip_size).limit(1).toArray();
let start_id = retval[0].id;
retval = await db.collection(...).find({ "_id": { "$gte": new mongo.ObjectID(start_id) } }).sort({ "_id": 1 }).project(...).limit(page_size).toArray();
Cela fonctionne car un saut sur un index projeté est très rapide, même si vous sautez des millions d'enregistrements (ce que je fais). si vous exécutez explain("executionStats")
, il a toujours un grand nombre pour totalDocsExamined
mais, en raison de la projection sur un index, il est extrêmement rapide (essentiellement, les blobs de données ne sont jamais examinés). Ensuite, avec la valeur pour le début de la page en main, vous pouvez récupérer très rapidement la page suivante.