В MongoDB - пейджинг


при использовании MongoDB существуют ли какие-либо специальные шаблоны для создания, например, paged view? скажем, блог, в котором перечислены 10 последних сообщений, где вы можете перейти назад к более старым сообщениям.

или решить его с помощью индекса, например, blogpost.publishdate и просто пропустить и ограничить результат?

6 70

6 ответов:

использование skip + limit не является хорошим способом сделать подкачку, когда производительность является проблемой или с большими коллекциями; он будет становиться все медленнее и медленнее по мере увеличения номера страницы. Использование skip требует, чтобы сервер прошел через все документы (или значения Индекса) от 0 до значения смещения (пропуска).

гораздо лучше использовать запрос диапазона (+limit), где вы передаете значение диапазона последней страницы. Например, если вы сортируете по "publishdate" , вы просто передадите последний значение "publishdate" в качестве критерия запроса для получения следующей страницы данных.

  1. диапазон на основе подкачки трудно реализовать, если вам нужно сортировать элементы во многих отношениях.
  2. помните, что если значение поля параметра сортировки не является уникальным , то подкачка на основе диапазона станет невозможной.

возможное решение: попробуйте упростить desgin, думая о том, можем ли мы сортировать только по идентификатору или некоторому уникальному значению?

и если мы можем, то диапазон на основе разбиения на страницы могут быть использованы.

распространенным способом является использование сортировки() , пропустить() и limit () для реализации подкачки то, что описано выше.

это решение я использовал, когда моя коллекция стала слишком большой, чтобы вернуть в одном запросе. Он использует преимущества присущего упорядочения _id поле и позволяет вам перебирать коллекцию по заданному размеру пакета.

вот он как модуль npm,Мангуст-пейджинг, полный код ниже:

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;
};

прикрепите его к вашей модели следующим образом:

MySchema.plugin(findPaged);

тогда запрос такой:

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)
  }
);

диапазон на основе подкачки выполним, но вы должны быть умны о том, как вы мин/макс запрос.

Если вы можете себе это позволить, вы должны попробовать кэшировать результаты запроса во временном файле или коллекции. Благодаря TTL коллекциям в MongoDB вы можете вставить свои результаты в две коллекции.

  1. Search + User + Parameters Query (TTL whatever)
  2. результаты запроса (TTL whatever + интервал очистки + 1)

использование обоих гарантирует вам не будет получать частичные результаты, когда TTL находится рядом с текущим временем. Вы можете использовать простой счетчик, когда вы храните результаты, чтобы сделать очень простой запрос диапазона в этот момент.

вот пример получения списка User документы заказа CreatedDate (где pageIndex на основе нуля) с использованием официального драйвера 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();
}

все операции сортировки и разбиения на страницы выполняются на стороне сервера. Хотя это пример в C#, я думаю, что то же самое можно применить к другим языковым портам.

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

    // 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