MongoDB - підкачка


81

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

Або хтось вирішує це за допомогою індексу, наприклад, blogpost.publishdate, і просто пропускає та обмежує результат?


1
Я збираюся залишити цього висіти, оскільки, схоже, є певні незгоди щодо того, який правильний спосіб зробити цей масштаб.
Роджер Йоханссон

Відповіді:


98

Використання скіп + ліміт не є хорошим способом підкачки сторінок, коли продуктивність є проблемою, або з великими колекціями; він буде ставати повільнішим і повільнішим із збільшенням номера сторінки. Використання пропуску вимагає від сервера проходження, хоча всі документи (або значення індексу) від 0 до значення зміщення (пропуску).

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


4
Буде чудово побачити деякі документи, які підтверджують, що пропуск у mongodb переглядає всі документи.
Ендрю Орсіч

5
Ось: пропустіть документи Якщо є інше місце, де інформація повинна бути оновлена, будь ласка, повідомте мене.
Скотт Ернандес,

2
@ScottHernandez: У мене є підкачка сторінок із посиланнями на кілька сторінок (наприклад: Сторінка: Перша, 2, 3, 4, 5, Остання) та сортування по всіх полях. Тільки одне з моїх полів є унікальним (та індексується), чи спрацює запит діапазону для цього випадку використання? Боюсь, що ні, я просто хотів підтвердити, чи це взагалі можливо. Дякую.
user183037

7
Ось посилання пропустити документи
Ulises

8
Здається, це не спрацювало б, якби було кілька документів з однаковим значенням дати публікації.
d512

12
  1. Підкачування на основі діапазону важко реалізувати, якщо вам потрібно сортувати елементи різними способами.
  2. Пам'ятайте, якщо значення поля параметра сортування не є унікальним, тоді підкачка на основі діапазону стане недостовірною.

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

І якщо ми можемо, тоді може використовуватися сторінка на основі діапазону.

Поширений спосіб - використовувати sort (), skip () та limit () для реалізації підкачки, що описано вище.


хорошу статтю з прикладами коду Python можна знайти тут codementor.io/arpitbhayani/…
Джанфранко П.

1
Дякую - чудова відповідь! Мене дратує, коли люди пропонують пагінацію за допомогою фільтрів, наприклад { _id: { $gt: ... } }... це просто не працює, якщо використовується спеціальне впорядкування - наприклад .sort(...).
Nick Grealy

1
@NickGrealy Я дотримувався підручника, щоб зробити саме це, і зараз я опинився в ситуації, коли підкачка `` виглядає '', як би це працювало, але я отримую відсутні документи, оскільки я використовую ідентифікатор mongo, але коли нові дані вставляються в db, а потім колекція сортується в алфавітному порядку, якщо початкова сторінка містить записи, які починаються з А, але ідентифікатори вище, ніж записи, що починають АА, оскільки вони були вставлені після того, як записи АА не повертаються сторінкою. Чи підходить пропуск і обмеження? У мене є близько 60 мільйонів документів для пошуку.
berimbolo

@berimbolo - це гідно розмови - ви не отримаєте відповіді тут, у коментарях. Питання: якої поведінки ви очікуєте? Ви працюєте з живою системою, постійно створюючи та видаляючи записи. Якщо ви повторно подаєте запит на оперативний знімок ваших даних для кожного нового завантаження сторінки, то слід очікувати, що базові дані будуть змінюватися. Якою має бути поведінка? Якщо ви працюєте зі знімком даних "у часі", у вас будуть "фіксовані сторінки", але ви також матимете "застарілі" дані. Наскільки велика проблема, яку ви описуєте, і як часто люди стикаються з нею?
Нік Грілі,

1
Безумовно, варто поговорити, моя проблема полягає в тому, що я отримав разовий файл в алфавітному порядку номерних знаків і кожні 15 хвилин застосовую оновлення до змінених (видалених або доданих) табличок, проблема полягає в тому, що якщо буде додано нову табличку і вона почнеться наприклад, з A і через розмір сторінки є останньою на сторінці, тоді, якщо запитується наступна, тоді не повертаються записи, я вважаю (припущення та надуманий приклад, але ілюстративний для моєї проблеми), оскільки ідентифікатор вище, ніж будь-які інші в набір. Я розглядаю використання повного номерного знаку для керування більшою частиною запиту зараз.
berimbolo

5

Це рішення, яке я використав, коли моя колекція зросла занадто великою, щоб повернутися в одному запиті. Він використовує переваги властивого впорядкування _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)
  }
);

не знаю, чому ця відповідь не має більше голосів. це більш ефективний спосіб
перенесення

Я також ознайомився з цим пакетом, але як це ефективність порівняно з пропуском / обмеженням та відповіддю, наданою @Scott Hernandez?
Танком

5
Як би працювала ця відповідь для сортування в будь-якому іншому полі?
Nick Grealy

1

Підкачка на основі діапазону здійсненна, але ви повинні бути розумними щодо того, як ви мінімізуєте / максимум запиту.

Якщо ви можете собі це дозволити, спробуйте кешувати результати запиту у тимчасовому файлі чи колекції. Завдяки колекціям TTL в MongoDB ви можете вставити свої результати у дві колекції.

  1. Пошук + користувач + запит параметрів (TTL незалежно)
  2. Результати запиту (TTL що-небудь + інтервал очищення + 1)

Використовуючи обидва запевнення, ви не отримаєте часткових результатів, коли TTL наближається до поточного часу. Ви можете використовувати простий лічильник, коли зберігаєте результати, щоб зробити ДУЖЕ простий запит діапазону в цей момент.


1

Ось приклад отримання списку 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 .


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.