Случайный порядок сортировки

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

Мне нужно собрать несколько случайных документов из коллекции и что еще хуже - эти документы должны соответствовать определенным критериям (отфильтрованные, я имею в виду). Например, у меня есть сборник статей, в которых каждая статья имеет поле "тема". Пользователь выбирает интересующую вас тему, и мой db должен показывать соответствующие статьи каждый раз в случайном порядке.

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

var arr = db.articles.find({topic: 3}, {_id:1}).toArray();

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

Как вы можете видеть, это кажется немного слишком медленным, особенно, если слишком много статей возвращено первым запросом :)

Поэтому я думаю, что может быть какая-то команда mongodb для получения документов по индексным ключам на основе их позиции в индексе. Дело в том, что я могу создать закрытый составной индекс следующим образом:

db.articles.ensureIndex({topic: 1, _id:1});

И теперь мой запрос должен был бы отсканировать непрерывную строку правых _ids в индексе. И если бы я мог запросить документы из коллекции этими позициями "_ids", тогда я мог бы сделать все в одном запросе! Что-то вроде:

var cursor = db.articles.find({topic:3, $indexKeyPosition: {$in: myRandomSequence}});

Кто-нибудь знает о таких функциях?

Ответ 1

В настоящее время вы должны иметь возможность использовать функцию агрегации $sample.

Пример (непроверенный):

db.articles.aggregate([
    { $match : { topic : 3 } },
    { $sample : { size: 3 } }
])

Обратите внимание, однако, что он может возвращать один и тот же документ более одного раза.

Ответ 2

Поэтому я думаю, что может быть какая-то команда mongodb для получения документов по индексным ключам на основе их позиции в индексе. Дело в том, что я могу создать закрытый составной индекс следующим образом:

Нет никакой функции в MongoDB, хотя это хорошая идея, чтобы иметь возможность рандомизировать набор результатов. Тем временем здесь находится JIRA: https://jira.mongodb.org/browse/SERVER-533

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

Текущее решение зависит от количества документов в вашем результирующем наборе.

Если в вашем результирующем наборе имеется небольшое количество документов, вы можете решить это с помощью простого skip(rand()) и limit(1) однако вы должны знать, что и skip() и limit() не используют эффективное использование индексов,

Это не означает, что он сканирует весь Btree, это означает, что он будет сканировать до тех пор, пока вы не skip().

Это означает, что если ваш результирующий набор станет большим, а rand() станет большим числом, вы увидите серьезные проблемы с производительностью, как и многие.

Одним из хороших способов решения этой проблемы является поддержание:

И используйте это новое поле, чтобы "пропустить", используя остальную часть вашего запроса, например:

var arr = db.articles.find({topic: 3, rand: rand()}, {_id:1}).limit(7).toArray();

Получит 7 случайных строк, используя идею от 0 до 1.

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

Что касается использования batchSize, он становится неуязвимым здесь, и обычно это происходит. Например, ваша логика использования BatchSize для получения всех ваших результатов назад не имеет полного смысла, поскольку BatchSize обычно имеет максимальный максимальный размер 16 МБ. Это означает, что если ваши документы большие, возможно, вы не получите ни одного тура в оба конца, как вы думаете.

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

Поэтому, учитывая, что вы должны сделать это с помощью mutliple курсоров (как я бы рекомендовал), вы можете просто запустить:

var arr = db.articles.find({topic: 3, rand: {$gte:rand()}}).sort({rand:1}).limit(1);

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

Есть еще один метод, но я не рекомендую его. Вы можете запускать MR, скажем, один раз в час или что-то, что создает другую коллекцию _id и rand() это означает, что вы можете сделать первый запрос, который я показал:

var arr = db.articles.find({topic: 3, rand: rand()}, {_id:1}).limit(7).toArray();

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

редактировать

Есть еще один способ. С помощью auto incrementing id вы можете сделать $or statement, чтобы выбрать 7 rand() сразу. Однако это вводит еще одну проблему, удаляя.

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

Чтобы добавить к этому $or операторы не может быть limit() ed on, что означает, что вы не можете обойти это, выполнив тип $gte $or чтобы MongoDB выбирал только один результат за $or предложение, используя $gte.

То же самое относится к rand() между 0 и 1. Это будет работать с $or если вы можете ограничить предложения.

Ответ 3

Вы можете (как в разбивке на страницы) подсчитать, сколько документов соответствует запросу. Затем выполните N запросов с пропуском (random_value) и limit (1).

db.collection.count({field:value,field2:value2})

db.collection.find({field:value,field2:value2}).skip(n).limit(1)

Если коллекция проиндексирована для запроса, она должна быть быстрой.