Когда участники HighLoad++ пришли на доклад Александра Крашенинникова, они надеялись услышать про обработку 1 600 000 событий в секунду. Ожидания не оправдались… Потому что во время подготовки к выступлению эта цифра улетела до 1 800 000 — так, на HighLoad++ реальность превосходит ожидания.
3 года назад Александр рассказывал, как в Badoo построили масштабируемую систему near-realtime обработки событий. С тех пор она эволюционировала, в процессе росли объёмы, приходилось решать задачи масштабирования и отказоустойчивости, а в определённый момент потребовались радикальные меры — смена технологического стека.
![](https://habrastorage.org/r/w780q1/webt/q8/s-/cq/q8s-cqlxfrv8abkdotnudzeq1u8.jpeg)
Из расшифровки вы узнаете, как в Badoo заменили связку Spark + Hadoop на ClickHouse, в 3 раза сэкономили железо и увеличили нагрузку в 6 раз, зачем и какими средствами собирать статистику в проекте, и что с этими данными потом делать.
О спикере: Александр Крашенинников (alexkrash) — Head of Data Engineering в Badoo. Занимается BI-инфраструктурой, масштабированием под нагрузки, руководит командами, которые строят инфраструктуру обработки данных. Обожает всё распределённое: Hadoop, Spark, ClickHouse. Уверен, что классные распределенные системы можно готовить из OpenSource.
Если у нас нет данных, мы слепы и не можем управлять своим проектом. Именно поэтому нам нужна статистика — для мониторинга жизнеспособности проекта. Мы, как инженеры, должны стремиться к улучшению наших продуктов, а если хочешь улучшить — измерь. Этого девиза я придерживаюсь в работе. В первую очередь, наша цель — это польза для бизнеса. Статистика дает ответы на вопросы бизнеса. Технические метрики — это технические метрики, но бизнес тоже заинтересован в показателях, и их тоже надо считать.
Жизненный цикл статистики я определяю 4 пунктами, про каждый из которых будем разговаривать отдельно.
![](https://habrastorage.org/r/w780q1/webt/pp/kb/p5/ppkbp5uw_stwtzdgmakh9z_ffbw.jpeg)
В приложении мы собираем несколько метрик. В первую очередь, это бизнес-метрики. Если у вас фото-сервис, например, вам интересно, сколько фотографий заливают в день, в час, в секунду. Следующие метрики — «полутехнические»: отзывчивость мобильного приложения или сайта, работа API, насколько быстро пользователь взаимодействует с сайтом, инсталляции приложений, UX. Трекинг поведения пользователей — третья важная метрика. Это системы типа Google Analytics и Яндекс.Метрики. У нас своя классная система трекинга, в которую мы много инвестируем.
В процессе работы со статистикой задействовано много пользователей — это разработчики и бизнес-аналитики. Важно, чтобы все разговаривали на одном языке, поэтому надо договариваться.
Формализация структуры бизнес-событий — это когда разработчик говорит, сколько у нас регистраций, аналитик понимает, что ему предоставили информацию не только об общем количестве регистраций, но и с разбивкой по странам, полу и другим параметрам. И вся эта информация формализована и находится в открытом доступе для всех пользователей компании. Событие имеет типизированную структуру и формальное описание. Например, мы у себя храним эту информацию в формате Protocol Buffers.
Описание события «Регистрация»:
Событие регистрации содержит информацию о пользователе, поле, времени события и стране регистрации пользователя. Эта информация доступна аналитикам, и, в дальнейшем, бизнес понимает, что мы вообще собираем.
Формальное описание — это единообразие для разработчиков, аналитиков и продуктового отдела. Потом эта информация красной нитью пронизывает описание бизнес-логики приложения. Например, у нас есть внутренняя система описания бизнес-процессов и в ней скрин, что у нас есть новая фича.
![](https://habrastorage.org/r/w780q1/webt/qf/cv/hg/qfcvhgvo39rtidli5fxdq65hx2q.jpeg)
В product requirements document есть секция с инструкцией, что когда пользователь взаимодействует с приложением таким образом, мы должны отправить event с точно такими же параметрами. Впоследствии мы сможем валидировать, насколько наши фичи хорошо работают, и что мы их правильно измерили. Формальное описание позволяет нам в дальнейшем понимать как эти данные сохранить в базе данных: NoSQL, SQL или других. У нас есть схема данных, и это круто.
В некоторых аналитических системах, которые предоставляются как сервис, в подноготной хранилища всего 10-15 событий. У нас это число выросло больше 1000 и не собирается останавливаться — без единого реестра жить невозможно.
Мы решили, что статистика — это важно и описали некую предметную область — это уже хорошо, можно жить дальше.
Мы решили строить систему так, что когда происходит бизнес-событие — регистрация, отправка сообщения, лайк — то одновременно с сохранением этой информации, мы отдельно отправляем некое статистическое событие.
Оно обрабатывается совершенно независимо от хранилищ данных, в которых работает приложение, потому что поток данных проходит через отдельный конвейер обработки.
Описание через EDL:
У нас есть описание события регистрации. Автоматически генерируется API, доступный разработчикам из кода, который в 4 строчки позволяет отправить статистику.
API на основании EDL:
Это наша внешняя система. Мы так делаем потому, что у нас есть невероятные сервисы, которые предоставляют API для работы с данными о фотографиях, о чем-то еще. Они все хранят данные в классных новомодных базах, например, в Aerospike и CockroachDB.
Когда нужно построить какой-то репортинг, не надо ходить и побираться: «Ребята, сколько у вас тут этого и сколько того?» — все данные отправляются отдельным flow. Конвейер обработки — внешняя система. Из контекста приложения мы отвязываем все данные от хранилища бизнес-логики, и отправляем дальше в отдельный pipeline.
Фаза Collect предполагает наличие application серверов. У нас это PHP.
![](https://habrastorage.org/webt/az/vo/va/azvova-esx681etff8drvvkigeq.gif)
Это подсистема, которая позволяет отправить в другой pipeline то, что мы делали, из контекста приложения. Транспорт выбирается исключительно из ваших требований, в зависимости от ситуации в проекте.
У транспорта есть характеристики, и первая — гарантии доставки. Характеристики транспорта: at-least-once, exactly-once, вы выбираете под ваши задачи статистики, исходя из того, насколько эти данные важны. Например, для биллинговых систем недопустимо, чтобы в статистике отражалось больше транзакций, чем есть — это деньги, так нельзя.
Второй параметр — биндинги для языков программирования. С транспортом надо как-то взаимодействовать, поэтому он выбирается под язык, на котором написан проект.
Третий параметр — масштабируемость. Раз мы говорим о миллионах событий в секунду, то хорошо бы держать в уме задел на дальнейшую масштабируемость.
Вариантов транспорта много: РСУБД приложения, Flume, Kafka или LSD. Мы используем LSD — это наш особый путь.
LSD не имеет ничего общего с запрещенными веществами. Это живой, очень быстрый стриминговый демон, который не предоставляет какого-то агента для того, чтобы в него записать. Мы его умеем тюнить, у нас существует интеграция с другими системами: HDFS, Kafka — мы можем отправленные данные перестримливать. У LSD нет сетевого вызова на INSERT, и в нём можно управлять топологией сети.
Самое главное, что это OpenSource от Badoo — нет повода не доверять этому программному обеспечению.
Если бы это был идеальный демон, то вместо Kafka мы бы на каждой конференции обсуждали LSD, но в каждом LSD есть ложка дёгтя. У нас есть свои ограничения, с которыми мы живем, которые нас устраивают: в LSD нет поддержки репликации и гарантия доставки у него at-least once. Также для денежных транзакций это не самый подходящий транспорт, но с деньгами вообще надо общаться исключительно через «кислотные» базы данных — поддерживающие ACID.
На основании результатов предыдущей серии мы получили формальное описание данных, сгенерировали из них отличный, удобный для разработчиков API для отправки событий, и придумали, как эти данные транспортировать из контекста приложения в отдельный pipeline. Уже неплохо, и мы подходим к следующей фазе.
Мы насобирали данные из регистраций, загруженных фотографий, голосований — что со всем этим делать? Из этих данных мы хотим получить графики с длинной историей и сырые данные. Графики все понимают — не надо быть разработчиком, чтобы понять по кривой, что доход компании растет. Сырые данные используем для оперативного репортинга и ad-hoc. Для более сложных случаев наши аналитики хотят выполнять аналитические запросы на этих данных. И тот, и тот функционал нам нужен.
Графики бывают разных видов.
![](https://habrastorage.org/r/w780q1/webt/hu/c7/ap/huc7apcxpcajxhc5k3an8ken4wk.jpeg)
Или, например, график с историей, который показывает данные за 10 лет.
![](https://habrastorage.org/r/w780q1/webt/cq/_z/pc/cq_zpcmcbe2b_nipvwhbtnkme94.jpeg)
Графики бывают даже такие.
![](https://habrastorage.org/r/w780q1/webt/rs/76/eg/rs76eg23gglv8m1xlaby2gr8vzg.jpeg)
Это результат некоторого AB-теста, и он удивительно похож на здание Крайслера в Нью-Йорке.
Существует два способа нарисовать график: запрос к «сырым» данным и time series. Оба подхода обладают недостатками и достоинствами, на которых мы не будем подробно останавливаться. Мы используем гибридный подход: короткий «хвост» из сырых данных держим для оперативного репортинга, а time series для долгосрочного хранения. Второе вычисляется из первого.
Это долгая история — миллионы RPS не случаются за день. Badoo — это компания с десятилетней историей, и можно сказать, что система обработки данных росла вместе с компанией.
![](https://habrastorage.org/r/w780q1/webt/av/o3/04/avo304ko07jkl4szc5dnz2x7zc8.jpeg)
Сначала у нас не было ничего. Мы начали собирать данные — получилось 5 000 событий в секунду. Один хост MySQL и больше ничего! С этой задачей справится любая реляционная СУБД, и с ней будет комфортно: у вас будет транзакционность — кладете данные, получаете по ним запросы — все классно и неплохо работает. Так мы жили некоторое время.
В какой-то момент случился функциональный шардинг: данные о регистрациях — сюда, а о фотографиях — туда. Так мы дожили до 200 000 событий в секунду и начали применять разные комбинированные подходы: хранить не сырые данные, а агрегированные, но пока в пределах реляционной БД. Мы храним счетчики, но суть большинства реляционных БД такая, что на этих данных потом невозможно будет выполнить DISTINCT-запрос — алгебраическая модель счетчиков не позволяет посчитать DISTINCT.
У нас в Badoo есть девиз «Unstoppable force». Мы не собирались останавливаться и росли дальше. В момент, когда перешагнули порог 200 000 событий в секунду, приняли решение о создании формального описания, о котором я говорил выше. До этого был некий хаос, а теперь получили структурированный реестр событий: начали масштабировать систему, подключили Hadoop, все данные легли в Hive-таблицы.
Hadoop — это огромный программный комплекс, файловая система. Для распределенных вычислений Hadoop говорит: «Вот сюда поместите данные, я вам позволю выполнять аналитические запросы на них». Так мы и сделали — написали регулярный расчет всех графиков — получилось отлично. Но графики имеют ценность, когда они оперативно обновляются — раз в день смотреть на обновление графика не так весело. Если мы выкатили на продакшн что-то, приводящее к fatal error, то хотели бы увидеть падение графика сразу, а не через день. Поэтому вся система начала деградировать через какое-то время. Однако мы поняли, что на данном этапе можно придерживаться выбранного стека технологий.
На этапе от 400 000 до 800 000 событий в секунду, мы заменили Hadoop в чистом виде и Hive, как исполнителя аналитических запросов, на Spark Streaming, написали generic map/reduce и инкрементальный обсчёт метрик. Я еще 3 года назад рассказывал, как мы это сделали. Тогда нам казалось, что Spark будет жить вечно, но жизнь распорядилась иначе — мы упёрлись в ограничения Hadoop. Возможно, если бы у нас были другие условия, то мы бы продолжали жить с Hadoop.
Другой проблемой, помимо вычислений графиков на Hadoop, были неимоверные четырехэтажные SQL-запросы, которые гоняли аналитики, а графики обновляются не быстро. Дело в том, что там довольно хитрая работа с оперативным процессингом данных, чтобы это было реалтайм, быстро и круто.
Badoo обслуживается двумя дата-центрами, расположенными по двум сторонам Атлантического океана — в Европе и в Северной Америке. Чтобы построить единые репортинги, надо данные из Америки прислать в Европу. Именно в европейском дата-центре мы ведем весь учет статистики, потому что там больше вычислительных мощностей. Roundtrip между дата-центрами порядка 200 мс — сеть довольно нежная — сделать запрос в другой ДЦ это не то же самое, что сходить в соседнюю стойку.
Когда мы начали формализовать события и разработчики, и продакт-менеджеры втянулись, всем всё понравилось — случился просто взрывной рост эвентов. В это время пора было покупать железо в кластер, но мы не особо хотели это делать.
Когда мы прошли пик 800 000 событий в секунду, то узнали, что Яндекс выложил в OpenSource ClickHouse, и решили его попробовать. Набили вагон шишек, пока что-то пытались сделать, и в результате, когда все заработало, устроили небольшой фуршет по поводу первого миллиона событий. Наверное, на ClickHouse можно было бы и заканчивать доклад.
Но это неинтересно, поэтому продолжим разговаривать про процессинг данных.
ClickHouse — это хайп последних двух лет и не нуждается в представлении: только на HighLoad++ в 2018 году я помню порядка пяти докладов о нем, а также семинары и митапы.
Этот инструмент предназначен для решения именно тех задач, которые мы перед собой поставили. Там есть realtime update и фишки, которые мы получили в свое время от Hadoop: репликация, шардинг. Не было повода не попробовать ClickHouse, потому что понимали, что с реализацией на Hadoop мы уже пробили дно. Инструмент классный, а документация вообще огонь — я сам туда писал, все очень нравится, и все здорово. Но нам надо было решить ряд вопросов.
Как весь поток событий переложить в ClickHouse? Как объединять данные из двух дата-центров? От того, что мы пришли к админам и сказали: «Ребята, давайте засетапим ClickHouse», они не сделают сеть в два раза толще, а задержку в два раза меньше. Нет, сеть по-прежнему такая же тонкая, и маленькая, как первая зарплата.
Как хранить результаты? На Hadoop мы понимали, как рисовать графики — но как это делать на волшебном ClickHouse? Волшебная палочка в комплект не входит. Как доставлять результаты до time series storage?
Как говорил мой лектор в институте, рассмотрим 3 схемы данных: стратегическая, логическая и физическая.
У нас есть 2 дата-центра. Мы узнали, что ClickHouse умеет ничего не знать про ДЦ, и просто засетапили в каждом ДЦ по кластеру. Теперь данные не переезжают через кросс-атлантический кабель — все данные, которые произошли в ДЦ, хранятся локально в своем кластере. Когда хотим произвести запрос над объединенными данными, например, узнать, сколько регистраций в обоих ДЦ, ClickHouse дает нам эту возможность. Низкая задержка и доступность для запроса — просто шедевр!
![](https://habrastorage.org/r/w780q1/webt/mr/dc/-2/mrdc-205rzloz1c2oemavnikcxu.jpeg)
Опять вопросы: как наши данные лягут в реляционную модель ClickHouse, что сделать, чтобы не потерять репликацию и шардинг? В документации ClickHouse всё обширно описано, и если у вас больше, чем один сервер, вы наткнетесь на эту статью. Поэтому не будем углубляться в то, что есть в мануале: репликации, шардирование и запросы ко всем данным по шардам.
Логическая схема — это самое интересное. В одном pipeline мы обрабатываем гетерогенные события. Это означает, что у нас есть поток гетерогенных событий: регистрации, голоса, заливки фотографий, технические метрики, трекинг поведения пользователей — все эти события обладают совершенно разными атрибутами. Например, я посмотрел экран на мобилке — мне нужен id экрана, я за кого-то проголосовал — надо понимать, голос был за или против. Все эти события обладают разными атрибутами, по ним рисуются разные графики, но все это надо обрабатывать в едином pipeline. Как это засунуть в модель ClickHouse?
Подход №1 — per event table. Этотпервый подход, мы экстраполировали с опыта, полученного с MySQL — создавали табличку под каждый эвент в ClickHouse. Звучит довольно логично, но мы натолкнулись на ряд сложностей.
У нас нет ограничения на то, что событие изменит свою структуру, когда выйдет сегодняшний билд. Патчем это может сделать любой разработчик. Схема вообще во все стороны мутабельная. Единственное required-поле — это timestamp-событие и что за событие было. Все остальное меняется на лету, и, соответственно, эти таблички надо модифицировать. В ClickHouse есть возможность выполнять ALTER на кластере, но это деликатная нежная процедура, которую сложно автоматизировать, чтобы она работала безотказно. Поэтому это минус.
У нас больше тысячи различных событий, что дает нам высокий INSERT rate на одну машину — все данные постоянно записываем в тысячу таблиц. Для ClickHouse это анти-паттерн. Если у Пепси слоган — «Живи большими глотками», то у ClickHouse — «Живи большими батчами». Если этого не делать, то захлебывается репликация, ClickHouse отказывается принимать новые вставки — неприятная схема.
Подход №2 — широкая таблица. Сибирские мужики попробовали подсунуть бензопиле рельсу и применить другую модель данных. Мы делаем таблицу с тысячей колонок, где у каждого события зарезервированы колонки под его данные. У нас получается огромная разреженная таблица — к счастью это не ушло дальше разработческой среды, потому что уже с первых вставок стало ясно, что схема абсолютно плохая, и мы так делать не будем.
Но все-таки хочется использовать такой классный программный продукт, чуть-чуть еще допилить — и будет что надо.
Подход №3 — generic таблица. У нас есть одна огромная таблица, в которой мы храним данные в массивах, потому что ClickHouse поддерживает нескалярные типы данных. То есть мы заводим колонку, в которой хранятся названия атрибутов, и отдельно колонку с массивом, в котором хранятся значения атрибутов.
![](https://habrastorage.org/r/w780q1/webt/ot/s3/ro/ots3ronbcjh69ssxujqvbuxeghc.jpeg)
ClickHouse здесь свою задачу выполняет очень хорошо. Если бы нам надо было только вставлять данные, мы бы, наверное, еще раз в 10 больше на текущей инсталляции выжали.
Однако, ложка дегтя в том, что это тоже анти-паттерн для ClickHouse —хранить массивы строк. Это плохо, потому что массивы строк занимают больше места на диске — они хуже жмутся, чем простые колонки, и сложнее в обработке. Но для нашей задачи мы на это закрываем глаза, так как достоинства перевешивают.
Как делать SELECT из такой таблицы? У нас задача — посчитать регистрации, сгруппированные по полу. Надо сначала в одном массиве найти, какая позиция соответствует колонке gender, потом в другую колонку залезть с этим индексом и достать данные.
![](https://habrastorage.org/r/w780q1/webt/gh/pw/ek/ghpwekgjjrr0eisi8_zjmoi48tk.jpeg)
Поскольку все эвенты описаны, у них есть строгая структура, каждому типу события мы формируем четырехэтажный SQL-запрос, выполняем его и сохраняем результаты в другую таблицу.
Проблема в том, что для отрисовки двух соседних точек на графике, надо отсканировать всю таблицу. Пример: мы смотрим регистрации за сутки. Это событие от самой верхней строки до предпоследней. Просканировали один раз — отлично. Через 5 минут хотим нарисовать новую точку на графике — опять сканируем диапазон данных, который пересекается с предыдущим сканированием, и так — для каждого события. Звучит логично, но выглядит не здорово.
Вдобавок, когда мы берем какие-то строки, нам надо еще считать результаты под агрегации. Например, есть факт, что раб божий зарегистрировался в Скандинавии и был мужчиной, и нам надо посчитать сводную статистику: сколько всего регистраций, сколько мужчин, сколько из них людей, и сколько из Норвегии. Это называется в терминах аналитических баз данных ROLLUP, CUBE и GROUPING SETS — одну строчку превратить в несколько.
К счастью, в ClickHouse существует инструмент для решения этой проблемы, а именно — сериализованное состояние агрегатных функций. Это означает, что можно какой-то кусочек данных просканировать один раз и сохранить эти результаты. Это прямо киллер-фича. Мы 3 года назад сделали точно такую на Spark и Hadoop, и круто, что параллельно с нами лучшие умы Яндекса реализовали в ClickHouse аналог.
У нас есть медленный запрос — посчитать уникальных юзеров за сегодня и вчера.
В физическом плане мы можем сделать SELECT для состояния за вчерашний день, получить его бинарное представление, куда-то сохранить.
Для сегодняшнего дня у нас просто меняется условие, что это будет сегодня:
Тактика такова:
То есть мы за сутки не сканируем все эвенты, а сливаем какое-то количество часовых или минутных промежуточных результатов. Эта техника позволила нам снизить время обработки в разы. Точных цифр не скажу, но она помогла нам выжить, потому что, когда мы сказали, что теперь всё классно считаем в ClickHouse, люди обрадовались: «О, теперь вам можно еще эвентиков налить! Давайте, сейчас посмотрим на вас!»
Как мы помним, если у нас будет тысяча таблиц, это не очень хорошо. Поэтому результаты агрегации нужно сливать в одну таблицу, в которой будем хранить имена агрегатных функций и их значения. Это опять два массива. Преимущество этой таблицы — к ней можно написать SQL-запрос, который позволит аналитикам колдовать над этими результатами. Классно, что можно выполнить любые запросы над исходными данными, но результаты агрегаций тоже полезная информация.
![](https://habrastorage.org/r/w780q1/webt/df/0b/o6/df0bo6pb7l6t94sxnrwtpctfcxk.jpeg)
Мы не забываем о том, что надо как-то нарисовать time series. Реализуем это посредством функциональности материализованных представлений: как только добавляем данные в таблицу, срабатывает триггер, который делает вставку в другую таблицу, предназначенную только для time series.
Таблица для time series проста как атом водорода: колонка с датой, колонка с именем метрики, колонка с точным timestamp и колонка со значением точки на графике. Когда вставляем результат выполнения агрегации над конкретным событием, у нас срабатывает триггер, который выглядит следующим образом. Нам надо получить имя метрики из таблички с массивами и взять от него хэш. Это важный момент, потому что в отличие от большинства баз данных, мы не храним здесь текстовое представление метрики, а берем от него хэш — это круто, он занимает меньше места. Да, математически у него возможны коллизии, но в ClickHouse существует хэш-функция, которая дает очень крутое распределение, и мы ее радостно используем.
Когда надо получить данные на клиенте, мы не знаем реальное значение этого хэша, и отдаем его на откуп ClickHouse:
Это дает нам выборку time series за 2 недели, то есть точки для 20 метрик за 20-80 мс. Это крутой результат. В ClickHouse есть функционал GraphiteMergeTree, который позволяет хранить time series, но наш подход круче и производительнее.
8 серверов ClickHouse, из которых 6 в дата-центре в Европе, а 2 в Америке: фактор репликации равен 2 — в одном шарде два сервера, и нам этого достаточно. В пике 1.8 млн. событий в секунду на вход, а выходная мощность системы 500 тысяч метрик в секунду. Представляете, нам прилетает 1,8 млн, и мы умудряемся усиливать и выдавать 500 тысяч! Суммарно наша система знает больше миллиарда метрик.
Снижение задержки в 2 раза. Мы стали рисовать точки на графиках в два раза чаще. Экономия по памяти в 3 раза, а по CPU — в 4. Я считаю, что это очень внушительные и исключительно позитивные результаты.
Мы научились поэтапно выбирать технологии под нашу задачу, решили, что будем измерять, собрали данные в кучу и сделали крутейший процессинг. Это не значит, что вы прочитаете доклад, тут же возьмете ClickHouse и начнете писать 3 000 событий в секунду. Да, это возможно, но и возможно, что под вас это будет overkill.
Мы научились выбирать, а вы узнали, как мы проходили этот путь. Мы выбрали ClickHouse, и пока не нащупали его пределов. Уверен, что модель, которую мы используем, позволит масштабироваться и дальше. По моим оценкам, эти 8 серверов позволят пережить пик в 3–4 миллиона событий без добавления железа. Выше — решать проблемы вместе с железом.
Что с этими данными произойдет дальше, кто с ними будет работать? Мы нарисуем time series, у нас даже есть дашборды из time series, где разные люди из компании смотрят и пишут друг другу вопросы, почему здесь так, а здесь так.
![](https://habrastorage.org/r/w780q1/webt/82/uo/qq/82uoqq4jw6amtxph_jgcjpjnwkm.jpeg)
Drop Detect — поиск отклонений на SQL: нехитрым SQL-запросом к таблице с результатами, которые мы посчитали по эвентам, можно выявить самое необычное поведение метрик.
![](https://habrastorage.org/r/w780q1/webt/ru/1v/up/ru1vuporiqj-suncjyc-sh06xrk.jpeg)
Это пока никакой не Anomaly Detection — простое сравнение нескольких точек на графике. У нас есть инструмент, который рассказывает, что голоса за профили просели на 2% в таком разрезе, а в другом — выросли на 40, и среди всех метрик, которые случились по этому эвенту, а их может быть десятки и сотни тысяч, это наиболее значимое изменение.
Это все здорово — можно построить мониторинг, задать границы, но хочется чего-то большого, поэтому приходит настоящий Anomaly Detection.
Это система, которая берет time series данные. Они у нас иерархические: регистрация, регистрация мужчин, регистрация мужчин в Скандинавии. Для каждого time series графика вычисляются предсказание и границы. В случае, если актуальное поведение метрики выходит за границы, производится алертинг. Мы еще ранжируем примерно, как делает drop detection — когда видим наиболее интересные и значимые аномалии, производим алертинг.
Примерно так выглядит наш UI.
![](https://habrastorage.org/r/w780q1/webt/sk/mg/hn/skmghn7mirsubwu-vpm2dq5tols.jpeg)
Аномалии отсортированы по весу. Есть какой-то внешний слой, который вылез за ширину доверительного интервала — серой области. Этим пользуются и продакт-менеджеры, и разработчики.
На основании классных технологий, которые мы применили на прошлом шаге, у нас появилась возможность рисовать графики. Отдел мониторинга теперь может настроить алертинг на основании жестких правил, которые задают разработчики: если метрика падает ниже 1000 — alarm, если метрика отлична от 0 — alarm. БД нам это позволяет.
Прикрутили наш Anomaly Detection со своей структурой данных, с табличками и колонками. Раньше у нас Anomaly Detection жил в аналитической системе Exasol, а теперь его данные также хранятся в ClickHouse. Этот переход позволил нам также ускорить процесс Anomaly Detection в 2 раза, просто сменив базу.
Подытожим всё, что узнали, рассмотрев 4 этапа жизненного цикла статистики.
Мы поняли, зачем собирать статистику в проекте, и познакомились с техниками сбора, которые можно для этого применить. Мы рассмотрели, как поэтапно изменять стек технологий, и как мы прошли это изменение. Узнали, какие инструменты можно построить на базе статистических данных.
3 года назад Александр рассказывал, как в Badoo построили масштабируемую систему near-realtime обработки событий. С тех пор она эволюционировала, в процессе росли объёмы, приходилось решать задачи масштабирования и отказоустойчивости, а в определённый момент потребовались радикальные меры — смена технологического стека.
![](https://habrastorage.org/webt/q8/s-/cq/q8s-cqlxfrv8abkdotnudzeq1u8.jpeg)
Из расшифровки вы узнаете, как в Badoo заменили связку Spark + Hadoop на ClickHouse, в 3 раза сэкономили железо и увеличили нагрузку в 6 раз, зачем и какими средствами собирать статистику в проекте, и что с этими данными потом делать.
О спикере: Александр Крашенинников (alexkrash) — Head of Data Engineering в Badoo. Занимается BI-инфраструктурой, масштабированием под нагрузки, руководит командами, которые строят инфраструктуру обработки данных. Обожает всё распределённое: Hadoop, Spark, ClickHouse. Уверен, что классные распределенные системы можно готовить из OpenSource.
Сбор статистики
Если у нас нет данных, мы слепы и не можем управлять своим проектом. Именно поэтому нам нужна статистика — для мониторинга жизнеспособности проекта. Мы, как инженеры, должны стремиться к улучшению наших продуктов, а если хочешь улучшить — измерь. Этого девиза я придерживаюсь в работе. В первую очередь, наша цель — это польза для бизнеса. Статистика дает ответы на вопросы бизнеса. Технические метрики — это технические метрики, но бизнес тоже заинтересован в показателях, и их тоже надо считать.
Жизненный цикл статистики
Жизненный цикл статистики я определяю 4 пунктами, про каждый из которых будем разговаривать отдельно.
![](https://habrastorage.org/webt/pp/kb/p5/ppkbp5uw_stwtzdgmakh9z_ffbw.jpeg)
Фаза Define — формализация
В приложении мы собираем несколько метрик. В первую очередь, это бизнес-метрики. Если у вас фото-сервис, например, вам интересно, сколько фотографий заливают в день, в час, в секунду. Следующие метрики — «полутехнические»: отзывчивость мобильного приложения или сайта, работа API, насколько быстро пользователь взаимодействует с сайтом, инсталляции приложений, UX. Трекинг поведения пользователей — третья важная метрика. Это системы типа Google Analytics и Яндекс.Метрики. У нас своя классная система трекинга, в которую мы много инвестируем.
В процессе работы со статистикой задействовано много пользователей — это разработчики и бизнес-аналитики. Важно, чтобы все разговаривали на одном языке, поэтому надо договариваться.
Договариваться можно устно, но гораздо лучше, когда это происходит формально — в четкой структуре событий.
Формализация структуры бизнес-событий — это когда разработчик говорит, сколько у нас регистраций, аналитик понимает, что ему предоставили информацию не только об общем количестве регистраций, но и с разбивкой по странам, полу и другим параметрам. И вся эта информация формализована и находится в открытом доступе для всех пользователей компании. Событие имеет типизированную структуру и формальное описание. Например, мы у себя храним эту информацию в формате Protocol Buffers.
Описание события «Регистрация»:
enum Gender {
FEMALE = 1;
MALE = 2;
}
message Registration {
required int32 userid =1;
required Gender usergender = 2;
required int32 time =3;
required int32 countryid =4;
}
Событие регистрации содержит информацию о пользователе, поле, времени события и стране регистрации пользователя. Эта информация доступна аналитикам, и, в дальнейшем, бизнес понимает, что мы вообще собираем.
Зачем нужно формальное описание?
Формальное описание — это единообразие для разработчиков, аналитиков и продуктового отдела. Потом эта информация красной нитью пронизывает описание бизнес-логики приложения. Например, у нас есть внутренняя система описания бизнес-процессов и в ней скрин, что у нас есть новая фича.
![](https://habrastorage.org/webt/qf/cv/hg/qfcvhgvo39rtidli5fxdq65hx2q.jpeg)
В product requirements document есть секция с инструкцией, что когда пользователь взаимодействует с приложением таким образом, мы должны отправить event с точно такими же параметрами. Впоследствии мы сможем валидировать, насколько наши фичи хорошо работают, и что мы их правильно измерили. Формальное описание позволяет нам в дальнейшем понимать как эти данные сохранить в базе данных: NoSQL, SQL или других. У нас есть схема данных, и это круто.
В некоторых аналитических системах, которые предоставляются как сервис, в подноготной хранилища всего 10-15 событий. У нас это число выросло больше 1000 и не собирается останавливаться — без единого реестра жить невозможно.
Итоги фазы Define
Мы решили, что статистика — это важно и описали некую предметную область — это уже хорошо, можно жить дальше.
Фаза Collect — сбор данных
Мы решили строить систему так, что когда происходит бизнес-событие — регистрация, отправка сообщения, лайк — то одновременно с сохранением этой информации, мы отдельно отправляем некое статистическое событие.
В коде статистика отсылается одновременно с бизнес-событием.
Оно обрабатывается совершенно независимо от хранилищ данных, в которых работает приложение, потому что поток данных проходит через отдельный конвейер обработки.
Описание через EDL:
enum Gender {
FEMALE = 1;
MALE = 2;
}
message Registration {
required int32 user_id =1;
required Gender user_gender = 2;
required int32 time =3;
required int32 country_id =4;
}
У нас есть описание события регистрации. Автоматически генерируется API, доступный разработчикам из кода, который в 4 строчки позволяет отправить статистику.
API на основании EDL:
\EDL\Event\Regist ration::create()
->setUserId(100500)
->setGender(Gender: :MALE)
->setTime(time())
->send();
Доставка событий
Это наша внешняя система. Мы так делаем потому, что у нас есть невероятные сервисы, которые предоставляют API для работы с данными о фотографиях, о чем-то еще. Они все хранят данные в классных новомодных базах, например, в Aerospike и CockroachDB.
Когда нужно построить какой-то репортинг, не надо ходить и побираться: «Ребята, сколько у вас тут этого и сколько того?» — все данные отправляются отдельным flow. Конвейер обработки — внешняя система. Из контекста приложения мы отвязываем все данные от хранилища бизнес-логики, и отправляем дальше в отдельный pipeline.
Фаза Collect предполагает наличие application серверов. У нас это PHP.
![](https://habrastorage.org/webt/az/vo/va/azvova-esx681etff8drvvkigeq.gif)
Транспорт
Это подсистема, которая позволяет отправить в другой pipeline то, что мы делали, из контекста приложения. Транспорт выбирается исключительно из ваших требований, в зависимости от ситуации в проекте.
У транспорта есть характеристики, и первая — гарантии доставки. Характеристики транспорта: at-least-once, exactly-once, вы выбираете под ваши задачи статистики, исходя из того, насколько эти данные важны. Например, для биллинговых систем недопустимо, чтобы в статистике отражалось больше транзакций, чем есть — это деньги, так нельзя.
Второй параметр — биндинги для языков программирования. С транспортом надо как-то взаимодействовать, поэтому он выбирается под язык, на котором написан проект.
Третий параметр — масштабируемость. Раз мы говорим о миллионах событий в секунду, то хорошо бы держать в уме задел на дальнейшую масштабируемость.
Вариантов транспорта много: РСУБД приложения, Flume, Kafka или LSD. Мы используем LSD — это наш особый путь.
Live Streaming Daemon
LSD не имеет ничего общего с запрещенными веществами. Это живой, очень быстрый стриминговый демон, который не предоставляет какого-то агента для того, чтобы в него записать. Мы его умеем тюнить, у нас существует интеграция с другими системами: HDFS, Kafka — мы можем отправленные данные перестримливать. У LSD нет сетевого вызова на INSERT, и в нём можно управлять топологией сети.
Самое главное, что это OpenSource от Badoo — нет повода не доверять этому программному обеспечению.
Если бы это был идеальный демон, то вместо Kafka мы бы на каждой конференции обсуждали LSD, но в каждом LSD есть ложка дёгтя. У нас есть свои ограничения, с которыми мы живем, которые нас устраивают: в LSD нет поддержки репликации и гарантия доставки у него at-least once. Также для денежных транзакций это не самый подходящий транспорт, но с деньгами вообще надо общаться исключительно через «кислотные» базы данных — поддерживающие ACID.
Итоги фазы Collect
На основании результатов предыдущей серии мы получили формальное описание данных, сгенерировали из них отличный, удобный для разработчиков API для отправки событий, и придумали, как эти данные транспортировать из контекста приложения в отдельный pipeline. Уже неплохо, и мы подходим к следующей фазе.
Фаза Process — обработка данных
Мы насобирали данные из регистраций, загруженных фотографий, голосований — что со всем этим делать? Из этих данных мы хотим получить графики с длинной историей и сырые данные. Графики все понимают — не надо быть разработчиком, чтобы понять по кривой, что доход компании растет. Сырые данные используем для оперативного репортинга и ad-hoc. Для более сложных случаев наши аналитики хотят выполнять аналитические запросы на этих данных. И тот, и тот функционал нам нужен.
Графики
Графики бывают разных видов.
![](https://habrastorage.org/webt/hu/c7/ap/huc7apcxpcajxhc5k3an8ken4wk.jpeg)
Или, например, график с историей, который показывает данные за 10 лет.
![](https://habrastorage.org/webt/cq/_z/pc/cq_zpcmcbe2b_nipvwhbtnkme94.jpeg)
Графики бывают даже такие.
![](https://habrastorage.org/webt/rs/76/eg/rs76eg23gglv8m1xlaby2gr8vzg.jpeg)
Это результат некоторого AB-теста, и он удивительно похож на здание Крайслера в Нью-Йорке.
Существует два способа нарисовать график: запрос к «сырым» данным и time series. Оба подхода обладают недостатками и достоинствами, на которых мы не будем подробно останавливаться. Мы используем гибридный подход: короткий «хвост» из сырых данных держим для оперативного репортинга, а time series для долгосрочного хранения. Второе вычисляется из первого.
Как мы доросли до 1,8 миллионов событий в секунду
Это долгая история — миллионы RPS не случаются за день. Badoo — это компания с десятилетней историей, и можно сказать, что система обработки данных росла вместе с компанией.
![](https://habrastorage.org/webt/av/o3/04/avo304ko07jkl4szc5dnz2x7zc8.jpeg)
Сначала у нас не было ничего. Мы начали собирать данные — получилось 5 000 событий в секунду. Один хост MySQL и больше ничего! С этой задачей справится любая реляционная СУБД, и с ней будет комфортно: у вас будет транзакционность — кладете данные, получаете по ним запросы — все классно и неплохо работает. Так мы жили некоторое время.
В какой-то момент случился функциональный шардинг: данные о регистрациях — сюда, а о фотографиях — туда. Так мы дожили до 200 000 событий в секунду и начали применять разные комбинированные подходы: хранить не сырые данные, а агрегированные, но пока в пределах реляционной БД. Мы храним счетчики, но суть большинства реляционных БД такая, что на этих данных потом невозможно будет выполнить DISTINCT-запрос — алгебраическая модель счетчиков не позволяет посчитать DISTINCT.
У нас в Badoo есть девиз «Unstoppable force». Мы не собирались останавливаться и росли дальше. В момент, когда перешагнули порог 200 000 событий в секунду, приняли решение о создании формального описания, о котором я говорил выше. До этого был некий хаос, а теперь получили структурированный реестр событий: начали масштабировать систему, подключили Hadoop, все данные легли в Hive-таблицы.
Hadoop — это огромный программный комплекс, файловая система. Для распределенных вычислений Hadoop говорит: «Вот сюда поместите данные, я вам позволю выполнять аналитические запросы на них». Так мы и сделали — написали регулярный расчет всех графиков — получилось отлично. Но графики имеют ценность, когда они оперативно обновляются — раз в день смотреть на обновление графика не так весело. Если мы выкатили на продакшн что-то, приводящее к fatal error, то хотели бы увидеть падение графика сразу, а не через день. Поэтому вся система начала деградировать через какое-то время. Однако мы поняли, что на данном этапе можно придерживаться выбранного стека технологий.
Для нас Java была в новинку, нам понравилось, и мы поняли, что можно сделать по-другому.
На этапе от 400 000 до 800 000 событий в секунду, мы заменили Hadoop в чистом виде и Hive, как исполнителя аналитических запросов, на Spark Streaming, написали generic map/reduce и инкрементальный обсчёт метрик. Я еще 3 года назад рассказывал, как мы это сделали. Тогда нам казалось, что Spark будет жить вечно, но жизнь распорядилась иначе — мы упёрлись в ограничения Hadoop. Возможно, если бы у нас были другие условия, то мы бы продолжали жить с Hadoop.
Другой проблемой, помимо вычислений графиков на Hadoop, были неимоверные четырехэтажные SQL-запросы, которые гоняли аналитики, а графики обновляются не быстро. Дело в том, что там довольно хитрая работа с оперативным процессингом данных, чтобы это было реалтайм, быстро и круто.
Badoo обслуживается двумя дата-центрами, расположенными по двум сторонам Атлантического океана — в Европе и в Северной Америке. Чтобы построить единые репортинги, надо данные из Америки прислать в Европу. Именно в европейском дата-центре мы ведем весь учет статистики, потому что там больше вычислительных мощностей. Roundtrip между дата-центрами порядка 200 мс — сеть довольно нежная — сделать запрос в другой ДЦ это не то же самое, что сходить в соседнюю стойку.
Когда мы начали формализовать события и разработчики, и продакт-менеджеры втянулись, всем всё понравилось — случился просто взрывной рост эвентов. В это время пора было покупать железо в кластер, но мы не особо хотели это делать.
Когда мы прошли пик 800 000 событий в секунду, то узнали, что Яндекс выложил в OpenSource ClickHouse, и решили его попробовать. Набили вагон шишек, пока что-то пытались сделать, и в результате, когда все заработало, устроили небольшой фуршет по поводу первого миллиона событий. Наверное, на ClickHouse можно было бы и заканчивать доклад.
Просто возьмите ClickHouse и живите с ним.
Но это неинтересно, поэтому продолжим разговаривать про процессинг данных.
ClickHouse
ClickHouse — это хайп последних двух лет и не нуждается в представлении: только на HighLoad++ в 2018 году я помню порядка пяти докладов о нем, а также семинары и митапы.
Этот инструмент предназначен для решения именно тех задач, которые мы перед собой поставили. Там есть realtime update и фишки, которые мы получили в свое время от Hadoop: репликация, шардинг. Не было повода не попробовать ClickHouse, потому что понимали, что с реализацией на Hadoop мы уже пробили дно. Инструмент классный, а документация вообще огонь — я сам туда писал, все очень нравится, и все здорово. Но нам надо было решить ряд вопросов.
Как весь поток событий переложить в ClickHouse? Как объединять данные из двух дата-центров? От того, что мы пришли к админам и сказали: «Ребята, давайте засетапим ClickHouse», они не сделают сеть в два раза толще, а задержку в два раза меньше. Нет, сеть по-прежнему такая же тонкая, и маленькая, как первая зарплата.
Как хранить результаты? На Hadoop мы понимали, как рисовать графики — но как это делать на волшебном ClickHouse? Волшебная палочка в комплект не входит. Как доставлять результаты до time series storage?
Как говорил мой лектор в институте, рассмотрим 3 схемы данных: стратегическая, логическая и физическая.
Стратегическая схема хранения
У нас есть 2 дата-центра. Мы узнали, что ClickHouse умеет ничего не знать про ДЦ, и просто засетапили в каждом ДЦ по кластеру. Теперь данные не переезжают через кросс-атлантический кабель — все данные, которые произошли в ДЦ, хранятся локально в своем кластере. Когда хотим произвести запрос над объединенными данными, например, узнать, сколько регистраций в обоих ДЦ, ClickHouse дает нам эту возможность. Низкая задержка и доступность для запроса — просто шедевр!
![](https://habrastorage.org/webt/mr/dc/-2/mrdc-205rzloz1c2oemavnikcxu.jpeg)
Физическая схема хранения
Опять вопросы: как наши данные лягут в реляционную модель ClickHouse, что сделать, чтобы не потерять репликацию и шардинг? В документации ClickHouse всё обширно описано, и если у вас больше, чем один сервер, вы наткнетесь на эту статью. Поэтому не будем углубляться в то, что есть в мануале: репликации, шардирование и запросы ко всем данным по шардам.
Логическая схема хранения
Логическая схема — это самое интересное. В одном pipeline мы обрабатываем гетерогенные события. Это означает, что у нас есть поток гетерогенных событий: регистрации, голоса, заливки фотографий, технические метрики, трекинг поведения пользователей — все эти события обладают совершенно разными атрибутами. Например, я посмотрел экран на мобилке — мне нужен id экрана, я за кого-то проголосовал — надо понимать, голос был за или против. Все эти события обладают разными атрибутами, по ним рисуются разные графики, но все это надо обрабатывать в едином pipeline. Как это засунуть в модель ClickHouse?
Подход №1 — per event table. Этотпервый подход, мы экстраполировали с опыта, полученного с MySQL — создавали табличку под каждый эвент в ClickHouse. Звучит довольно логично, но мы натолкнулись на ряд сложностей.
У нас нет ограничения на то, что событие изменит свою структуру, когда выйдет сегодняшний билд. Патчем это может сделать любой разработчик. Схема вообще во все стороны мутабельная. Единственное required-поле — это timestamp-событие и что за событие было. Все остальное меняется на лету, и, соответственно, эти таблички надо модифицировать. В ClickHouse есть возможность выполнять ALTER на кластере, но это деликатная нежная процедура, которую сложно автоматизировать, чтобы она работала безотказно. Поэтому это минус.
У нас больше тысячи различных событий, что дает нам высокий INSERT rate на одну машину — все данные постоянно записываем в тысячу таблиц. Для ClickHouse это анти-паттерн. Если у Пепси слоган — «Живи большими глотками», то у ClickHouse — «Живи большими батчами». Если этого не делать, то захлебывается репликация, ClickHouse отказывается принимать новые вставки — неприятная схема.
Подход №2 — широкая таблица. Сибирские мужики попробовали подсунуть бензопиле рельсу и применить другую модель данных. Мы делаем таблицу с тысячей колонок, где у каждого события зарезервированы колонки под его данные. У нас получается огромная разреженная таблица — к счастью это не ушло дальше разработческой среды, потому что уже с первых вставок стало ясно, что схема абсолютно плохая, и мы так делать не будем.
Но все-таки хочется использовать такой классный программный продукт, чуть-чуть еще допилить — и будет что надо.
Подход №3 — generic таблица. У нас есть одна огромная таблица, в которой мы храним данные в массивах, потому что ClickHouse поддерживает нескалярные типы данных. То есть мы заводим колонку, в которой хранятся названия атрибутов, и отдельно колонку с массивом, в котором хранятся значения атрибутов.
![](https://habrastorage.org/webt/ot/s3/ro/ots3ronbcjh69ssxujqvbuxeghc.jpeg)
ClickHouse здесь свою задачу выполняет очень хорошо. Если бы нам надо было только вставлять данные, мы бы, наверное, еще раз в 10 больше на текущей инсталляции выжали.
Однако, ложка дегтя в том, что это тоже анти-паттерн для ClickHouse —хранить массивы строк. Это плохо, потому что массивы строк занимают больше места на диске — они хуже жмутся, чем простые колонки, и сложнее в обработке. Но для нашей задачи мы на это закрываем глаза, так как достоинства перевешивают.
Как делать SELECT из такой таблицы? У нас задача — посчитать регистрации, сгруппированные по полу. Надо сначала в одном массиве найти, какая позиция соответствует колонке gender, потом в другую колонку залезть с этим индексом и достать данные.
![](https://habrastorage.org/webt/gh/pw/ek/ghpwekgjjrr0eisi8_zjmoi48tk.jpeg)
Как рисовать графики на этих данных
Поскольку все эвенты описаны, у них есть строгая структура, каждому типу события мы формируем четырехэтажный SQL-запрос, выполняем его и сохраняем результаты в другую таблицу.
Проблема в том, что для отрисовки двух соседних точек на графике, надо отсканировать всю таблицу. Пример: мы смотрим регистрации за сутки. Это событие от самой верхней строки до предпоследней. Просканировали один раз — отлично. Через 5 минут хотим нарисовать новую точку на графике — опять сканируем диапазон данных, который пересекается с предыдущим сканированием, и так — для каждого события. Звучит логично, но выглядит не здорово.
Вдобавок, когда мы берем какие-то строки, нам надо еще считать результаты под агрегации. Например, есть факт, что раб божий зарегистрировался в Скандинавии и был мужчиной, и нам надо посчитать сводную статистику: сколько всего регистраций, сколько мужчин, сколько из них людей, и сколько из Норвегии. Это называется в терминах аналитических баз данных ROLLUP, CUBE и GROUPING SETS — одну строчку превратить в несколько.
Как лечить
К счастью, в ClickHouse существует инструмент для решения этой проблемы, а именно — сериализованное состояние агрегатных функций. Это означает, что можно какой-то кусочек данных просканировать один раз и сохранить эти результаты. Это прямо киллер-фича. Мы 3 года назад сделали точно такую на Spark и Hadoop, и круто, что параллельно с нами лучшие умы Яндекса реализовали в ClickHouse аналог.
Медленный запрос
У нас есть медленный запрос — посчитать уникальных юзеров за сегодня и вчера.
SELECT uniq(user_id)
FROM table
WHERE dt IN (today(), yesterday())
В физическом плане мы можем сделать SELECT для состояния за вчерашний день, получить его бинарное представление, куда-то сохранить.
SELECT uniq(user_id),
‘xxx’ AS ts,
uniqState(user id) AS state
FROM table
WHERE dt IN (today(), yesterday())
Для сегодняшнего дня у нас просто меняется условие, что это будет сегодня:
‘yyy’ AS ts
и WHERE dt = today()
и timestamp мы назовем “xxx” и “yyy”. Потом из таблицы с этими бинарными данными селектим и получаем в итоге, сколько у нас было людей вчера и сегодня, то есть за 2 дня.SELECT uniqMerge(state)
FROM ageagate_table
WHERE ts IN (‘xxx’, ‘yyy’)
Инкрементальные вычисления
Тактика такова:
- квантуем время на минуты, над данными за минуту выполняем этот запрос;
- получаем результаты агрегатных функций в сериализованном виде;
- объединяем результаты.
То есть мы за сутки не сканируем все эвенты, а сливаем какое-то количество часовых или минутных промежуточных результатов. Эта техника позволила нам снизить время обработки в разы. Точных цифр не скажу, но она помогла нам выжить, потому что, когда мы сказали, что теперь всё классно считаем в ClickHouse, люди обрадовались: «О, теперь вам можно еще эвентиков налить! Давайте, сейчас посмотрим на вас!»
Как хранить эти результаты
Как мы помним, если у нас будет тысяча таблиц, это не очень хорошо. Поэтому результаты агрегации нужно сливать в одну таблицу, в которой будем хранить имена агрегатных функций и их значения. Это опять два массива. Преимущество этой таблицы — к ней можно написать SQL-запрос, который позволит аналитикам колдовать над этими результатами. Классно, что можно выполнить любые запросы над исходными данными, но результаты агрегаций тоже полезная информация.
![](https://habrastorage.org/webt/df/0b/o6/df0bo6pb7l6t94sxnrwtpctfcxk.jpeg)
Мы не забываем о том, что надо как-то нарисовать time series. Реализуем это посредством функциональности материализованных представлений: как только добавляем данные в таблицу, срабатывает триггер, который делает вставку в другую таблицу, предназначенную только для time series.
Таблица для time series проста как атом водорода: колонка с датой, колонка с именем метрики, колонка с точным timestamp и колонка со значением точки на графике. Когда вставляем результат выполнения агрегации над конкретным событием, у нас срабатывает триггер, который выглядит следующим образом. Нам надо получить имя метрики из таблички с массивами и взять от него хэш. Это важный момент, потому что в отличие от большинства баз данных, мы не храним здесь текстовое представление метрики, а берем от него хэш — это круто, он занимает меньше места. Да, математически у него возможны коллизии, но в ClickHouse существует хэш-функция, которая дает очень крутое распределение, и мы ее радостно используем.
Когда надо получить данные на клиенте, мы не знаем реальное значение этого хэша, и отдаем его на откуп ClickHouse:
— Вот тебе метрика «регистрация мужчин», так она выглядит — поколдуй и сделай из нее хэш.
Это дает нам выборку time series за 2 недели, то есть точки для 20 метрик за 20-80 мс. Это крутой результат. В ClickHouse есть функционал GraphiteMergeTree, который позволяет хранить time series, но наш подход круче и производительнее.
Параметры системы процессинга
8 серверов ClickHouse, из которых 6 в дата-центре в Европе, а 2 в Америке: фактор репликации равен 2 — в одном шарде два сервера, и нам этого достаточно. В пике 1.8 млн. событий в секунду на вход, а выходная мощность системы 500 тысяч метрик в секунду. Представляете, нам прилетает 1,8 млн, и мы умудряемся усиливать и выдавать 500 тысяч! Суммарно наша система знает больше миллиарда метрик.
Последствия перехода с Hadoop
Снижение задержки в 2 раза. Мы стали рисовать точки на графиках в два раза чаще. Экономия по памяти в 3 раза, а по CPU — в 4. Я считаю, что это очень внушительные и исключительно позитивные результаты.
Итоги фазы Process
Мы научились поэтапно выбирать технологии под нашу задачу, решили, что будем измерять, собрали данные в кучу и сделали крутейший процессинг. Это не значит, что вы прочитаете доклад, тут же возьмете ClickHouse и начнете писать 3 000 событий в секунду. Да, это возможно, но и возможно, что под вас это будет overkill.
Мы научились выбирать, а вы узнали, как мы проходили этот путь. Мы выбрали ClickHouse, и пока не нащупали его пределов. Уверен, что модель, которую мы используем, позволит масштабироваться и дальше. По моим оценкам, эти 8 серверов позволят пережить пик в 3–4 миллиона событий без добавления железа. Выше — решать проблемы вместе с железом.
Фаза Present — доступ к данным
Что с этими данными произойдет дальше, кто с ними будет работать? Мы нарисуем time series, у нас даже есть дашборды из time series, где разные люди из компании смотрят и пишут друг другу вопросы, почему здесь так, а здесь так.
![](https://habrastorage.org/webt/82/uo/qq/82uoqq4jw6amtxph_jgcjpjnwkm.jpeg)
Drop Detect — поиск отклонений на SQL: нехитрым SQL-запросом к таблице с результатами, которые мы посчитали по эвентам, можно выявить самое необычное поведение метрик.
![](https://habrastorage.org/webt/ru/1v/up/ru1vuporiqj-suncjyc-sh06xrk.jpeg)
Это пока никакой не Anomaly Detection — простое сравнение нескольких точек на графике. У нас есть инструмент, который рассказывает, что голоса за профили просели на 2% в таком разрезе, а в другом — выросли на 40, и среди всех метрик, которые случились по этому эвенту, а их может быть десятки и сотни тысяч, это наиболее значимое изменение.
Это все здорово — можно построить мониторинг, задать границы, но хочется чего-то большого, поэтому приходит настоящий Anomaly Detection.
Anomaly Detection
Это система, которая берет time series данные. Они у нас иерархические: регистрация, регистрация мужчин, регистрация мужчин в Скандинавии. Для каждого time series графика вычисляются предсказание и границы. В случае, если актуальное поведение метрики выходит за границы, производится алертинг. Мы еще ранжируем примерно, как делает drop detection — когда видим наиболее интересные и значимые аномалии, производим алертинг.
Примерно так выглядит наш UI.
![](https://habrastorage.org/webt/sk/mg/hn/skmghn7mirsubwu-vpm2dq5tols.jpeg)
Аномалии отсортированы по весу. Есть какой-то внешний слой, который вылез за ширину доверительного интервала — серой области. Этим пользуются и продакт-менеджеры, и разработчики.
Итоги фазы Present
На основании классных технологий, которые мы применили на прошлом шаге, у нас появилась возможность рисовать графики. Отдел мониторинга теперь может настроить алертинг на основании жестких правил, которые задают разработчики: если метрика падает ниже 1000 — alarm, если метрика отлична от 0 — alarm. БД нам это позволяет.
Прикрутили наш Anomaly Detection со своей структурой данных, с табличками и колонками. Раньше у нас Anomaly Detection жил в аналитической системе Exasol, а теперь его данные также хранятся в ClickHouse. Этот переход позволил нам также ускорить процесс Anomaly Detection в 2 раза, просто сменив базу.
Заключительная фаза
Подытожим всё, что узнали, рассмотрев 4 этапа жизненного цикла статистики.
Мы поняли, зачем собирать статистику в проекте, и познакомились с техниками сбора, которые можно для этого применить. Мы рассмотрели, как поэтапно изменять стек технологий, и как мы прошли это изменение. Узнали, какие инструменты можно построить на базе статистических данных.
До следующего большого московского HighLoad++ еще далеко, но до ноября HighLoad++ успеет побывать в Санкт-Петербурге в апреле и в Новосибирске в июне. Это будут абсолютно самостоятельные конференции со своей программой, поэтому если вы хотите быть в курсе всего, вам нужно посетить их все :)
Кроме того, на май намечена PHP Russia и, какое совпадение, Александр Крашенинников входит в её программный комитет. То есть, возможно, именно тот человек, который ускорил обработку событий д 1,8 млн/с, будет помогать вам с докладом, если вы подадите заявку до 1 апреля.