×Закрыть

Держим 11k req/s

Обычно я пишу технические статьи на хабр, но в связи с последними событиями заиграли патриотические нотки, и я решил сделать исключение. На ДОУ частенько возникали срачи споры про унылость проектов в аутсорсе и безысходность бытия. Мне с этим всегда везло, и я попадал в более-менее интересные проекты.

В большинстве случаев бизнес-идеи продуктов и архитектура проектов закрыты NDA, поэтому на просторах инета трудновато найти что-то интересное и новое, касательно архитектуры каких-то готовых работающих решений. К счастью, мне удалось уговорить нашего заказчика дать добро на разглашение информации о структуре нашего проекта (за исключением имени клиента нашего заказчика =)).

Поэтому представляю Вашему вниманию пост об архитектуре одного рекламного движка.

Около 8 месяцев назад к одному из клиентов Cogniance обратилась одна довольно большая и известная компания. Назовем ее «Х». У компании «Х» уже довольно давно существует бесплатное мобильное приложение с огромной пользовательской базой (на текущий момент — 85 млн активных пользователей). Проблема «Х» была в том, что они никак не монетизировали приложение. Ну и вполне очевидно, что наступил момент, когда появилась необходимость получения прибыли. Какой самый простой и очевидный способ заработать на приложении? Правильно — баннеры. И, как это часто бывает, «Х» захотел свое решение со своим блекджеком и... ну вы поняли.

Требования

Для нас как исполнителей требование выглядело приблизительно так:

Нужен UI, где можно сконфигурировать рекламные компании. Например, показывать баннер туфель девушкам, которые увлекаются музыкой, старше 14 лет в Калифорнии не чаще чем раз в день. «Х» в свою очередь дергает наши сервера с клиентских приложений, запрашивая рекламу для конкретного пользователя, передавая всю имеющуюся информацию для таргетинга. + более-менее real time репорты с инфой о том — кому, сколько и какой рекламы было отдано.

Очевидно, что это десятки страниц спеки, сжатые в 3 предложения. Но вот отдаленно все выглядело как-то так. Отдельные требования были касательно производительности системы:

Request rate: 1000 req/sec
Protocol: https
Response time : 99% < 100ms.
No downtime
Hosting: Amazon

После 3-х месяцев разработки требования по нагрузке в 1000 рек/сек изменились (а как же без этого =)), и появилась цифра в 11000 рек/cек. Из-за чего нам впоследствии пришлось немного сменить вектор развития продукта.

Архитектура

Из спеки сразу стало ясным, что систему можно условно разделить на 3 подсистемы, что мы собственно и сделали:
UI (CRUD + интеграция с сервисами клиента), AdServer (high-load), Reporting (big data).

Разделение на модули на начальном этапе было необходимо как воздух:

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

UI

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

Техн. стек: JS, Spring, Hibernate, Tomcat, MySQL.

Детальное описание UI модуля я опущу, так как в нем все довольно банально, и это обычный CRUD модуль + интеграция с сервисами заказчика. Уверен, что 90% проектов делает такое же, только в разных доменных областях.

Одна из важнейших задач, которая решалась на этапе разработки UI модуля, — как соединить UI модуль с AdServer модулем. Дело в том, что возможных решений было очень много. Ну, например, это проблему можно было бы решить через RMI, RESTfull API, JMS, обычную master-slave репликацию СУБД, распределенный ehCache, распределенные датагриды и еще +100500 разными способами. Лично я остановил свой выбор на master-slave репликации СУБД, так как это решение не требовало кода и выглядело довольно простым. К сожалению, наш заказчик также обладал технической экспертизой. И после обсуждения предложенных вариантов, имея в багаже схожие запущенные проекты (как основной аргумент — это решение уже есть, и оно работает) — клиент настоял на Solr.

Используя Solr, предполагалось убить сразу двух зайцев:

  • Во-первых, все нужные для доставки рекламы данные будут локально храниться на каждом из деливери серверов. Таким образом, повышаем отказоустойчивость системы и уменьшаем время доступа к данным;
  • Во-вторых, всю сложную логику по подбору рекламы для конкретного пользователя можно будет вынести в Solr.

AdServer

Задача модуля — на основе входящего запроса и его параметров подбирать наиболее релевантную рекламу для пользователя в конкретный момент времени.

Техн. стек: Spring, Tomcat, Solr, Redis.

Solr

После 3-х месяцев разработки, базовый функционал был реализован и мы начали первое нагрузочное тестирование. Результат оказался плачевным. Один с1.xLarge сервер смог обрабатывать всего 200 реквестов в секунду при времени ответа приближающемуся к 100 мс.

Быстрый профайлинг сразу позволил обнаружить узкое место — SOLR. Все дело в том, что SOLR — это http сервер, поэтому на каждый реквест от пользователя приходилось делать http запрос на localhost к солру. Что, конечно же, не могло быть дешево. К сожалению, существующий Embedded Solr работал еще хуже. Кеш на уровне приложения помог поднять рейт к 400 рек/сек. Но нас это тоже не устраивало, так как, помимо всего прочего, мы вплотную подошли к требованию времени ответа сервера 99% < 100мс. Это обстоятельство сужало пространство для маневра при добавлении нового функционала, а также накладывало риски в будущем в случае роста индекса:

Index size < 1 Gb - response time 20-100 ms
Index size < 100 Gb - response time 1-10 sec

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

volatile DeliveryData cache;
Cron Job:
DeliveryData tempCache = loadAllDataFromSolr();
cache = tempCache;

Как результат, скорость работы одного сервера выросла до 600 рек/сек c временем ответа ~15ms при LA 4.

No-SQL

В мире веб-рекламы существует такая фича, как frequency capping. Если коротко — это лимит показов одной и той же рекламы для одного и того же пользователя. Например, если вы не хотите, чтобы пользователь видел ваш баннер чаще, чем раз в день, или если Вы хотите, чтобы уже после показанного банера показывался другой, например, со скидкой, в случае, если после показа первого баннера вы не получили клик. Обычно в веб мире эта проблема решается через куки. И подобная инфа хранится на стороне клиента. «Х» почему-то не захотел реализовывать куку на стороне мобильного клиента и переложил эту задачу на нас.

Для нас же это означало хранить фактически всю пользовательскую базу (когда, кому и какая реклама была отдана) и иметь быстрый к ней доступ во время каждого реквеста от пользователя. Нужно было решение, которое бы хранило общее состояние для всех деливери серверов — с максимально коротким временем ответа и максимально возможной производительностью. Идеальная ситуация для no-sql решения.

DynamoDB

Теперь возникла необходимость выбрать нужное решение. Изначально мы решили попробовать DynamoDB. Предполагалось, что мы снизим стоимость AdOps поддержки + по описанию, решение от амазона выглядело очень привлекательно. Первые нагрузочные тесты показали, что DynamoDB очень далек от идеала. Как говорится, я просто оставлю это здесь:

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

Redis

После основательного гуглинга и опыта работы с некоторыми no-sql решениями в прошлом, мы остановили свой выбор на Redis. Редис показывал просто сказочное среднее время ответа — 0.2ms в приватной сети амазона. При этом он вполне себе держал нагрузку в 50к get рек/сек на одной с3.xLarge ноде. Ну и наконец, у редиса есть пачка вкуснейших фич — atomic increments, sets, hashes. Каждой из которых мы нашли применение.

Конечно, как и у каждого решения у редиса есть своя темная сторона:

  • Все хранится в памяти, диск используется только для бекапа и восстановления данных;
  • Кластерное решение еще не готово к продакшену, так как вышло несколько месяцев назад;
  • Для масштабируемости придется использовать sharding. А значит закладывать сразу в архитектуру при разработке;
  • Использование диска влияет на время ответа, так же возможен page swapping, который тоже может увеличить время ответа;
  • Возможна потеря данных.

Несмотря на недостатки, редис полностью подходил для всех наших бизнес задач.

Оптимизации

Несмотря на довольно хороший результат в 600 рек/сек и времени ответа 15ms, было понятно, что можно выжать больше. Оптимизировалось все — начиная от банальных очевидных вещей, о которых стыдно писать, и заканчивая некоторыми алгоритмами. Оптимизация кода в яве — это отдельная тема для поста. Собственно, несколько таких постов я уже написал на хабре. Поэтому не буду повторятся, лишь оставлю на почитать интересующихся — небольшие трюки и еще, сколько стоит выделить объект, одна маленькая оптимизация, оптимизируем еще, изменения в String.

В результате оптимизаций мы дошли до 1000 рек/сек и времени овтета 1.2 ms при LA 4 на с3.xLarge. После всех наших стараний узким местом стал редис, который в среднем на 1 пользовательский запрос выполнял 1.5 реквеста по сети, а также использовал синхронизацию при обращении к пулу коннекшенов. На что уходило ~50-60% времени обработки запроса.

Reporting

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

Техн. стек: Hadoop (Cascading), MySQL.

Каждый ответ сервера клиенту логируется. Во время первого же тестирования было обнаружено, что генерится довольно большое количество логов. Одна запись в лог была в среднем ~700 байт. Следовательно, при нагрузке в 11k рек/сек в секунду генерировалось около 7 мб логов в секунду или около 25 ГБ в час. Структура лога:

{
"uid":"test", 
"platform":"android", 
"app":"xxx", 
"ts":1375952275223, 
"adId":1, 
"education":"Some-Highschool-or-less", 
"type":"new", 
"sh":1280, 
"appver":"6.4.34", 
"country":"AU", 
"time":"Sat, 03 August 2013 10:30:39 +0200", 
"deviceGroup":7, 
"rid":"fc389d966438478e9554ed15d27713f51", 
"responseCode":200, 
"event":"ad", 
"device":"N95", 
"sw":768, 
"ageGroup":"18-24", 
"preferences":["beer","girls"]
}

Hadoop

Было ясно, что при генерации 25 ГБ логов в час мы никак не можем напрямую хранить их в базе, так как это стоило бы не дешево. Необходимо было как-то сократить объемы. К счастью, заказчик точно знал, какого рода репорты ему нужны. Поэтому из имеющихся полей лога:

device, os, osVer, sreenWidth, screenHeight, country, region, city, carrier, advertisingId, preferences, gender, age, income, sector, company, language, ...

мы определили набор таблиц необходимых клиенту, например:

Geo table:
Country, City, Region, CampaignId, Date, counters;
Device table:
Device, Carrier, Platform, CampaignId, Date, counters;
Uniques table:
CampaignId, UID
…

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

Да, агрегация входных данных по определенной совокупности полей приводит к потере большей части информации (после агрегации нельзя узнать например, сколько человек с айфоном посмотрели рекламу в Сан-Франциско), но бизнес задачу решение решало, а значит устраивало и нас (хотя кое-какие сырые данные мы все же храним =)). В результате этого подхода объем данных удалось сократить на 3 порядка, а именно к 40ГБ данных в месяц. Которые ну никак не страшны даже самым хиленьким СУБД.

У внимательного читателя наверняка возник вопрос: «А зачем использовать хадуп?». Вопрос, на самом деле, очень правильный и интересный. Дело в том, что объем в 25 ГБ в час — это совсем не много (для нашей задачи), и даже если надо обработать несколько десятков таких файлов — написанный на коленке агрегатор справится с этой задачей очень быстро. Но в нашем случае хадуп выполняет не только агрегацию, но и определенную валидацию, которая является довольно ресурсоемкой (к сожалению, эта часть закрыта NDA). Собственно из-за этой валидации нам и необходимо было решение, которое легко можно было масштабировать в случае необходимости, что на коленке реализовать уже гораздо сложнее.

Заключение

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

Сейчас, оглядываясь назад, если бы я был в тех же условиях сегодня, я б однозначно настоял на полном избавлении от Solr. Solr для нас оказался явно overengineering solution. Не имея в проекте солр, мы вполне смогли бы избавится и от Tomcat, банально заменив его HttpServer, что упростило бы процес деплоя и написания интеграционных тестов. Ну и касательно репортинга — я бы уже наверное посмотрел в сторону более перспективных технологий, а именно — Spark, Storm, Redshift. У них, конечно, своя специфика, но решить нашу проблему можно и на них. И вполне возможно, что получилось бы дешевле.

Спасибо всем, кто дочитал. Буду рад любой конструктивной критике. Надеюсь пост вам понравился.

Лучшие комментарии пропустить

— “Hello, XXX. I want to share our experience regarding development of project with no details about customer, code, algorithms. Just architecture, problems and our solutions are you ok with that?”
— “Sure. Do it.”

=))).

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

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

Дякую за статтю. Хабр хабром, але пора вже щось своє розвивати, скільки ж ми будемо за іншими бігти. Адже все для цього в нас є. )

Обычно я пишу технические статьи на хабр, но в связи с последними событиями заиграли патриотические нотки, и я решил сделать исключение. На ДОУ частенько возникали срачи споры про унылость проектов в аутсорсе и безысходность бытия. Мне с этим всегда везло, и я попадал в более-менее интересные проекты.
А что — хорошая идея всем украинским хабровцем и опеннетовцам писать сюда!
А ТО как писать код — так украинские программисты, а как называться — то русские программисты... Еще завтра прийдут путлеровцы с тезой «защиты русских программистов»!
РЕСПЕКТ АВТОРУ!

Если будет много желающих почитать как мы делали нагрузочное тестирование — можно будет сделать и такую статью.

120 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Кафку не розглядали як ядро? Думаю чудово б підійшло.
Дякую за статтю.

Кафку не розглядали як ядро?

Ні.

Дима, я вот сейчас немного почитую литературу по поиску, и у меня возник следующий вопрос, который относится к статье.
Каким образом у Вас в проекте была решена проблема определения контекста для рекламы?

На сколько я понимаю, то это находится за псевдокодом DeliveryData. Но мне всё же больше интересна прикладная часть решения.

Я вот немного полистав учебники по поиску сделала вывод, что решить эту проблему можно было бы с помощью term vectors. Это решение используется в реализации поиска подобных документов в Lucene/Solr/Elasticsearch и более извесное под названием MoreLikeThis. В учебнике Lucene in Action 2nd edition в разделе 5.9.2 What category? term vectors описываются как раз для решения проблемы определения категории (в нашем случае это можно считать контекстом) для книги исходя только из её названия.

term vectors
Они решают более широкий круг задач. Гораздо больший нежели нужно нам. У нас же все просто. Пришел город=киев,пол=мужской — отфильтровали всю рекламу у которой таргетинг по горду = (киев или любой) и пол = (мужской или любой). Упрощенно — как-то так. Наворотов поверх этих кейсов много, но в целом решается так.

Дійсно цікавий та корисний матеріал. Дякую!

Для репортінга я думаю замість EMR можна Redshift. Буде дешевше в плані розробки і в плані утримання. Solr в мене асоціюється з enterprise, дорого і повільно. EllasticSearch і швидший і з розгортанням в хмарі зручніший.

И в догонку: прокомментируйте «свой Hadoop» vs. EMR

Лично мне EMR очень нравится. Быстро, удобно, просто. Один из немногих сервисов амазона, которым я полностью доволен =).

Еще один вопрос из серии железка/облако: считается, что в случае с облаком можно сильно сэкономить на дорогом админе :) ну, будут devops, но без железяк, постоянных поездок в датацентры к своим стойкам и прочего. Подтвердилось ли у вас?

Амазон дороже, по крайней мере с нашей инфрастуктурой. Но амазон сильно снижает человеческий фактор.

Спасибо за статью.
По поводу «разгона» Redis — он реплицируется плюс можно добавить шардинг по ключам, для этого даже есть готовые решения.
Кроме того, как уже писали ниже — Redis отлично справляется с задачами аггрегации данных «на лету» (с помощью инкрементов).
P.S. а RTB вы поддерживаете?

P.S. а RTB вы поддерживаете?
Нет. У нас 1 паблишер и он эксклюзивно продает каждому отдельному адвертайзеру показы/клики/инсталы. RTB есть на другом проекте.

Дима, а могли бы Вы пожалуйста в своей статье добавить якоря к заголовкам (<a name="redis"/> например), чтоб было удобно ссылаться на определенные части Вашей статьи?

Спасибо, Серёжа. Мне, кстати, стало открытием сегодня, что кроме <a name="anchor"><h1>Header</h1></a> можно ещё использовать <h1 id=”anchor”>Header</h1> для обозначения места под якорь (см., например, Add Anchor Tags To Jump To Specific Location On A Page). Ответ крылся в спецификациях HTML 4 / HTML 5:

Destination anchors in HTML documents may be specified either by the A element (naming it with the name attribute), or by any other element (naming with the id attribute).

Дима, отталкиваясь от вопроса Никиты у меня тоже появился вопрос:
А проводили ли Вы нагрузочное тестирование вашего решения в разные часовые промежутки времени?

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

Да, было нагрузочное тестирование на протяжении нескольких дней. Скажу даже больше — нагрузочное проводилось при 20к рек/сек.

Тоесть система не просидала за время тестирования, верно?

Почему именно амазоновский VPS, а не железячное решение?

Железяки не быстро масштабируются.

Если производительность железки покрывает потребности с большим запасом время когда потребность в масштабировании может быть весьма отдаленным.

Но когда наступает потребность в масштабировании закупка железяки занимает недопустимо долгое время. Elastic Cloud это Elastic Cloud.

У клиента были раньше проколы связанные с администрированием серверов/бекапами/дата центром и т. д. Поэтому в этот раз решили максимально уменьшить человеческий фактор.

Если не NDA сколько обходится такое кол-во машинок которое предполагается на 11К запросов ?

Могу сказать только, что дорого =). Свое железо на порядок дешевле.

Могу сказать только, что дорого =).
Я знаю, что дорого и что железки дешевле, но любопытно какой у вас порядок получился. Скажем само соотношение 1 к 10 не сильно дает представление потому как 1000 и 100 баксов на таком маштабе не разница, а вот если 10К и 1К то уже не так однозначно.

Извините, но эту инфу я не могу предоставить.

Что касается репортинга, не смотрели в сторону Vertica?

Смотрели (юзается на другом проекте в проде). Очень дорого. Решение себя не оправдывает.

А в сторону Druid (druid.io) или Apache Drill? Мы использовали Друид — сжатие данных просто поражает, после ~5Гб статистики в день в Монге на Друиде историческая статистика стала исчисляться десятками мегабайт в день, при этом запросы в районе 10-100 мс как на историческую статистику, так и на риал-тайм. Запросы можно делать по любым полям и с агригацией,но для этого нужно внести эти параметры в структуру датасорса (т.е. нужно заранее предусмотреть по каким полям может быть агрегация и внести их в конфигурацию).

Не слышал про друид. Гляну. Спс

Спасибо большое. Действительно интересный опыт и статья.

Якщо вже патріотично, то де Українська мова? ;)

Про redis — приготуйте його через сокет, а не через TCP/IP — отримаєте необхідний час відгуку
Замість solr краще мабуть elasticsearch — хоча я особисто не порівнював

Логи в redis писати не думали?
Якщо великий об’єм даних і всі вони потрібні — це значить помилка проектування і архітектури, а значить тут можна реально робити тонни оптимізації.

Ps. Сервер з 512gb оперативки не така і проблема, і якщо це дорого для замовника — архітектуру можна міняти

Оптимізація лога
було

{
“uid”:"test“,
“platform”:"android“,
“app”:"xxx“,
“ts”:1375952275223,
“adId”:1,
“education”:"Some-Highschool-or-less“,
“type”:"new“,
“sh”:1280,
“appver”:"6.4.34“,
“country”:"AU“,
“time”:"Sat, 03 August 2013 10:30:39 +0200“,
“deviceGroup”:7,
“rid”:"fc389d966438478e9554ed15d27713f51“,
“responseCode”:200,
“event”:"ad“,
“device”:"N95“,
“sw”:768,
“ageGroup”:"18-24“,
“preferences”:["beer","girls“]
}
має стати
{
“u”:"test“,
“pl”:$platformID,
“app”:$appid,
“ts”:1375952275223,
“adId”:1,
“ed”:$edID,
“ty”:"new“,
“sh”:1280,
“av”:"6.4.34“,
“cntry”:"AU“,
“t”:$timestamp,
“dev”:$groupid,
“rid”:$crc32,
“resp”:200,
“ev”:"ad“,
“device”:"N95“,
“sw”:768,
“age”:$groupid,
“pref”:[$id1,$id2]
}

+ всі назви ключів зробити 1-2 буквенні, або взагалі забрати із структури — просто одномірний масив про ключі якого аплікуха знає. Це дозволить зменшити логи разів в 5.

Це дозволить зменшити логи разів в 5.
Можна і в 10 разів, якщо пакувати кріо і гзіпити. Але загальний виграш — ~1-2% на 1 реквесті. Ми свідомо залишили цю частину, так як великого виграшу нема, натомість ми отримуєм швидкий доступ до стану системи. Наприклад, якщо обробка одного реквеста фейлиться, скажімо з статусом 500, то ми швидко грепаєм потрібний сервер і отримуєм інпут з яким виникли проблеми (якщо логи аппи не дозволили цього зробити).

Дима, а как часто у Вас логи ротейтятся? Все таки ~1-2% от 25 GB/h = 250 MB/h, что только за сутки может составить ~6 GB на текущей структуре лог сообщений.

Дима, а как часто у Вас логи ротейтятся?
Раз в час.
Все таки ~1-2% от 25 GB/h = 250 MB/h, что только за сутки может составить ~6 GB на текущей структуре лог сообщений.
Тут проблемы нету. У нас не хватает в первую очередь CPU. Потому оптимизировать работу с диском нету особого смысла.
Тут проблемы нету. У нас не хватает в первую очередь CPU. Потому оптимизировать работу с диском нету особого смысла.

Ну по Вашему решению, на сколько я поняла, проблему с CPU на Hadoop’е можно проще всего решить путём горизонтального расширения (добавления новых узлов в существующий кластер), верно?

По поводу оптимизации CPU еще хотела бы добавить, что на сериализацию и десериализацию объектов тоже тратится CPU время, и это может быть ощутимо при больших объемах данных, поэтому идея Андрея по поводу уменьшения размера сообщений может иметь сенс и в случае с оптимизацией не только диска, но и CPU. Что думаете, Дима, по этому поводу?

Ну по Вашему решению, на сколько я поняла, проблему с CPU на Hadoop’е можно проще всего решить путём горизонтального расширения (добавления новых узлов в существующий кластер), верно?
В хадупе как раз основная проблема в дисковом IO и сетевом IO. Но там у нас все ужато и пережато.
По поводу оптимизации CPU еще хотела бы добавить, что на сериализацию и десериализацию объектов тоже тратится CPU время
Да, тратится.
и это может быть ощутимо при больших объемах данных,
Ну ок. Возьмем наш случай. Средний лог 700 байт. При нагрузке 1000 рек/сек на одну машину имеем 684 кб логов в сек. Сколько вы сэкономите если надо будет записать не 684кб, а скажем 200кб? При том что скорость сериализаторов/гзиперов порядка 20-100 МБ/с.
поэтому идея Андрея по поводу уменьшения размера сообщений может иметь сенс и в случае с оптимизацией не только диска, но и CPU. Что думаете, Дима, по этому поводу?
Я не спорю, я же написал, что это позволит сэкономить 1-2% на одном реквесте. Но быстрый доступ к данным нам важнее, чем эти 1-2%.

Мій варіант був не архівація, а оптимізація даних. Яка грепається без проблем. Архівовані ж дані грепати — проблемно і накладно, з цим погоджуся.

Мій варіант був не архівація, а оптимізація даних. Яка грепається без проблем.
Так, згоден.

Кстати есть ещё формат, разработанный как раз для целей экономии размера сообщений, называется он Bencode и используется в BitTorrent сетях для обмена сообщениями.

Дякую за bencode!!!

Про redis — приготуйте його через сокет, а не через TCP/IP — отримаєте необхідний час відгуку

Андрей, здесь наверное речь о unix domain sockets, верно?

Спасибо, Андрей, нашла на этот счет интересную статью: How fast is Redis раздел Factors impacting Redis performance.

Так. Вони.
При великих навантаженнях потрібно опції ядра накручувати також

Логи в redis писати не думали?
Вийде дорожче ніж на диск.
Якщо великий об’єм даних і всі вони потрібні — це значить помилка проектування і архітектури
=))).
приготуйте його через сокет, а не через TCP/IP
А есть еще какие-то сокеты? Ну кроме конечно UDP?

Класна стаття — дякую. Трохи змінило мої стереотипи на рахунок кваліфікації українських аутсорсерів.

Не хватает кнопки «В избранное» :)

Обычно я пишу технические статьи на хабр, но в связи с последними событиями заиграли патриотические нотки, и я решил сделать исключение. На ДОУ частенько возникали срачи споры про унылость проектов в аутсорсе и безысходность бытия. Мне с этим всегда везло, и я попадал в более-менее интересные проекты.
А что — хорошая идея всем украинским хабровцем и опеннетовцам писать сюда!
А ТО как писать код — так украинские программисты, а как называться — то русские программисты... Еще завтра прийдут путлеровцы с тезой «защиты русских программистов»!
РЕСПЕКТ АВТОРУ!
В конце концов было принято решение отказаться от выборки из солра. И вся логика выборки перекочевала на уровень приложения. SOLR, тем не менее, остался — исключительно как хранилище delivery индекса. Весь код работы с солром сводился к следующему:
volatile DeliveryData cache; Cron Job: DeliveryData tempCache = loadAllDataFromSolr(); cache = tempCache;

Дима, я хочу еще узнать у Вас:

  • Есть ли смысл в том, чтобы Solr использовать только как хранилище данных и выгребать из него все данные за один присест в кеш приложения без фильтров и какие преимущества в этом решении?
  • delivery индекс == поисковому индексу Solr?
  • Какие качества есть у структуры DeliveryData и на чем построен механизм работы с этой структурой (какие технологии используются и для каких целей), что позволяет Вам добиться лучшего результата выборки (качество результатов и скорость их получения), чем в случае с полнотекстовым поиском на Solr?
Есть ли смысл в том, чтобы Solr использовать только как хранилище данных
Нету. Мы уже выкатили продукт в бету, потому добро на переделывание уже никто не дал. Да и проблем после выноса логики в приложение уже не было.
delivery индекс == поисковому индексу Solr?
Нет, по сути мы делаем «select *» и после выборки строим свои структуры для быстрого поиска (в большинстве случаев хеш-мапы).
Какие качества есть у структуры DeliveryData
DeliveryData — это псевдокод, его не существует. Выборка чуть сложнее, но в целом сводится к этому куску кода. Это для примера.
что позволяет Вам добиться лучшего результата выборки (качество результатов и скорость их получения), чем в случае с полнотекстовым поиском на Solr?
Ну тут все просто. Солр использует диск, а мы — нет. + запрос к Солру это сетевые задержки, да еще и http. Если бы использовали lucene, тогда все свелось бы к медленному диску.

А почему не рассматривали неблокирующие асинхронные событийные фреймворки, например Netty? Думаю результат был бы на порядок лучше

Максим, а в каком месте Вы считаете Netty подходит лучше и почему?

P.S. Что такое NIO и NIO2 я знаю, поэтому интересует ответ в контексте описанного автором решения.

1. для снижение кол-ва памяти, которую съедает java на обработку каждого коннекта
2. для снижение кол-ва переключений контекста (и соответственно LA) на обработку каждого потока
3. неблокирующая запись в логи (можно даже не дожидаться ответа от файловой системы — я так понимаю потери тут допускаются).
4. можно было бы выкинуть спринг и томкат на этапе приема клиентских данных апликейшн-сервером от мобильного приложения

Из того, где это можно применить у нас — это редис и запись логов. По редису — есть клиентская либа написанная на NIO, но по бенчмаркам она хуже. По логам — так как диск у нас почти не юзается, то и особого смысла нету.

Організаційне питання: як вмовили кастомера відкрити архітектуру для громадськості?

— “Hello, XXX. I want to share our experience regarding development of project with no details about customer, code, algorithms. Just architecture, problems and our solutions are you ok with that?”
— “Sure. Do it.”

=))).

Отличная статья!!!

youtu.be/0TsnCR6JMIM Вот у ребят из AdRoll циферки. Советую на досуге уделить 30 минут.

Ребята из AdRoll на Эрланге делали, поэтому все так хорошо.

все так хорошо.
500 инстансов с3.4xLarge для 50 млрд рек в день для RTB? Как-то печально.

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

Хотелось бы больше подробностей, о возникших проблемах
Ну все так сразу и не выложишь, надо время. Из того, что вспоминается сразу :
1) Мы юзаем не чистый редис, а ElastiCache. Некоторые одиночные запросы отпадают по таймауту (SO_TIMEOUT = 50ms). Проблему до сих пор не решили. Вот просто бывает так, что отваливается какой-то реквест по таймауту (даже без нагрузки). Я уже даже общался к Сальвадору по этому поводу. Пока все подозрения на Амазон, так как там закрытая среда.
2) Была проблема роста данных в редисе. Так как редис хранит все в памяти, то нужно было использовать или шардинг или уменьшить объем. Ужали данные (kryo + gzip) — уменьшили объем в 8 раз. И реализовали шардинг для редиса на будущее.
3) Постоянно сталкивались с лимитами на амазоне — Package per Second limit, network limit, balancer problem и т.д. Уже всего и не вспомню.
использовали ли concurrency
Ну мы старались следовать главному правилу многопоточности — не использовать многопоточность. К сожалению, без нее никуда. Если говорить о нагруженных местах, то можно выделить такие по нагруженности:
1) JedisPool.getResource(), JedisPool.returnRsource();
2) OutputSreamWrite.write();
3) UUID.randomUUID;

А с IO как-то решали? Или у вас привязка к томкету?

1) Мы юзаем не чистый редис, а ElastiCache. Некоторые одиночные запросы отпадают по таймауту (SO_TIMEOUT = 50ms). Проблему до сих пор не решили. Вот просто бывает так, что отваливается какой-то реквест по таймауту (даже без нагрузки). Я уже даже общался к Сальвадору по этому поводу. Пока все подозрения на Амазон, так как там закрытая среда.
—это «стандартная» его проблема. Советую посмотреть Cassandra. Там они как раз изначально «затачивались» под то, что Вам необходимо...

Вот представляете — каждый Ваш AdServer имеет по своей ноде Сassandra и они нормально между собой строят «мягкий кластер»...

Советую посмотреть Cassandra.
Смотрели. Время ответа большое. И нагрузку держит гораздо хуже.
это «стандартная» его проблема
Это проблема ElastiCache, а не редиса.

Дмитрий, спасибо за информацию. Интересно сравнить решения при построении ad servers.

11k rps — это показатель относительный, при 10 x с3.xLarge, как я понимаю. Но у Вас же и autoscaling задействован. Каковы thresholds?

Сейчас, оглядываясь назад, если бы я был в тех же условиях сегодня, я б однозначно настоял на полном избавлении от Solr. Solr для нас оказался явно overengineering solution.
Вы бы могли смело заменить Solr на Redis, который также поддерживает master-slave replication.

Еще интересует, как у Вас реализован XDR?

Саша, а под XDR имеется ввиду External Data Representation?

11k rps — это показатель относительный, при 10 x с3.xLarge, как я понимаю.
Да относительный.
Но у Вас же и autoscaling задействован.
Да.
Каковы thresholds?
LA 3 для одного деливери сервера (с3.xLarge). Сервер, конечно же, может держать и 1500 рек/сек, просто при такой нагрузке время ответа уже растет не линейно и в пределах 60-80мс и много запросов выпрыгивают за 100мс.
Вы бы могли смело заменить Solr на Redis, который также поддерживает master-slave replication.
Да конечно, решений много.
Еще интересует, как у Вас реализован XDR?
В другой географической зоне — резервные сервера, на которые должен переключатся трафик в случае чего.

Спасибо.

А как часто происходит инвалидация кэша?

DeliveryData cache
Интервал либо какой-то сигнал/проверка?

Спрашивал насчет XDR, т.к. рано или поздно нужно будет иметь приемлемое latency на всех aws regions, а не только на соседней availability zone. А в этом сложном вопросе с user-data в Redis, как подсказывает опыт, далеко не уйти.

А в этом сложном вопросе с user-data в Redis, как подсказывает опыт, далеко не уйти.

Саша, а что тут плохого в решении с user-data в Redis, что с этим далеко не уйти?

В комментарии ниже Дмитрий описал текущий принцип шардирования в Redis. Это будет работать какое-то время. А как же replication factor, scalability? Вручную поддерживать такого рода кластер будет тяжеловато. А в контексте XDR — нереально.
Здесь нужен consistent-hashing. Aerospike/Couchbase/Dynamo etc.

Здесь нужен consistent-hashing.
Зачем. В доке по редису есть приемлемые решения — redis.io/...cs/partitioning
А как часто происходит инвалидация кэша?
1 мин.
иметь приемлемое latency на всех aws regions,
Рабочие сервера только в одном регионе. В случае падения, трафик перебрасывается на другой регион.
А в этом сложном вопросе с user-data в Redis, как подсказывает опыт, далеко не уйти.
А в чем проблема?

Предположим, у Вас много user-data в кластере в одном регионе. 50GB in RAM. И вот бизнес решает проинтегрироваться с кем-то и получать трафик из иных регионов. Это чревато большим latency. Для уменьшения latency Вы делаете multiregional deploy. Соответственно и доступ к собранной актуальной user-data тоже нужно обеспечить в новых регионах. Так же быстро отвечать < 10 ms. Нужна XDR, которую Redis не умеет.

Еще интересует, как у Вас реализован XDR?

Саша, а по отношению к какой части решения Вас интересует XDR?

Я насчитала следующие решения: Solr, Redis, Hadoop, MySQL, к которым, если я правильно поняла этот термин, XDR применим.

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

Вы не можете нормально синхронизировать данные используя Redis. При репликации он делает snapshot всех данных и посылает их на slave.
MySQL не поддерживает синхронной репликации, так что в этом случае тоже далеко не уедешь.
И я думаю что не стоит использовать Solr только для этих целей.

MySQL не поддерживает синхронной репликации, так что в этом случае тоже далеко не уедешь.
Galera?

Начиная с 2.8 Redis уже имеет Partial resynchronization

Кстати еще вопрос, ребята, правильно ли я понимаю, что и сама площадка (хостинг Amazon, например) может обеспечивать XDR?

Кстати еще вопрос, ребята, правильно ли я понимаю, что и сама площадка (хостинг Amazon, например) может обеспечивать XDR?

Поняла правильно, так как это является одним из преимуществ Amazon AWS. Об этом на примере конфигурации Couchbase Server’а показано здесь: Cross Data Center Replication — A step-by-step guide for Amazon AWS и Couchbase Server 2.0 and Cross Data Center Replication (XDCR). Для понимания, что такое Regions и Availability Zones посмотрела сюда: Regions and Availability Zones.

Если я в чём-то ошибаюсь, дайте пожалуйста знать.

Не могли бы Вы рассказать чуть подробнее о Solr:
1. Количество проиндексированных атрибутов в документе?
2. Порядок количества документов в тестовой базе и на продакшене?
3. Количество шард, метод шардирования и суммарные характеристики железа?
Спасибо!

1. Количество проиндексированных атрибутов в документе?
8 разных доков, ~50 полей в самом большом.
2. Порядок количества документов в тестовой базе и на продакшене?
В тестовой базе индекс был около 200мб. На проде сейчас несколько десятков мегабайт. По докам не скажу, так как не знаю.
3. Количество шард, метод шардирования и суммарные характеристики железа?
Всего серверов не мало. Но я не могу этого разглашать. В основном юзаем с3.2xLarge. Шардинг у нас только для редиса, но он пока лежит в отдельной ветке и там юзаем банальный hashcode() % shardsNumber. Пока редис справляется без шардов.

Ситуацию понял. Спасибо за подробности!
Действительно странное требование использовать Solr на таких в общем-то небольших датасетах и при таких требованиях ко времени ответа.

Поверх Hadoop используется что-то вроде Hive или чистый хардкор с map-reduce? :)
На сколько быстро строятся отчёты на таком объёме данных? Агрегаты считаются или только количественные характеристики?

Поверх Hadoop используется что-то вроде Hive или чистый хардкор с map-reduce? :)
Юзаем cascading (ООП над map-reduce). Но, лично мне, он не очень нравится. В терминах map-reduce оперировать легче. Правда, в случае, если результат одной джобы должен быть отработан другой — то cascading очень упрощает вещи.
На сколько быстро строятся отчёты на таком объёме данных?
В пиковый час работа хадуп кластера из 4-х нод с3.XLarge занимает 45 мин. Инсерт в базу еще минут 15. То есть обычно до часа.
Агрегаты считаются или только количественные характеристики?
Не очень понял вопрос.
Агрегаты считаются или только количественные характеристики?
Не очень понял вопрос.
В отчётах участвуют min, max, отклонение и т.п. по срезам или только подсчёт чего-либо (показы, просмотры, etc)?
Я сейчас в процессе выбора БД для аналитических отчётов и Хадуп не подошёл из-за требования «necessarily realtime». Но в любом случае, очень интересны кейсы реального использования.
только подсчёт чего-либо (показы, просмотры, etc)?
Только это.
Я сейчас в процессе выбора БД для аналитических отчётов и Хадуп не подошёл из-за требования «necessarily realtime».
Ну так хадуп это и не БД =). Если «necessarily realtime» — это кликнул по банеру и через 5 сек увидел в репортах, то я бы рекомендовал Storm. Но сделать fraud detection на нем уже будет проблематично/дорого. Без контекста трудно что-то подсказать.

Да понятно, что не БД :) Рассматривались HBase + Phoenix, Hive + Shark и прочие вариации на эту же тему. В итоге, чувствую, в качестве промежуточного решения придётся накручивать что-то вроде map-reduce прямо поверху Solr, используя его как поставщика данных под запрос. А по мере роста переходить на одну из относительно бюджетных columnar db: InfiniDB, CitusDB, etc. О Redshift и ещё более дорогих хранилищах приходится только мечтать ))
Контекст светить не могу, но хранилище должно иметь возможность апдейтить уже добавленные документы (удаление-вставка), хранить историю за годы и с приемлемым временем построения отчётов в произвольных срезах (с вложенными группировками). Объём — 10 миллиардов документов сразу (~10 терабайт сырых данных) и до 100+ в будущем. Допускается запаздывание до суток и потеря некоторого незначительного количества документов (в виду объёма данных на агрегаты они не сильно повлияют). Естественно, крайне желательно чтобы стоимость владения решением была равна стоимости аренды серверов :)

О Redshift и ещё более дорогих хранилищах приходится только мечтать ))
Ну он не такой уже и дорогой. Тут нада считать конкретно на ваших данных — выгодно или нет.

Ну и отписывайтесь, как найдете решение =).

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

Я ведь правильно поняла, что платформа полнотекстового поиска использовалась для обслуживания запросов на разных языках?

спасибо! очень интересно

Дима, у меня есть несколько вопросов:

  • Что подразумевается под термином SLA из предложения:
    средний SLA — 0.2ms в приватной сети амазона
    ?
  • Под page swapping’ом подразумевается использования файла подкачки?
  • А чем бы Вы заменили релевантный поиск избавившись от Solr?
  • А чем избавление от Tomcat помогло бы Вам упростить процесс деплоя?

По поводу интеграционных тестов, то к Maven’у есть плагины, позволяющие поднять полноценный сервер на Jetty/Tomcat во время выполнения фазы pre-integration-test Maven’ом, например, и прогонять интеграционные тесты. Что думаете по этому решению, Дима?

Что подразумевается под термином SLA из предложения:
То я заговорился, подразумевалось среднее время ответа (response time). Просто для нас время ответа — важнейшаяя метрика, так как прописана в SLA.
Под page swapping’ом подразумевается использования файла подкачки?
Да, с большими объемами оперативки (наш случай), операционка может сбрасывать страницы памяти на диск. Что, конечно же, влияет на время ответа.
А чем бы Вы заменили релевантный поиск избавившись от Solr?
Тут мы его не используем. Всю логику выборки мы сделали на яве. Если речь о полнотекстовом поиске, то Solr/Sphinx/ElasticSearch вполне подойдут. (это из того, что я знаю).
А чем избавление от Tomcat помогло бы Вам упростить процесс деплоя?
Ну банально не надо сетапить томкат на каждом из серверов, не надо паковать в образ, не знать где он находится и т.д. По мелочам, но наберется нормально.
По поводу интеграционных тестов, то к Maven’у есть плагины, позволяющие поднять полноценный сервер на Jetty/Tomcat во время выполнения фазы pre-integration-test Maven’ом, например, и прогонять интеграционные тесты.
Да, можно. Но зачем, если можно сделать проще =).

Спасибо за статью, познавательно. Присутствовал на конференции BigData (Cogniance) пару месяцев назад, где Дима делился успехами своей команды. С тех пор остался вопрос про целесообразность Hadoop’а на проекте, на который сейчас получил ответ.

Было бы еще интересно почитать как вы замеряете производительность?

Во время нагрузочного тестирования используем Tsung. С ним мониторим состояние всего кластера. Для мониторинга поведения отдельных деливери серверов используем — codahale metrics — metrics.codahale.com

Если будет много желающих почитать как мы делали нагрузочное тестирование — можно будет сделать и такую статью.

Я очень хочу и такую статью увидеть, Геннадий!

Можете использовать loader.io (один из проектов для нашего клиента) На бесплатном акаунте дает до 10К (только не нужно сразу 10К брать, а то потом многие удивляются что response time — 0 на графике, а он оказывается слег под нагрузкой)

Можете использовать loader.io
Не можем. Слишком примитивный сервис. нНльзя написать сложный тест (например, в зависимости от ответа послать тот или иной реквест). Это не говоря уже о том — чтобы вызвать 2 урлы уже надо платить 100 у.е.

На бесплатном Вам дается 3 урла — monosnap.com/...fH81ilRd8b0iifr А по поводу решения по ответам — работа ведется (тут в основном проблема с интерфейсом — заказчик не хочет еще больше перегружать интерфейс создания теста), динамические запросы с разными хедерами и request body уже есть.

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

Я проглянул и так и не понял, можно ли менять значения хидеров подставляя их из словаря для каждого новго реквеста?

Да, можно. В словаре названия ключей и их значения. Далее используете {{ключ}} в headers, params, post body, url. Ну и там есть чтение параметров через vars и передача дальше в следующий запрос (Variables — там пример взятие хедера с предыдущего запроса и использование в следующем). А вот системы перехода на разные урлы в зависимости от ответа предыдущего запроса еще нет (ветвление нужно еще как то на графике показывать).

Хм, тогда не плохо. А что на бекенде крутится, если не секрет?

Самописное на C. Ничего пока лучше не справилось с заданой нагрузкой (мы пробовали Node.js, Ruby, Python, Erlang), да и низкий уровень к железу очень помогает (кроме удобства написания кода — тут сахара мало).

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

Дякую за статтю. Хабр хабром, але пора вже щось своє розвивати, скільки ж ми будемо за іншими бігти. Адже все для цього в нас є. )

Спасибо! Было интересно!

Класс, спасибо, Дима!

Подписаться на комментарии