MongoDB - stronicowanie

81

Czy podczas korzystania z MongoDB istnieją jakieś specjalne wzorce tworzenia np. Widoku stronicowanego? powiedz blog, który zawiera listę 10 najnowszych postów, na którym możesz przejść wstecz do starszych postów.

Czy można rozwiązać to za pomocą indeksu np. Blogpost.publishdate i po prostu pominąć i ograniczyć wynik?

Roger Johansson
źródło
1
Zostawię ten wisi, ponieważ wydaje się, że istnieje spór co do tego, jaki jest właściwy sposób wykonania tej skali.
Roger Johansson,

Odpowiedzi:

98

Używanie skip + limit nie jest dobrym sposobem na stronicowanie, gdy problemem jest wydajność lub w przypadku dużych kolekcji; będzie działać coraz wolniej w miarę zwiększania liczby stron. Użycie pomijania wymaga, aby serwer przeszedł przez wszystkie dokumenty (lub wartości indeksu) od 0 do wartości przesunięcia (pominięcia).

O wiele lepiej jest użyć zapytania zakresowego (+ limit), w którym podaje się wartość zakresu ostatniej strony. Na przykład, jeśli sortujesz według „opublikowania”, możesz po prostu przekazać ostatnią wartość „opublikujdatę” jako kryterium zapytania, aby uzyskać następną stronę danych.

Scott Hernandez
źródło
4
Wspaniale będzie zobaczyć niektóre dokumenty, które potwierdzają, że pomijaj mongodb iterując przez wszystkie dokumenty.
Andrew Orsich,
5
Proszę bardzo: pomiń dokumenty Jeśli jest jakieś inne miejsce, w którym informacje powinny zostać zaktualizowane, daj mi znać.
Scott Hernandez
2
@ScottHernandez: Mam stronicowanie z linkami do wielu stron (na przykład: Strona: Pierwsza, 2, 3, 4, 5, Ostatnia) i sortowanie po wszystkich polach. Tylko jedno z moich pól jest unikalne (i zindeksowane). Czy zapytanie o zakres będzie działać w tym przypadku użycia? Obawiam się, że nie, chciałem tylko potwierdzić, czy to w ogóle możliwe. Dzięki.
user183037
7
Oto link
Ulises
8
Wydaje się, że nie zadziałaby, gdyby istniało wiele dokumentów z tą samą wartością opublikowania.
d512,
12
  1. Stronicowanie na podstawie zakresu jest trudne do zaimplementowania, jeśli musisz sortować elementy na wiele sposobów.
  2. Pamiętaj, że jeśli wartość pola parametru sortowania nie jest unikalna, stronicowanie oparte na zakresie stanie się nierealne.

Możliwe rozwiązanie: spróbuj uprościć projekt, zastanawiając się, czy możemy sortować tylko według identyfikatora lub jakiejś unikalnej wartości?

Jeśli możemy, można zastosować stronicowanie oparte na zakresie.

Powszechnym sposobem jest użycie sort (), skip () i limit () w celu zaimplementowania stronicowania, co opisano powyżej.

szakalop
źródło
dobry artykuł z przykładami kodu w Pythonie można znaleźć tutaj codementor.io/arpitbhayani/…
Gianfranco P.
1
Dziękuję - świetna odpowiedź! Denerwuje mnie, gdy ludzie sugerują paginację za pomocą filtrów, np. { _id: { $gt: ... } }... po prostu nie działa przy niestandardowym zamówieniu - np .sort(...).
Nick Grealy,
1
@NickGrealy Postępowałem zgodnie z tutorialem, aby to zrobić i jestem teraz w sytuacji, w której stronicowanie `` wygląda '' tak, jak działa, ale brakuje mi dokumentów, ponieważ używam identyfikatora mongo, ale gdy nowe dane są wstawiane do bazy danych, a następnie zbiór jest sortowany alfabetycznie, jeśli strona początkowa zawiera rekordy zaczynające się od A, ale identyfikatory są wyższe niż rekordy rozpoczynające się od AA, ponieważ zostały one wstawione po tym, jak rekordy AA nie są zwracane przez stronicowanie. Czy opcja Skip and Limit jest odpowiednia? Mam około 60 milionów dokumentów do przeszukania.
berimbolo
@berimbolo - warto o tym porozmawiać - w komentarzach nie dostaniesz odpowiedzi. Pytanie: jakiego zachowania się spodziewasz? Pracujesz z systemem na żywo, w którym rekordy są tworzone i usuwane przez cały czas. Jeśli ponownie poprosisz o migawkę danych na żywo przy każdym wczytaniu nowej strony, powinieneś spodziewać się zmiany danych bazowych. Jakie powinno być zachowanie? Jeśli pracujesz z migawką danych „z punktu w czasie”, będziesz mieć „poprawione strony”, ale także dane „nieaktualne”. Jak duży jest problem, który opisujesz i jak często ludzie go napotykają?
Nick Grealy
1
Na pewno warto porozmawiać, moim problemem jest to, że odzyskałem jednorazowy plik w kolejności alfabetycznej tablic rejestracyjnych i co 15 minut aktualizuję zmienione (usunięte lub dodane) tablice, problem polega na tym, że jeśli zostanie dodana nowa tablica i zacznie się na przykład z literą A i ze względu na rozmiar strony jest ostatnią na stronie, a następnie, jeśli zażąda się następnego, żadne rekordy nie zostaną zwrócone, jak sądzę (założenie i wymyślony przykład, ale ilustrują mój problem), ponieważ identyfikator jest wyższy niż jakikolwiek inny w zbiór. Patrzę teraz na użycie pełnej tablicy rejestracyjnej do przejechania większej niż część zapytania.
berimbolo
5

To jest rozwiązanie, którego użyłem, gdy moja kolekcja stała się zbyt duża, aby można ją było zwrócić w pojedynczym zapytaniu. Wykorzystuje nieodłączną kolejność _idpola i umożliwia zapętlenie kolekcji według określonego rozmiaru partii.

Tutaj jest to moduł npm, mongoose-paging , pełny kod jest poniżej:

function promiseWhile(condition, action) {
  return new Promise(function(resolve, reject) {
    process.nextTick(function loop() {
      if(!condition()) {
        resolve();
      } else {
        action().then(loop).catch(reject);
      }
    });
  });
}

function findPaged(query, fields, options, iterator, cb) {
  var Model  = this,
    step     = options.step,
    cursor   = null,
    length   = null;

  promiseWhile(function() {
    return ( length===null || length > 0 );
  }, function() {
    return new Promise(function(resolve, reject) {

        if(cursor) query['_id'] = { $gt: cursor };

        Model.find(query, fields, options).sort({_id: 1}).limit(step).exec(function(err, items) {
          if(err) {
            reject(err);
          } else {
            length  = items.length;
            if(length > 0) {
              cursor  = items[length - 1]._id;
              iterator(items, function(err) {
                if(err) {
                  reject(err);
                } else {
                  resolve();
                }
              });
            } else {
              resolve();
            }
          }
        });
      });
  }).then(cb).catch(cb);

}

module.exports = function(schema) {
  schema.statics.findPaged = findPaged;
};

Dołącz go do swojego modelu w następujący sposób:

MySchema.plugin(findPaged);

Następnie zapytaj w ten sposób:

MyModel.findPaged(
  // mongoose query object, leave blank for all
  {source: 'email'},
  // fields to return, leave blank for all
  ['subject', 'message'],
  // number of results per page
  {step: 100},
  // iterator to call on each set of results
  function(results, cb) {
    console.log(results);
    // this is called repeatedly while until there are no more results.
    // results is an array of maximum length 100 containing the
    // results of your query

    // if all goes well
    cb();

    // if your async stuff has an error
    cb(err);
  },
  // function to call when finished looping
  function(err) {
    throw err;
    // this is called once there are no more results (err is null),
    // or if there is an error (then err is set)
  }
);
mz3
źródło
Nie wiem, dlaczego ta odpowiedź nie ma więcej głosów za. to jest bardziej efektywny sposób na paginację niż skip / limit
nxmohamad
Ja też przeszedłem przez ten pakiet, ale jak to jest wydajność w porównaniu do skip / limit i odpowiedzi udzielonej przez @Scott Hernandez?
Tanckom
5
Jak działałaby ta odpowiedź w przypadku sortowania na jakimkolwiek innym polu?
Nick Grealy,
1

Stronicowanie na podstawie zakresu jest wykonalne, ale musisz być sprytny, jeśli chodzi o min / max zapytania.

Jeśli możesz sobie na to pozwolić, spróbuj buforować wyniki zapytania w tymczasowym pliku lub kolekcji. Dzięki kolekcjom TTL w MongoDB możesz wstawić swoje wyniki do dwóch kolekcji.

  1. Wyszukiwanie + użytkownik + zapytanie dotyczące parametrów (bez względu na TTL)
  2. Wyniki zapytania (bez względu na TTL + interwał czyszczenia + 1)

Używając obu zapewnień, nie uzyskasz częściowych wyników, gdy TTL będzie zbliżone do aktualnego czasu. Podczas przechowywania wyników możesz skorzystać z prostego licznika, aby w tym momencie wykonać BARDZO proste zapytanie o zakres.

Whardier
źródło
1

Oto przykład pobierania listy Userdokumentów w kolejności CreatedDate(gdzie pageIndexjest od zera) przy użyciu oficjalnego sterownika C #.

public void List<User> GetUsers() 
{
  var connectionString = "<a connection string>";
  var client = new MongoClient(connectionString);
  var server = client.GetServer();
  var database = server.GetDatabase("<a database name>");

  var sortBy = SortBy<User>.Descending(u => u.CreatedDate);
  var collection = database.GetCollection<User>("Users");
  var cursor = collection.FindAll();
  cursor.SetSortOrder(sortBy);

  cursor.Skip = pageIndex * pageSize;
  cursor.Limit = pageSize;
  return cursor.ToList();
}

Wszystkie operacje sortowania i stronicowania są wykonywane po stronie serwera. Chociaż jest to przykład w C #, myślę, że to samo można zastosować do portów innych języków.

Zobacz http://docs.mongodb.org/ecosystem/tutorial/use-csharp-driver/#modifying-a-cursor-before-enumerating-it .

Alex Ho
źródło
0
    // file:ad-hoc.js
    // an example of using the less binary as pager in the bash shell
    //
    // call on the shell by:
    // mongo localhost:27017/mydb ad-hoc.js | less
    //
    // note ad-hoc.js must be in your current directory
    // replace the 27017 wit the port of your mongodb instance
    // replace the mydb with the name of the db you want to query
    //
    // create the connection obj
    conn = new Mongo();

    // set the db of the connection
    // replace the mydb with the name of the db you want to query
    db = conn.getDB("mydb");

    // replace the products with the name of the collection
    // populate my the products collection
    // this is just for demo purposes - you will probably have your data already
    for (var i=0;i<1000;i++ ) {
    db.products.insert(
        [
            { _id: i, item: "lamp", qty: 50, type: "desk" },
        ],
        { ordered: true }
    )
    }


    // replace the products with the name of the collection
    cursor = db.products.find();

    // print the collection contents
    while ( cursor.hasNext() ) {
        printjson( cursor.next() );
    }
    // eof file: ad-hoc.js
Yordan Georgiev
źródło