Комментарии 18
Интересно было бы посмотреть на сравнение производительности простых запросов, group(), Aggregation Framework и MapReduce. На StackOverflow, например, утверждают, что Aggregation Framework на порядок быстрее MapReduce.
Была задача группировать по периодам (минута, час, день). Сделано было сначала через MapReduce MongoDB Docs Hierarchical Aggregation, а потом через Aggregate Framework, прирост в производительности порядка 70%.
Кстати, есть подозрение, что индекс или индекс + шардинг дадут ещё больший прирост запросов.
В нашем случае это уже были индексы + 5 шардов, на каждый 2 реплики. Map reduce отлично справляется с 20к записями, потом сложно идет, но странно то, что как раз когда записей много этот функционал востребован. А тот же Aggregate со всем справляется в лет, и перемалывает более 10000к документов за секунды
Всё, что я читал/слышал об aggregation framework подтверждает это мнение. Но. Лично моё мнение, что они решают несколько разные задачи. Да, с помощью map-reduce можно решать задачи простейших группировок и конечно же он будет проигрывать инструментам, заточенным под эти задачи. Но когда речь идёт, скажем, о классической для map-reduce задаче анализа и классификации текстов, то тут уже в принципе не получится сравнивать map-reduce и aggregation, т.к. последний просто не справится с поставленной задачей. Т.е. сравнение изначально не совсем корректное. Под каждую задачу — свой инструмент.
Вы пробовали сравнить своё решение с аналогичной выборкой из постгреса?
mapReduce может применяться где угодно в программном коде, не обязательно в mongo.
Мне так кажется, что Ваш «документ описывающий отель» вполне соотносится с реляционной структурой.
«Дело в том, что при обработке большого количества отелей (скажем, 100 тысяч) и при условии, что каждый отель имеет наборы из суммарно 100 значений параметров, то будет произведено 10 миллионов вызовов функции emit()».
Тем то map-reduce и хорош, что им можно map'ать и reduce'ить не один раз а сколько угодно и что-угодно поэтапно. Т.е. в теории РБД Вам было бы совсем не обязательно map'ать все варианты, как в случае с mongo. Вы бы сначала map'нули подходящие подгруппы, за-reduce'или их, затем получили в подгруппах следующие характеристики по иерархии, map'нули их и т.д.
Т.е. не просто map-reduce, а древовидный map-reduce
Мне так кажется, что Ваш «документ описывающий отель» вполне соотносится с реляционной структурой.
«Дело в том, что при обработке большого количества отелей (скажем, 100 тысяч) и при условии, что каждый отель имеет наборы из суммарно 100 значений параметров, то будет произведено 10 миллионов вызовов функции emit()».
Тем то map-reduce и хорош, что им можно map'ать и reduce'ить не один раз а сколько угодно и что-угодно поэтапно. Т.е. в теории РБД Вам было бы совсем не обязательно map'ать все варианты, как в случае с mongo. Вы бы сначала map'нули подходящие подгруппы, за-reduce'или их, затем получили в подгруппах следующие характеристики по иерархии, map'нули их и т.д.
Т.е. не просто map-reduce, а древовидный map-reduce
Ещё один приятный момент — промежуточные результаты можно кэшировать.
Так и есть. В основной БД структура под хранение отелей насчитывает около 30 нормализованных таблиц. И конечно же есть ещё одно решение этой задачи, в котором строится темповая таблица с отелями после фильтрации и с помощью нескольких запросов с группировками можно получить тот же список сервисов с подсчётом количества отелей для построение фильтров. Но решение с Монгой показалось красивее и проще.
Иерархический map-reduce ИМХО хорош, когда нужна сложная аналитика по данным. В нашем случае — простейший подсчёт количества отелей в разрезе сервисов и других характеристик. Решается задача за один проход. Основной вопрос был в том, как именно мапить. При решении задачи в лоб, получалось слишком много значений на этапе маппинга. Оптимизированное решение упиралось в производительность интерпретатора JS в Монге. Как оказалось, серебряной пули не существует :)
Иерархический map-reduce ИМХО хорош, когда нужна сложная аналитика по данным. В нашем случае — простейший подсчёт количества отелей в разрезе сервисов и других характеристик. Решается задача за один проход. Основной вопрос был в том, как именно мапить. При решении задачи в лоб, получалось слишком много значений на этапе маппинга. Оптимизированное решение упиралось в производительность интерпретатора JS в Монге. Как оказалось, серебряной пули не существует :)
Нельзя ли привести ТТХ комплекса, на котором работает Mongo, максимальный достигнутый RPS и примерное кол-во записей об отелях? И какая СУБД используется как основная?
Проект в разработке и поэтому ТТХ могу привести только сервера, на котором альфа развёрнута. Восьмиядерник, 24Гб оперативы и 2 ХДД в рэйде. Из которых Монга сейчас кушает что-то около 200Мб. Рабочих отелей до 5К. Тестировалось всё на 100К. Чистый RPS не замерял, так как считаю это сферическим конём в вакууме. Главный вопрос в том, сколько времени займёт полный цикл получения данных, на сколько активно при этом можно использовать кеш и какая алгоритмическая сложность решения, т.к. всё это добро придётся ещё не один год саппортить. По-факту, в данном случае резалтсет после фильтров по сервисам можно складывать в очень длинный кеш, т.к. данные по отелям редко меняются, а финальный резалтсет после фильтров по цене — в короткий кеш и миксовать их. Самое главное, что после всех этих махинаций страницы на разогретом кеше и с нагрузкой в 100 конкурентных соединений отдаются за 20-30мс и это полностью нас устраивает.
В перспективе будет 3-5 серверов с похожими конфигурациями, но по-большей части они будут использоваться для работы с ценовыми предложениями по отелям. Для начала — шардирование. По мере роста нагрузки будут докидываться слейвы.
Основная СУБД — MariaDb.
В перспективе будет 3-5 серверов с похожими конфигурациями, но по-большей части они будут использоваться для работы с ценовыми предложениями по отелям. Для начала — шардирование. По мере роста нагрузки будут докидываться слейвы.
Основная СУБД — MariaDb.
страницы на разогретом кеше и с нагрузкой в 100 конкурентных соединений отдаются за 20-30мсОтличный результат.
Правильно ли я понимаю, что система заточена под запросы типа «Найди отель в Уганде, с шахматным клубом и поэтессами», а не на «найди мне double на 2 недели за $500 все равно где»?
Интересный опыт конечно, но после фильтрации отелей по стране и городу обычно остаётся не больше 10к строк (в подавляющем случае — до 500). А это можно легко обсчитать и за пределами БД.
Могу немного ошибаться, т.к. работаю в другом отделе, но на ostrovok.ru/ все отели города загружаются в браузер и фильтры затем прекрасно применяются прямо в браузере.
Ну и Array + GIN / GiST индекс в постгресе тоже можно использовать для случаев, когда у строки может быть несколько значений поля одновременно.
Могу немного ошибаться, т.к. работаю в другом отделе, но на ostrovok.ru/ все отели города загружаются в браузер и фильтры затем прекрасно применяются прямо в браузере.
Ну и Array + GIN / GiST индекс в постгресе тоже можно использовать для случаев, когда у строки может быть несколько значений поля одновременно.
Отфильтровать — не проблема. Тут основное — это просчёт за один проход количества отелей, соответствующие каждому из сервисов. И таки да. Можно на С написать демона, который будет держать всю базу отелей с сервисами в памяти и делать обсчёт. :) Вопрос в целесообразности.
Островок — классный проект. Мы попроще :)
Островок — классный проект. Мы попроще :)
Ну я к тому, что демон на C не нужен, когда работаем с 500-1000 строк (количество отелей после фильтрации по стране и городу). Это можно спокойно посчитать и в application сервере (Python/PHP/Ruby etc) и даже непосредственно в браузере. Можно тем же мап-редьюсом на коленке, можно простой хеш-таблицей (псевдокод):
И это возможно благодаря тому, что после фильтрации по стране и городу отсекается большая часть строк. А если нужно вывести все отели страны или вообще все отели, то конечно придется помучиться. Но, как правило, это никому не нужно.
hotels = db.query("SELECT * FROM hotels WHERE country='russia' AND city='moscow';") // вернет меньше 1000 строк
counter = {}
for (hotel in hotels) {
for (amenity in hotel.amenities) {
// здесь amenity это, например строка "wifi", "парковка", "бассейн" или соответствующий числовой id услуги
counter[amenity] += 1
}
}
И это возможно благодаря тому, что после фильтрации по стране и городу отсекается большая часть строк. А если нужно вывести все отели страны или вообще все отели, то конечно придется помучиться. Но, как правило, это никому не нужно.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Простая методика построения фильтров товаров с помощью MongoDb и MapReduce