web-dev-qa-db-fra.com

MongoDB à longue pagination

On dit que l'utilisation de skip () pour la pagination dans la collection MongoDB avec de nombreux enregistrements est lente et n'est pas recommandée.

La pagination à distance (basée sur> _id comparsion) pourrait être utilisée 

db.items.find({_id: {$gt: ObjectId('4f4a3ba2751e88780b000000')}});

C'est bon pour afficher prev. Touches & next - mais ce n'est pas très facile à mettre en œuvre lorsque vous souhaitez afficher les numéros de page réels 1 ... 5 6 7 ... 124 - vous devez pré-calculer à partir de quel "_id" chaque page commence.

J'ai donc deux questions:

1) Quand devrais-je commencer à m'inquiéter à ce sujet? Quand il y a "trop ​​d'enregistrements" avec un ralentissement notable pour skip ()? 1 000? 1 000 000?

2) Quelle est la meilleure approche pour afficher des liens avec les numéros de page réels lors de l’utilisation de la pagination à distance?

65
Roman

Bonne question!

"Combien c'est trop?" - Cela dépend bien sûr de la taille de vos données et de vos exigences de performances. Personnellement, je me sens mal à l'aise lorsque je saute plus de 500 à 1 000 enregistrements.

La réponse dépend de vos besoins. Voici ce que font les sites modernes (ou du moins certains d'entre eux).

Tout d'abord, navbar ressemble à ceci:

1 2 3 ... 457

Ils obtiennent le numéro de page final à partir du nombre total d'enregistrements et de la taille de la page. Passons à la page 3. Cela impliquera de sauter du premier enregistrement. Lorsque les résultats arrivent, vous connaissez l’identifiant du premier enregistrement à la page 3.

1 2 3 4 5 ... 457

Sautons un peu plus et allons à la page 5.

1 ... 3 4 5 6 7 ... 457

Vous avez eu l'idée. À chaque point, vous voyez les première, dernière et dernière pages, ainsi que deux pages en avant et en arrière par rapport à la page en cours.

Des requêtes

var current_id; // id of first record on current page.

// go to page current+N
db.collection.find({_id: {$gte: current_id}}).
              skip(N * page_size).
              limit(page_size).
              sort({_id: 1});

// go to page current-N
// note that due to the nature of skipping back,
// this query will get you records in reverse order 
// (last records on the page being first in the resultset)
// You should reverse them in the app.
db.collection.find({_id: {$lt: current_id}}).
              skip((N-1)*page_size).
              limit(page_size).
              sort({_id: -1});
95
Sergio Tulentsev

Il est difficile de donner une réponse générale, car cela dépend beaucoup de la requête (ou des requêtes) utilisée (s) pour construire l'ensemble des résultats affichés. Si les résultats peuvent être trouvés en utilisant uniquement l'index et sont présentés dans l'ordre de l'index, db.dataset.find () .lim (). Skip () peut fonctionner même avec un grand nombre de sauts. C’est probablement l’approche la plus simple pour coder. Mais même dans ce cas, si vous pouvez mettre en cache les numéros de page et les associer à des valeurs d'index, vous pouvez accélérer la procédure pour les deuxième et troisième personnes souhaitant afficher la page 71, par exemple.

Dans un jeu de données très dynamique où des documents seront ajoutés et supprimés pendant qu'une autre personne paginera dans les données, cette mise en cache deviendra rapidement obsolète et la méthode limit et saut sera peut-être la seule assez fiable pour donner de bons résultats.

6
Tad Marshall

J'ai récemment rencontré le même problème lorsque j'essayais de paginer une demande en utilisant un champ qui n'était pas unique, par exemple "Prénom". L'idée de cette requête est de pouvoir implémenter la pagination sur un champ non unique sans utiliser skip ()

Le principal problème ici est de pouvoir interroger un champ qui n'est pas unique "Prénom" car les événements suivants se produiront:

  1. $ gt: {"Prénom": "Carlos"} -> Ceci ignorera tous les enregistrements dont le prénom est "Carlos"
  2. $ gte: {"Prénom": "Carlos"} -> renverra toujours le même ensemble de données

Par conséquent, la solution que j'ai proposée consistait à rendre unique la partie $ match de la requête en combinant le champ de recherche ciblé avec un champ secondaire afin d'en faire une recherche unique.

Ordre croissant:

db.customers.aggregate([
    {$match: { $or: [ {$and: [{'FirstName': 'Carlos'}, {'_id': {$gt: ObjectId("some-object-id")}}]}, {'FirstName': {$gt: 'Carlos'}}]}},
    {$sort: {'FirstName': 1, '_id': 1}},
    {$limit: 10}
    ])

Ordre décroissant:

db.customers.aggregate([
    {$match: { $or: [ {$and: [{'FirstName': 'Carlos'}, {'_id': {$gt: ObjectId("some-object-id")}}]}, {'FirstName': {$lt: 'Carlos'}}]}},
    {$sort: {'FirstName': -1, '_id': 1}},
    {$limit: 10}
    ])

La partie $ match de cette requête se comporte fondamentalement comme une instruction if: Si firstName est "Carlos", il doit également être supérieur à cet ID Si firstName n'est pas égal à "Carlos", il doit être plus grand que "Carlos"

Le seul problème est que vous ne pouvez pas naviguer vers un numéro de page spécifique (cela peut probablement être fait avec une manipulation de code) mais cela résout mon problème de pagination pour les champs non uniques sans avoir à utiliser skip qui consomme beaucoup de mémoire et de traitement puissance lorsque vous arrivez à la fin de l’ensemble de données que vous interrogez.

1
Carlos Ruiz