Поиск по MongoDB на примере Quto.ru
December 6, 2015
В далеком 2014 году я пришел на работу в компанию Rambler&Co и одним из моих первых заданий было улучшение текущего поиска. Задача была достаточно сложной (на тот момент), потому как незнание проекта и структуры хранения его в данных доставляло некоторые неудобства.
Более того, есть понятие “пакетов опций” (к примеру климат контроль отдельно стоит дороже чем в пакете) и “взаимоисключения опций” (к примеру кандей и климат контроль вместе не установить), а так же “возможнолсть взаимоисключения пакетов опций”.
После долгого “курения” структуры данных (она достаточна простая, однако дерево достаточно большое) было принято решение не городить изгороду из джоинов и кешей, которые на само деле нельзя было там применять по простой причине разных фильтров.
Как всегда, хочется не просто сделать работу, а сделать ее красиво и качественно. Было принято решение не просто написать поиск по фильтру, а так же считать на лету с учетом выбраных фильтров еще и их цену и, соответственно, вхождение в дельту по цене с учетом этих фильтров. К примеру, есть у нас опция “климат-контроль” и есть машинв “лада приора”.
Стоимость лады приоры, к примеру, 400 000 рублей. С учетом опции стоимость уже 450 000 рублей.
При условии указания дельты цены 200 000 - 400 000 не должны входить в поиск машины за 450 000 с учетом установленной опции.
Так же, хотелось бы информировать пользователя в результатах поисковой выдачи о стоимости каждой опции конкретно и итоговой стоимости авто.
В конечном итоге, спустя 2 недели, на продакшен был выкачен новый поиск.
###Что в итоге получили:
- количество страниц в поиске + постраничная навигация (хоть и смешно, но до этого момента не было)
- легкий индекс данных в Mongo (всего 11 Мб коллекция)
- простой и расширяемый интерфейс для дальнейшего улучшения
- легкая и быстрая индексация из БД в Mongo
- быстрый поиск
###С чем пришлось столкнуться
- были проблемы с сортировкой после UNWIND, оказалось что после того как снова собираются в группу - сортировка пропадает. Происходит это по вине асинхронности аггрегативных функций.
- отсутствие связности (к примеру бренд-модель) заставило идти в сторону хранения дополнительной (избыточной) информации.
###Краткое описание
В Gist’e несколько файлов, вот их описание:
- mongo_document_example.json - вид единичного документа в Mongo (пример);
- mongo_query_example.json - пример запроса в Mongo, который генерирует набор скриптов;
- Result.php - обработка результирующих данных
- Search.php - объект (класс), осуществляющий поиск
- SearchCriteriaBuilder.php - “строитель” запросов по условиям
- SearchCriteriaDefault.php - “базовое условие” на основе фильтра
- SearchHelper.php - для удобства =)
Конечно, рефакторинг бы не помешал. Уж очень стремно и глупо этот код поныне выглядит. Однако, свою задачу он выполняет. Разобраться в нем сможет даже джуниор. Добавить специфический фильтр не составит труда.
Все детали формирования запроса и логика его формирования в классе: SearchCriteriaDefault.
Посмотреть код на gist.github.com!
Благодарю за внимание!