Потому что иногда вам нужно получить случайный документ из вашей базы данных

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

Прежде всего: сценарии использования и реальные потребности

В конце концов, вам действительно нужно иметь возможность рандомизировать извлечение документов.

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

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

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

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

Вот несколько возможных сценариев:

  • Показаны популярные сообщения
  • Извлечение случайных новостей из определенной категории
  • Вывод данных образца
  • Применение случайности для соответствия и достижения определенных целей - например, предоставление вашему контенту такой же скорости показов и распределения.
  • Генерация случайного контента для рандомизации поведения - возможно, в целях тестирования, например, для ввода данных в модульные тесты.

Проблема разбивки на страницы

Просто и очень неприятно: когда вы извлекаете первые 50 документов, вы хотите иметь возможность разбивать данные на страницы и правильно использовать $skip и $limit, показывая согласованные данные в каждом последующем запросе и избегая дублирования контента.

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

Первый подход - это запоминать (или кэшировать) контент, выдаваемый в каждом предыдущем выводе. Но это плохо. Нет способа масштабировать этот подход, и мы не хотим хранить бесполезные данные в нашей коллекции документов x миллиардов - мы хотим сделать это лучше. Так как мы могли?

Отправка запроса

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

Такой подход действительно существует в SQL. Популярный Order by Random() может просто применить определенное значение, вызвав случайную функцию со значением в скобках. Итак, такой запрос…

SELECT * FROM theCollection ORDER BY RANDOM(123)

… Годится для большинства СУБД. Так что насчет MongoDB?
MongoDB не предоставляет никаких конкретных операторов или функций для рандомизации доступа к коллекции, кроме оператора $sample.

Оператор $ sample: не решение

Оператор $sample может использоваться внутри любого конвейера агрегации, так как он существует как этап конвейера.

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

db.theCollection.aggregate(
   [ { $sample: { field: fieldValue } } ]
)

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

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

Правильный путь

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

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

  • Клиент дает нам семя
  • Запрос использует начальное число для перемешивания, рандомизации и последующего проецирования нового поля.
  • Затем запрос упорядочивает результаты по этому новому значению.

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

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

Все зависит от вас и очень зависит от структуры сбора и моделирования данных вашей базы данных.

По моему опыту, использовать семена в качестве чисел намного проще, чем манипулировать форматами строк (количество символов, длина строки и т. Д.).

Используя число в качестве начального числа, легко найти формулу для $project нового поля со случайным значением. Я лично использовал оператор модуля (%) или делителя (/) с очень хорошими результатами, сохраняя очень низкую сложность и избегая накладных расходов на ЦП.

Выводы

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

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

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