Подтвердить что ты не робот

Запрос диапазона для разбивки на страницы MongoDB

Я хочу реализовать разбиение на страницы поверх MongoDB. Для моего запроса диапазона я подумал об использовании ObjectID:

db.tweets.find({ _id: { $lt: maxID } }, { limit: 50 })

Однако в соответствии с документами структура ObjectID означает, что "значения ObjectId не представляют строгий порядок вставки":

Связь между порядком значений ObjectId и временем генерации не является строгой в течение одной секунды. Если несколько систем или несколько процессов или потоков в одной системе генерируют значения за одну секунду; Значения ObjectId не представляют собой строгий порядок вставки. Временное перекос между клиентами также может привести к нестандартному упорядочению даже для значений, поскольку драйверы клиента генерируют значения ObjectId, а не процесс mongod.

Затем я подумал о запросе с меткой времени:

db.tweets.find({ created: { $lt: maxDate } }, { limit: 50 })

Однако нет никакой гарантии, что дата будет уникальной - вполне вероятно, что за одну секунду могут быть созданы два документа. Это означает, что документы могут быть пропущены при пейджинге.

Есть ли какой-либо запрос диапазона, который обеспечит мне большую стабильность?

4b9b3361

Ответ 1

Хорошо использовать ObjectId(), хотя ваш синтаксис для разбивки на страницы неверен. Вы хотите:

 db.tweets.find().limit(50).sort({"_id":-1});

Это говорит о том, что вы хотите, чтобы твиты, отсортированные по значению _id в порядке убывания, и вам нужны самые последние 50. Ваша проблема заключается в том, что разбиение на страницы сложно, когда меняется текущий набор результатов, поэтому вместо использования пропустить для следующего страницы, вы хотите отметить наименьший _id в результирующем наборе (50-е последнее значение _id), а затем получить следующую страницу с помощью

 db.tweets.find( {_id : { "$lt" : <50th _id> } } ).limit(50).sort({"_id":-1});

Это даст вам следующие "самые последние" твиты, без новых входящих твитов, которые испортят вашу разбивку на страницы во времени.

Абсолютно не нужно беспокоиться о том, что значение _id строго соответствует порядку вставки - оно будет достаточно близко 99,999%, и никто на самом деле не заботится о второстепенном уровне, на котором сначала появился твит, вы можете даже уведомление Twitter часто отображает твиты не по порядку, это просто не так важно.

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

Ответ 2

Не будет ли "актуальная" временная метка "твита" (т.е. время в твиттере и критерии, которые вы хотите отсортировать) отличаться от отметки времени "вставки" твита (т.е. время, добавленное в местную коллекцию). Разумеется, это зависит от вашего приложения, но это вероятный сценарий, в котором можно вставлять вставки для твитов или иначе вставлять их в "неправильный" порядок. Итак, если вы не работаете в Twitter (и имеете доступ к коллекциям, вставленным в правильном порядке), вы не сможете полагаться только на $natural или ObjectID для сортировки логики.

Mongo docs предлагает skip и limit для подкачки:

db.tweets.find({created: {$lt: maxID}).
          sort({created: -1, username: 1}).
          skip(50).limit(50); //second page

Однако при использовании пропусков возникает проблема с производительностью:

Метод cursor.skip() часто дорог, потому что он требует, чтобы сервер шел от начала коллекции или индекса, чтобы получить смещение или пропустить позицию, прежде чем начать возвращать результат. По мере увеличения смещения, cursor.skip() будет становиться все медленнее и интенсивнее.

Это происходит потому, что skip не вписывается в модель MapReduce и не является операцией, которая будет хорошо масштабироваться, вам придется подождать, пока отсортированная коллекция станет доступной, прежде чем ее можно будет нарезать. Теперь limit(n) звучит как плохой метод, так как он применяет аналогичное ограничение "с другого конца"; однако при использовании сортировки двигатель может несколько оптимизировать процесс, сохраняя только элементы n в памяти на каждый осколок, когда он пересекает коллекцию.

Альтернативой является использование оповещения на основе диапазона. После получения первой страницы твитов вы знаете, что значение created для последнего твита, поэтому все, что вам нужно сделать, это заменить исходное maxID на это новое значение:

db.tweets.find({created: {$lt: lastTweetOnCurrentPageCreated}).
          sort({created: -1, username: 1}).
          limit(50); //next page

Выполнение такого условия find может быть легко параллелизовано. Но как обращаться со страницами, кроме следующего? Вы не знаете дату начала для страниц № 5, 10, 20 или даже на предыдущей странице! @SergioTulentsev предлагает творческую цепочку методов, но я бы отстаивал предварительные вычисления первых диапазонов совокупного поля в отдельной коллекции pages; они могут быть пересчитаны при обновлении. Кроме того, если вы недовольны DateTime (обратите внимание на примечания о производительности) или обеспокоены дублирующими значениями, вы должны рассмотреть составные индексы в timestamp + account tie (так как пользователь не может дважды прокручивать дважды) или даже искусственный агрегат из двух:

db.pages.
find({pagenum: 3})
> {pagenum:3; begin:"[email protected]"; end:"[email protected]_ben_clock"}

db.tweets.
find({_sortdate: {$lt: "[email protected]_ben_clock", $gt: "[email protected]"}).
sort({_sortdate: -1}).
limit(50) //third page

Использование агрегированного поля для сортировки будет работать "на складке" (хотя, возможно, есть более кошерные способы борьбы с этим условием). Это можно было бы установить как уникальный индекс со значениями, скорректированными во время вставки, с одним твитковым документом, похожим на

{
  _id: ...,
  created: ...,    //to be used in markup
  user: ...,    //also to be used in markup
  _sortdate: "[email protected]" //sorting only, use date AND time
}

Ответ 3

ObjectIds должны быть достаточно хороши для разбивки на страницы, если вы ограничиваете свои запросы до предыдущей секунды (или не заботитесь о возможности субэконичности странности). Если это недостаточно для ваших нужд, вам нужно будет внедрить систему генерации идентификаторов, которая работает как автоматическое приращение.

Update:

Чтобы запросить предыдущую секунду ObjectIds, вам нужно будет создать ObjectID вручную.

См. спецификацию ObjectId http://docs.mongodb.org/manual/reference/object-id/

Попробуйте использовать это выражение, чтобы сделать это из mongos.

{ _id : 
  {
      $lt : ObjectId(Math.floor((new Date).getTime()/1000 - 1).toString(16)+"ffffffffffffffff")
  }

}

"f" в конце - это максимальное количество возможных случайных битов, которые не связаны с меткой времени, поскольку вы делаете меньше запроса.

Я рекомендую во время фактического создания ObjectId на сервере приложений, а не на монго, поскольку этот тип вычислений может замедлить вас, если у вас много пользователей.

Ответ 4

Следующий подход будет работать, даже если в один и тот же миллисекунда вставлено/обновлено несколько документов, даже если из нескольких клиентов (который генерирует ObjectId). Для симуляции В следующих запросах я проецирую _id, lastModifiedDate.

  • Первая страница, выберите результат Сортируется по измененному времени (по убыванию), ObjectId (по возрастанию) для первой страницы.

    db.product.find({},{"_id":1,"lastModifiedDate":1}).sort({"lastModifiedDate":-1, "_id":1}).limit(2)

Запишите объекты ObjectId и lastModifiedDate из последней записи, выбранной на этой странице. (loid, lmd)

  • Для страницы sencod включите условие запроса для поиска if (lastModifiedDate = lmd AND oid > loid) ИЛИ (lastModifiedDate < loid)

db.productfind({$or:[{"lastModifiedDate":{$lt:lmd}},{"_id":1,"lastModifiedDate":1},{$and:[{"lastModifiedDate":lmd},{"_id":{$gt:loid}}]}]},{"_id":1,"lastModifiedDate":1}).sort({"lastModifiedDate":-1, "_id":1}).limit(2)

повторите то же самое для последующих страниц.

Ответ 5

Пользовательская разбивка по страницам с использованием запроса Mongo с Drupal

global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;

$per_page = 10;

$page = isset ($ _ GET ['page'])? $_GET ['page']: '';

$pager_page_array = explode (',', $page);

$on_page = $pager_page_array [0];

//результат запроса

$result1 = $res- > find (array ( "exam_nid" = > (int) $exam_id)) → limit ($ per_page) → skip ($ on_page * $per_page);

$form ['path'] ['table'] = array (

'# theme' = > 'table',

'# header' = > $header,

'# rows' = > $row,

'# empty' = > t ( "Таблица не имеет строки!" )

);

//Добавить пейджер.

if ($ on_page > 0 || count ($ row) >= $per_page) {

$pager_total_items [0] = $result1- > count();

$pager_total [0] = ceil ($ pager_total_items [0]/$per_page);

$pager_page_array [0] = max (0, min ((int) $pager_page_array [0],

((int) $pager_total [0]) - 1));

$pager_limits [0] = $per_page;

$form ['path'] ['pager'] = array (

'# theme' = > 'pager',

);

}