89 votes

Pagination lente sur des tonnes d'enregistrements dans mongodb

J'ai plus de 300 000 enregistrements dans une collection dans Mongo.

Quand je lance cette requête très simple :

db.myCollection.find().limit(5);

Cela ne prend que quelques milisecondes.

Mais quand j'utilise "skip" dans la requête :

db.myCollection.find().skip(200000).limit(5)

Il ne renvoie rien... il tourne pendant des minutes et ne renvoie rien.

Comment l'améliorer ?

109voto

Russell Points 5472

Une approche de ce problème, si vous avez de grandes quantités de documents et que vous les affichez en trié (je ne suis pas sûr de l'utilité de l'ordre skip est si vous ne l'êtes pas) serait d'utiliser la clé sur laquelle vous effectuez le tri pour sélectionner la page suivante de résultats.

Donc si vous commencez par

db.myCollection.find().limit(100).sort({created_date:true});

et ensuite extraire la date de création de la dernier document retourné par le curseur dans une variable max_created_date_from_last_result vous pouvez obtenir la page suivante avec la méthode bien plus efficace (en supposant que vous ayez un index sur created_date ) interrogation

db.myCollection.find({created_date : { $gt : max_created_date_from_last_result } }).limit(100).sort({created_date:true});

91voto

Tomasz Nurkiewicz Points 140462

De MongoDB documentation :

Coûts de la radiomessagerie

Malheureusement, le saut peut s'avérer (très) coûteux et nécessite que le serveur se déplace depuis le début de la collection, ou index, pour atteindre la position de décalage/saut avant de pouvoir commencer à renvoyer la page de données (limite). Au fur et à mesure que le nombre de pages augmente, le saut deviendra plus lent et plus intensif en cpu, et éventuellement lié aux entrées-sorties, avec des collections plus importantes.

La pagination basée sur les plages permet une meilleure utilisation des index mais ne permet pas de passer facilement à une page spécifique.

Vous devez vous poser une question : à quelle fréquence avez-vous besoin de la 40000e page ? Voir aussi este article ;

12voto

Mr. T Points 4405

J'ai trouvé plus performant de combiner les deux concepts ensemble (à la fois un skip+limit et un find+limit). Le problème avec skip+limit est la mauvaise performance lorsque vous avez beaucoup de documents (en particulier les plus grands documents). Le problème avec find+limit est que vous ne pouvez pas sauter à une page arbitraire. Je veux être capable de paginer sans le faire séquentiellement.

Les mesures que je prends sont :

  1. Créez un index basé sur la façon dont vous voulez trier vos documents, ou utilisez simplement l'index _id par défaut (c'est ce que j'ai utilisé).
  2. Connaître la valeur de départ, la taille de la page et la page à laquelle vous voulez accéder.
  3. Projet + saut + limite la valeur à partir de laquelle vous devriez commencer
  4. Rechercher + limiter les résultats de la page

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 parce qu'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") mais il reste un grand nombre de personnes pour totalDocsExamined mais en raison de la projection sur un index, elle est extrêmement rapide (essentiellement, les blocs de données ne sont jamais examinés). Ensuite, avec la valeur du début de la page en main, vous pouvez aller chercher la page suivante très rapidement.

8voto

Kamil Dąbrowski Points 135

J'ai connecté deux réponses.

Le problème est que lorsque vous utilisez le skip et la limite, sans tri, c'est juste une pagination par ordre de table dans la même séquence que vous écrivez les données dans la table, donc le moteur a besoin de faire un premier index temporaire. il vaut mieux utiliser un index _id prêt :) Vous devez utiliser le tri par _id. C'est très rapide avec les grandes tables comme.

db.myCollection.find().skip(4000000).limit(1).sort({ "_id": 1 });

En PHP, ce sera

$manager = new \MongoDB\Driver\Manager("mongodb://localhost:27017", []);
$options = [
            'sort' => array('_id' => 1),
            'limit' => $limit, 
            'skip' => $skip,

        ];
$where = [];
$query = new \MongoDB\Driver\Query($where, $options );
$get = $manager->executeQuery("namedb.namecollection", $query);

0voto

user3317919 Points 1

Vous pourriez avoir un cas où il y a plusieurs documents (peut-être des centaines ou des milliers) avec la même "created_date". La simple utilisation de l'opérateur $gt peut renvoyer le même ensemble de résultats, encore et encore. Une façon d'éviter ce problème est d'inclure également le "_id" dans la requête (en supposant que vous utilisez le _id par défaut créé par MongoDB).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X