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

Highload

Безопасность в масштабе HighLoad магия или realtime?

16.04.2021 16:20:16 | Автор: admin

Миллионы запросов в секунду. Сотни серверов с десятками ядер и терабайтами оперативной памяти. Много пользователей и данных. И их становится всё больше. Да, это всё HighLoad. Но HighLoad не только это.

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

Но что насчет безопасности и защиты данных при высоких нагрузках? Что думают разработчики и эксперты о внешних угрозах, которые тоже могут вывести систему из строя? О сохранении данных, их правильной передаче и использовании? Артём Гавриченков в Программном комитете отвечает за эту область на конференции HighLoad++. Сегодня он расскажет, чем наша долгожданная офлайн-встреча Highload++ Весна 2021 будет интересна и полезна любому разработчику. Доклады на конференции будут и о безопасности, и о шифровании, и о биометрии, и, конечно, о многих других смежных с безопасностью темах.

Артем, чем ты сейчас занимаешься и какое отношение имеешь именно к высоким нагрузкам?

На протяжении 10 лет я строил продукт Qrator по защите DDoS-атак. Тут, наверное, сразу понятно, где высокие нагрузки. Последние 5 лет был техническим директором этого продукта, даже больше пяти. В данный момент я директор по продукту в Servers.com это компания, которая занимается предоставлением инфраструктурных решений (bare metal cloud, облачное хранение данных, фаерволы, Managed Kubernetes), то есть опять же решения для больших нагруженных проектов.

Как давно ты в Программном комитете и как ты в него пришел?

Я выступал несколько раз на Highload, начиная с 2011 года. А в 2018-м мы пообщались с Олегом Буниным, и он пригласил меня в ПК как эксперта по защите информации, по высоким нагрузкам в плане DDoS-атак и по масштабируемым системам по сети и инфраструктуре.

Чем тебе нравится быть в ПК? Что это для тебя?

Это возможность пообщаться с очень интересными людьми, высокими профессионалами своего дела, как с членами ПК, так и докладчиками. Вы можете увидеть прямо весь bleeding edge технологий самые модные, и при этом уже немного проверенные в деле. То есть можно узнать, какие направления есть в индустрии, куда это все идет, что появляется каждый год, а на что не стоит тратить время. Программа HighLoad++ сама по себе дает такое понимание людям, поэтому и стоит на него ходить.

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

Какие темы нас ждут в разделе безопасности в этом году на конференции?

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

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

Также будет тема защиты систем управления базами данных (СУБД), отвечающая на не совсем тривиальные вопросы. Как усиление безопасности БД влияет на производительность? Что, если мы хотим применять защищенные соединения? Что, если у нас данные нужно хранить в зашифрованном виде? Как организовать аудит?

Еще будет близкая к этому тема шифрования соединений. Сейчас практически все соединения в интернете от браузера или мобильного приложения до сервера надежно шифруются, но криптография всегда повышает нагрузку. У нас будет Александр Крижановский (Tempesta Technologies) с рассказом о том, как написать быстрый TLS-хендшейк в ядре Linux. Это такой хендшейк, который эффективней на десятки процентов, чем стандартное решение он дает меньшее время задержки, то есть готов работать под большой нагрузкой.

Будет доклад от РТЛабс про единую биометрическую систему. Это мультивендорная система, которая обеспечивает возможность работы с любыми модальностями биометрии (лицо, голос и т.д.). Биометрия в последнее время в России это очень активный хайп, развертывание биометрической аутентификации в приоритете у правительства. И конечно, у людей есть опасения, как это может работать и что может пойти не так вопрос на самом деле серьезный. Поэтому мы включили в программу этот доклад, чтобы люди, кому важно и/или интересно устройство единой биометрической системы, могли узнать, как она работает.

Система РТЛабс рассчитана на 150 млн человек, и этого должно хватить для 136 миллионов россиян. Хотя в реальности, конечно, будет меньше. Но, с другой стороны, может быть, ее удастся расширить и на таможенный союз, например, на Беларусь.

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

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

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

Хочу заметить, что проблема спама никогда никуда не уйдет. Есть даже категория шуток в IT-сообществе про Final Ultimate Solution to the Spam Problem. Примерно раз в год в каких-нибудь списках рассылки появляется человек, который утверждает, что он нашел уникальное полное решение проблемы спама и чего на практике никогда не случается.

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

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

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

По смежной теме будет доклад Василия Сошникова про API Gateway. Эта технология реально популярный хайп последних лет. Она очень часто встречается, в том числе, в over-engineering проектах. Поэтому в докладе будет обзор концепции и того, какие плюшки это нам дает и какие сложности возникают в эксплуатации. Что поможет найти ответ на вопрос: для нашего проекта API Gateway это полезный момент или просто хайп, на который не нужно тратить время?

Это действительно интересно. А ты сам на HighLoad++ собираешься посетить только то, что описал, или тебя интересуют еще какие-то доклады и мероприятия?

Уже принято 75 докладов, и есть, конечно, интересные и не только про безопасность. Например, доклад Сбербанка про борьбу с TCP Incast. Это как раз инфраструктурная часть как устроены центры обработки данных и как устроена сеть на большом проекте. Там возникают разные проблемы.

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

Ты часто бываешь на конференциях?

С 2017 года я не пропускал ни одного HighLoad++, в том числе ни одного питерского, и ни одного РИТа, правда, в Сибирь я не ездил ни разу.

Ты сам предпочитаешь онлайн или офлайн?

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

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

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

Доклад не должен быть комедийным. Если посмотрим, например, боевики последних лет (Мстители и т.д.), увидим, что там много времени уходит на юмор, перебивки, прибаутки и прочие штуки. То есть доклад не должен быть совсем уж развлекательным, но крайне желательно, чтобы часть entertainment в нем тоже присутствовала.

Что такое Highload лично для тебя? Как бы ты описал его человеку, который не член ПК, а просто приходит туда по собственному желанию?

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

Конференция HighLoad++ 2021 пройдет уже 17 и 18 мая в Москве, в Крокус-экспо. Сейчас открыт дополнительный прием докладов по новым темам. Если вы хотите поделиться тем важным и интересным, что вы нашли, разработали и открыли во время онлайна в пандемию мы вас ждем!

Билеты можно купить здесь. И да, цена с 30 апреля вырастет, для решения осталось 2 недели.

Подписывайтесь на наши новости о конференции, чтобы быть в курсе всех изменений, новинок и интересностей!

Телеграм здесь и здесь, FaceBook, VK, Twitter.

Подробнее..

Приходи, общайся и слушай. Выходи из внутреннего бега

10.05.2021 16:07:24 | Автор: admin

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

Евгений основал компанию по разработке высоконагруженных проектов Netstream (online-вещание и видео), а в 2012 году вместе со всей командой перешел в ivi, где является СТО до сих пор. C 2006 года преподает в МГТУ им. Баумана авторский курс Технологии командной разработки ПО, потому что однажды обнаружил, что не может найти грамотных разработчиков в команду.

Такой же стиль у него в Программном комитете HighLoad++ находить интересных спикеров и животрепещущие темы, чтобы показать их широкой аудитории и продвинуть в будущее. Накануне конференции мы встретились с Евгением, чтобы расспросить о докладах и спикерах HighLoad++ Весна 2021. Что было бы интересно, а что просто обязательно надо идти и слушать, если вы разработчик высоконагруженных систем.

Женя, расскажи, чем ты занимаешься и какое отношение имеешь именно к высоким нагрузкам?

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

Какие доклады HighLoad++ ты курируешь как член Программного комитета?

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

Тебе нравится быть в ПК? Что это для тебя?

Да, мне нравится. Это прекрасное времяпрепровождение по четвергам вечером :)

Но на самом деле это дает много возможностей.

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

Ты сам часто бываешь на конференциях?

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

Но офлайн круче онлайна?

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

Я и выступать онлайн не люблю, потому что ты не понимаешь аудиторию. Говорить в монитор можно 5 минут или даже 10. Но за 40 минут, пока что-то рассказываешь, ты знать не знаешь может, все уже ушли кофе пить, а ты распинаешься. Я предпочитаю видеть аудиторию. Поэтому я за то, чтобы люди приходили и смотрели офлайн. Плюс я считаю, что конференция хороша, когда это совместная работа с обеих сторон и докладчиков, и слушателей которые к тому же находятся в одном помещении. Когда ты пришел, тебе уже, черт возьми, неудобно перед человеком, который стоит на сцене! Ты, конечно, в телефончике можешь что-то тыкать, но общий коэффициент полезного действия от слов докладчика сильно выше.

Как часто ты сам выступаешь?

На HighLoad я выступал последний раз в 2018 году, и у меня было 3 доклада. Пару раз был спикером на TeamLead Conf. А сейчас у меня пока нет таких вещей, которыми хотелось бы похвастаться. Можно, конечно, из пальца высосать какую-то тему. Даже она кому-то будет интересна потому что всегда найдутся люди, у которых нет твоего опыта и твои слова будут им нужны и интересны. Но хочется же, чтобы доклад нравился самому себе.

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

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

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

В целом же ты должен понимать запросы аудитории, чтобы предложить конкретный практический опыт в том, о чем говоришь. Тогда можно привести примеры, которые зайдут аудитории и люди поймут, зачем им это нужно. Важно говорить на одном языке с аудиторией. И подсвечивать, не скрывая, проблемы, которые были у тебя. Есть доклады: У нас было все прекрасно! Сейчас мы сделали все еще более замечательно! но так не бывает. У всех всегда есть большая бочка дегтя и всего одна ложка меда, а все только про нее и рассказывают. А самое важное это как раз то, как бочку расхлебать.

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

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

Что же ждет нас в этом году на HighLoad++ 2021?

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

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

Хороший доклад от Ozon про Data Management Platform. Будет интересно узнать, как ребята автоматизировали фильтрацию пользовательских событий. Сама тема мне тоже довольно близка, поэтому я с удовольствием послушаю.

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

Также всегда очень интересно послушать Андрея Аксенова даже когда он несет какую-то дичь, все равно это интересная дичь. В этот раз у него будет прикладная эзотерика. Хочу это послушать, потому что 80% его докладов фееричные. Ну и просто интересно узнать, что у человека в голове.

Доклад Погружение в Helm package manager пришел к нам по рекомендации, и после проверки тоже вошел в программу. Олег Вознесенский из X5 Retail group расскажет про Helm. Это как раз тот случай, когда мне интересно послушать конкретные use case и какие-то вещи, которых в документации нет, а человек уже их попробовал и у него есть экспертиза. И Олег очень просто и на пальцах объясняет те штуки, которые надо долго пробовать, чтобы накопить эту экспертизу.

Также всегда приятно послушать Алексея Миловидова. Он говорит про интересные вещи, которые иногда становятся основой для философии. К тому же мне очень интересно всё, что касается ClickHouse. В ivi мы его применяем тоже, и поэтому эта тема всегда очень острая для нас.

Еще в этом году будут выступать Коля Самохвалов, а Костя Осипов покажет паксос в картинках. Костя блестящий инженер и всегда режет правду-матку, не стесняясь ни регалий, ничего. Хочет кого-то отчихвостить расскажет всё, что думает. У него может быть честный, искренний доклад, а я такие люблю.

Денис Рожков и Георгий Тарасов в Информационной безопасности против HighLoad раскроют нужную и одновременно интересную тему про кибербезопасность. А Mail.ru расскажет историю про версионирование данных на Tarantool.

А еще Саша Тоболь! Мой хороший друг, прекрасный инженер, который великолепно рассказывает, прекрасно разбирается в материале и всегда очень глубоко погружается во все темы. Саша бывший лид Одноклассников, а сейчас СТО в ВК. Его всегда классно послушать, у него редкое сочетание, когда и докладчик хороший, и инженер классный.

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

В общем и целом какие боли затрагивает эта конференция и чем она поможет слушателям?

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

И даже если у тебя была аналогичная задача, в твоей коллекции появится еще одна тропинка, по которой кто-то сходил и чего-то добился (либо нет). Ты можешь свое текущее решение сравнить с тем, что рассказывают коллеги на сцене. И даже можешь подойти потом и сказать: Ребята, вы идиоты! Зачем вы это сделали, можно ведь так, так и так. Тут неожиданно выясняется, что у тебя одна специфика, а у них другая, но вы находите какие-то интересные мысли друг у друга, и всем становится хорошо. Где вы такое найдете в онлайне или в статье?

Последний вопрос: что HighLoad лично для тебя?

Сначала это была просто тусовочка, а сейчас это работа, хобби, увлечение и сообщество.

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

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

С каким настроением идти на HighLoad++?

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

КонференцияHighLoad++ Весна 2021уже готовится к посадке 17 и 18 мая в Москве в Крокус-экспо мы все встретимся и обсудим всё то, что невозможно было обсудить в чатиках. Расписание ждет вас, а там в течение всех двух дней 8 потоков выступлений, 6 мастер-класов и пивная вечеринка!

Билеты можно купитьздесь. А подписавшисьна нашу рассылку, можно получить материалы мини-конференции Saint HighLoad++ 2020 :)

Подробнее..

Перевод Улучшаем производительность Java-микросервиса парой простых приемов

10.03.2021 18:12:48 | Автор: admin

Привет, Хабр. Для будущих студентов курса "Highload Architect" подготовили перевод материала.

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


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

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

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

Что будем использовать

Мы будем использовать два микросервиса:

  • External-service (внешний сервис): "реальный" микросервис, доступный по HTTP.

  • Facade-service (фасад): микросервис, который будет читать данные из external-service и отправлять результат клиентам. Будем оптимизировать этот сервис.

Что нам нужно

  • Java 8

  • Jmeter 5.3

  • Java IDE

  • Gradle 6.6.1

Исходный код

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

External service

Сервис был создан с помощью Spring Initializer. В нем один контроллер, имитирующий нагрузку:

@RestController public class ExternalController {   @GetMapping(/external-data/{time})  public ExternalData getData(@PathVariable Long time){  try {  Thread.sleep(time);  } catch (InterruptedException e) {  // do nothing  }  return new ExternalData(time);  } }

Запустите ExternalServiceApplication. Сервис должен быть доступен по адресу https://localhost:8543/external-data/300 .

Facade service

Этот сервис также был создан с помощью Spring Initializer. В нем два основных класса: ExternalService и ExternalServiceClient.

Класс ExternalService читает данные из сервиса External Service с помощью externalServiceClient и вычисляет сумму.

@Service public class ExternalService {   @Autowired  private ExternalServiceClient externalServiceClient;   public ResultData load(List<Long> times) {  Long start = System.currentTimeMillis();  LongSummaryStatistics statistics = times  .parallelStream()  .map(time -> externalServiceClient.load(time).getTime())  .collect(Collectors.summarizingLong(Long::longValue));  Long end = System.currentTimeMillis();  return new ResultData(statistics, (end  start));  } }

Для чтения данных из external service класс ExternalServiceClient использует библиотеку openfeign. Реализация HTTP-клиента на основе OKHttp выглядит следующим образом:

@FeignClient( name = external-service, url = ${external-service.url}, configuration = ServiceConfiguration.class) public interface ExternalServiceClient {   @RequestMapping(  method = RequestMethod.GET,  value = /external- data/{time},  consumes = application/json)  Data load(@PathVariable(time) Long time); }

Запустите класс FacadeServiceApplication и перейдите на http://localhost:8080/data/1,500,920,20000.

Ответ будет следующим:

{  statistics: {  count: 4,  sum: 1621,  min: 1,  max: 920,  average: 405.25  },  spentTime: 1183 }

Подготовка к тестированию производительности

Запустите Jmeter 5.3.1 и откройте файл perfomance-testing.jmx в корне проекта.

Конфигурация теста:

Нагрузочный тест будем проводить по следующему URL-адресу: http://localhost:8080/data/1,500,920,200

Перейдите в Jmeter и запустите тест.

Первый запуск Jmeter

Сервер стал недоступен. Это связано с тем, что в ExternalService мы использовали parallelStream(). Stream API для параллельной обработки данных использует ForkJoinPool. А по умолчанию параллелизм ForkJoinPool рассчитывается на основе количества доступных процессоров. В моем случае их три. Для операций ввода-вывода это узкое место. Итак, давайте увеличим параллелизм ForkJoinPool до 1000.

-Djava.util.concurrent.ForkJoinPool.common.parallelism=1000

И запустим Jmeter еще раз.

Второй запуск Jmeter

Как вы видите, пропускная способность (throughput) увеличилась с 6 до 26 запросов в секунду. Это хороший результат. Кроме того, сервис работает стабильно без ошибок. Но тем не менее среднее время (average time) составляет 9 секунд. У меня есть предположение, что это связано с затратами на создание HTTP-соединение. Давайте добавим пул соединений:

@Configuration public class ServiceConfiguration {      @Bean  public OkHttpClient client()  throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, NoSuchProviderException {     okhttp3.OkHttpClient client = new okhttp3.OkHttpClient.Builder()  .sslSocketFactory(sslContext.getSocketFactory(), trustManager)  .hostnameVerifier((s, sslSession) -> true)  .connectionPool(new ConnectionPool(2000, 10, TimeUnit.SECONDS))  .build();   OkHttpClient okHttpClient = new OkHttpClient(client);   return okHttpClient;  }

Таким образом, приложение может поддерживать до 2000 HTTP-соединений в пуле в течение 10 секунд.

Третий запуск Jmeter

Пропускная способность улучшилась почти в три раза: с 26 до 71 запросов в секунду.

В целом пропускная способность улучшилась в 10 раз: с 6 до 71 запросов / сек, но мы видим, что максимальное время запроса (maximum time) составляет 7 секунд. Это много и влияет как на общую производительность, так и на задержку в UI.

Поэтому давайте ограничим количество обрабатываемых запросов. Сделать это можно, используя указанные ниже свойства Tomcat в application.properties:

server.tomcat.accept-count=80server.tomcat.max-connections=80 server.tomcat.max-threads=160

Приложение будет отклонять запросы на подключение и отвечать ошибкой "Connection refused" (отказ соединения) всем клиентам, как только количество подключений достигнет 160.

Четвертый запуск Jmeter

Теперь максимальное время составляет меньше пяти секунд и число запросов увеличилось с 71 до 94 запросов в секунду. Процент ошибок ожидаемо увеличился до 29%. Это все ошибки "Connection refused".

Заключение

В этой статье мы продемонстрировали реальный сценарий повышения производительности в 15 раз с 6 до 94 запросов / сек без каких-либо сложных изменений кода. Кроме того, упомянутые выше шаги позволяют снизить стоимость инфраструктуры, такой как AWS. Возможно, для вашего следующего проекта вам стоит подумать об использовании микросервисов. Хотя одна из тенденций последних лет переход к бессерверной архитектуре, но вы должны всё взвесить при переходе к такой архитектуре.

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


Узнать подробнее о курсе "Highload Architect".

Смотреть открытый вебинар по теме Репликация как паттерн горизонтального масштабирования хранилищ.

Подробнее..

Выбор архитектурного стиля (часть 2)

17.09.2020 12:16:31 | Автор: admin
Привет, хабр. Сегодня я продолжаю серию публикаций, которую написал специально к старту нового потока курса Software Architect.



Введение


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

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

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

Компонентно-ориентированная архитектура


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

При грамотном использовании компонентов решается проблема большого кома грязи (большой размер + высокая связанность), а сами компоненты могут представлять из себя как единицы сборки (модули, библиотеки), так и единицы развертывания (сервисы). Единицы развертывания не всегда отображаются на выполняемый процесс: например, веб-приложение и база данных развертываются совместно.

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

Самая главная проблема такого монолита заключается в том, что разделение на модули является чисто логическим и может быть легко нарушено разработчиками. Может появиться модуль core, который постепенно превращается в помойку, может расти граф зависимостей между модулями и так далее. Для избежания таких проблем разработка должна вестить либо очень зрелой командой, либо под руководством архитектора, который на full time занимается code review и бьет нарушающих логическую структуру разработчиков по рукам.

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

Сервис-ориентированная архитектура


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

Сервис-ориентированная архитектура (SOA = service oriented architecture) решает все обозначенные проблемы монолита: при изменении затрагивается только одна служба, а четко определенный API поддерживает хорошую инкапсуляцию компонент.

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

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

Сервис-ориентированная архитектура неплохо поддерживается архитектурным коммьюнити и вендорами. Отсюда следует наличие множества курсов и сертификаций, хорошо проработанных паттернов. К последним относится, например, не безызвестная сервисная шина предприятия (ESB = enterprise service bus). При этом ESB это багаж от вендоров, она не обязательно должна использоваться в SOA.

Пик популярности сервис-ориентированной архитектуры приходился примерно на 2008 год, после чего она пошла на спад, который стал существенно более резким после появления микросервисов (~2015 год).

Заключение


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

Подробнее..

Паттерн сага как способ обеспечения консистентности данных

18.09.2020 14:13:41 | Автор: admin
Всем привет. Уже сейчас в OTUS открывает набор в новую группу курса Highload Architect. В связи с этим я продолжаю серию своих публикаций, написанных специально для этого курса, а также приглашаю вас на свой бесплатный демо урок по теме: Индексы в MySQL: best practices и подводные камни. Записаться на вебинар можно тут.



Введение


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

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

Паттерн Сага


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

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

Типов транзакций в саге несколько, целых четыре:

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

Организовывать сагу можно с помощью хореографии или оркестрации.

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

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

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

Сага позволяет добиться ACD-модели (Atomicity + Consistency + Durability в терминах ACID), но одну букву мы потеряли. Недостаток буквы I приводит к известным проблемам недостатка изолированности. К ним относятся: потерянные обновления (lost updates) одна сага перезаписывает изменения, внесенные другой, не читая их при этом, грязное чтение (dirty reads) транзакция или сага читают незавершенные обновления другой саги, нечеткое/неповторяемое чтение (fuzzy/nonrepeatable reads) два разных этапа саги читают одни и те же данные, но получают разные результаты, потому что другая сага внесла изменения. Существует ряд паттернов, позволяющих пофиксить те или иные аномалии: семантическая блокировка, коммутативные обновления, пессимистическое представление, повторное чтение значения, файл изменений и по значению. Вопрос обеспечения изоляции остается открытым.

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

Заключение


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

Подробнее..

Выбор архитектурного стиля (часть 3)

28.09.2020 02:15:18 | Автор: admin
Привет, Хабр. Сегодня я продолжаю серию публикаций, которую написал специально к старту нового потока курса Software Architect.



Введение


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

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

Сейчас мы наконец определим основные характеристики микросервисной архитектуры.

Отношение архитектур


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

Характеристики микросервисной архитектуры


Основными характеристиками микросервисной архитектуры являются:

Организация в соответствии с бизнес-возможностями
(Organized around Business Capabilities)
Продукты, а не проекты (Products not Projects)
Умные точки входа и глупые каналы (Smart endpoints and
dumb pipes)
Децентрализованное управление (Decentralized Governance)
Децентрализованное управление данными (Decentralized
Data Management)
Автоматизация инфраструктуры (Infrastructure Automation)
Страховка от сбоев (Design for failure)
Архитектура с эволюционным развитием (Evolutionary
Design)

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

Организация в соответствии с бизнес-возможностями (Organized around Business Capabilities)


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

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

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

Продукты, а не проекты (Products not Projects)


Проектный подход при котором команда передает разработанную функциональность другим командам в случае микросервисной архитектуры совершенно не подходит. Команда должна поддерживать систему на протяжении всего ее жизненного цикла. Компания Amazon, один из флагманов внедрения микросервисов, заявляла: вы создаете продукт, и вы же запускаете его (you build, you run it). Продуктовый подход позволяет команде почувствовать потребности бизнеса.

Умные точки входа и глупые каналы (Smart endpoints and dumb pipes)


SOA архитектура большое внимание уделяла каналам связи, в частности Enterprise Service Bus (сервисная шина предприятия). Что зачастую приводит к Erroneous Spaghetti Box, то есть сложность монолита переходит в сложность связей между сервисами. В микросевисной архитектуре используются только простые способы взаимодействия.

Децентрализованное управление (Decentralized Governance)


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

Децентрализованное управление данными (Decentralized Data Management)


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

Автоматизация инфраструктуры (Infrastructure Automation)


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

Страховка от сбоев (Design for failure)


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

Архитектура с эволюционным развитием (Evolutionary Design)


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

Заключение


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



Читать часть 2
Подробнее..

Выбор архитектурного стиля. Часть 4

12.10.2020 16:11:28 | Автор: admin
В конце октября запускаем новую группу курса Архитектура и шаблоны проектирования и приглашаем всех специалистов на бесплатный Demo-урок Шаблон адаптер, который проведёт Матвей Калинин главный разработчик в одном из крупнейших банков страны.




Введение


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

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

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

Проблема выбора


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

Независимое развертывание


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

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

Улучшение отказоустойчивости


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

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

Гибкость и ясность


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

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

Варьирование технологического стека


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

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

Техническая сложность


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

Распространенные заблуждения


С микросервисной архитектурой связан ряд заблуждений. Вот некоторые из них:
  1. Код будет чище. Код не будет чище, если не будут предприняты усилия, чтобы код стал чище.
  2. Писать модули, решающие одну задачу легче.
    Не легче, поскольку у таких модулей будет очень много интеграций.
  3. Это работает быстрее, чем монолит.
    Монолит работает быстрее из-за меньшего количества сетевых вызовов.
  4. Инженерам проще, если не нужно работать с единой кодовой
    базой.
  5. Это самый простой способ обеспечить автоматическое
    масштабирование.
  6. И тут где-то замешан Докер.


Заключение


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

image
Подробнее..

Как не держать лишнее железо и справляться с ростом нагрузки внедрение graceful degradation в Яндекс.Маркете

14.01.2021 12:05:51 | Автор: admin

Привет, меня зовут Евгений. Я разрабатываю инфраструктуру поиска Яндекс.Маркета. Хочу рассказать, как graceful degradation помогает нам обрабатывать больше запросов, чем физически могут выдержать наши сервера, и что происходит с поиском в Маркете, если один из дата-центров отключается.

Проблема

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

Обычное состояниеОбычное состояние

Но когда все ДЦ работают, каждый из них оказывается задействован лишь на 60%. Ещё 10% резервируются для экспериментов и непредвиденного роста, а оставшиеся 30% используются только в случае, если один из ДЦ отключается, и нужно перераспределить запросы.

ДЦ 2 отключёнДЦ 2 отключён

Если сервис работает на сотнях серверов, 30% это огромное количество железа. А поскольку отключаются ДЦ очень редко, резервные мощности почти всегда простаивают.

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

ДЦ 1 и ДЦ 3 не справляются с нагрузкойДЦ 1 и ДЦ 3 не справляются с нагрузкой

Применяем graceful degradation

Graceful degradation или изящная деградация это подход к построению систем, при котором система продолжает выполнять свою основную функцию, даже если отдельные её элементы вышли из строя.

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

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

Чтобы запустить graceful degradation, нам надо было решить две задачи:

  1. Разработать механизм уменьшения нагрузки.

  2. Сделать автоматизацию включения механизма.

Механизм уменьшения нагрузки

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

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

Таким образом, мы выбрали третий вариант снижать качество. Основное преимущество этого подхода возможность уменьшать нагрузку на 5%, 10% и так далее.

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

Бэкенд получает запрос и раcпределяет его на 8 серверов с шардами. В шардах хранятся предложения от магазинов.

Общая схема обработки поискового запросаОбщая схема обработки поискового запроса

На каждом шарде поиск проходит несколько стадий. На стадии фильтрации ищется примерно 50000 предложений, это число зависит от категории. На этапе ранжирования для каждого предложения вычисляется релевантность, учитывается цена, рейтинг товара, рейтинг магазина и ещё более 2000 факторов. ML по факторам вычисляет вес каждого предложения. Затем берётся только 48 лучших. Meta Search получает эти 48 предложений с каждого шарда, то есть всего 48*8=384 предложения. Предложения снова ранжируются, опять берётся 48 лучших. Последние 48 уже показываются пользователю. То есть чтобы показать нашим пользователям 48 телефонов, мы обрабатываем 400 000 предложений.

Количество обрабатываемых документов без graceful degradationКоличество обрабатываемых документов без graceful degradation

В случае с graceful degradation, когда надо уменьшить нагрузку, мы можем скомандовать: теперь обрабатывай 95% документов, а теперь 90% или 80%. Если обрабатывать 95%, то есть 400000*0.95=380 000 документов, то из них всё равно выбираются 48 лучших предложений для выдачи. И в среднем только 2 предложения будут отличаться от изначальной выдачи без снижения качества. При таком маленьком изменении большинство пользователей даже не заметят разницы.

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

Автоматизация включения механизма

Автоматизация работает за счёт постоянного мониторинга загрузки CPU. Если нагрузка становится выше 90%, автоматика начинает снижать качество. На 90% снижение небольшое, но если нагрузка продолжает расти, процент деградации повышается линейно и доходит до максимума при 100% загрузки CPU. Такой подход позволяет снижать качество минимально.

Общий алгоритм выглядит так:

При выключении ДЦ: балансеры перераспределяют запросы в оставшиеся ДЦ => нагрузка на CPU повышается => при превышении порогового значения происходит снижение качества по заданной формуле.

При включении ДЦ: балансеры перераспределяют запросы на все ДЦ => нагрузка на CPU снижается => понижение качества прекращается.

Повышение нагрузки при выключении ДЦ. Линии на верхнем графике показывают загрузку CPU в отдельных ДЦ. Нагрузка выросла с 82% до 98%. Нижний график показывает процент срезанных документов. Повышение нагрузки при выключении ДЦ. Линии на верхнем графике показывают загрузку CPU в отдельных ДЦ. Нагрузка выросла с 82% до 98%. Нижний график показывает процент срезанных документов.

Внедрение

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

В конце, когда всё было готово, мы провели эксперимент с отключением дата-центра, чтобы убедиться, что всё работает как надо.

Выводы

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

Подробнее..

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

27.08.2020 10:09:54 | Автор: admin
Всем привет. Уже в сентябре OTUS открывает набор в новую группу курса Highload Architect. В связи с этим я продолжаю серию своих публикаций, написанных специально для этого курса, а также приглашаю вас на свой бесплатный вебинар, в рамках которого я подробно расскажу о программе курса и формате обучения в OTUS. Записаться на вебинар можно тут.



Введение


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

Согласованность


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

Причина проблемы


Почему обеспечение согласованности затруднено именно в микросервисной архитектуре? Дело в том, что данный архитектурный стиль зачастую предполагает применение паттерна database per service. Позволю себе напомнить, что этот паттерн заключается в том, что у каждого микросервиса своя независимая база или базы (базы, потому что помимо первичного источника данных может использоваться, например, кеш). Такой подход позволяет с одной стороны не добавлять неявные связи по формату данных между микросервисами (микросервисы взаимодействуют только явно через API), с другой стороны по максимуму использовать такое преимущество микросервисной архитектуры как technology agnostic (мы можем выбирать подходящую под особую нагрузку на микросервис технологию хранения данных). Но при всем при этом мы потеряли гарантию согласованности данных. Посудите сами, монолит общался с одной большой базой, которая предоставляла возможности по обеспечению ACID транзакций. Теперь баз данных стало много, а вместо одной большой ACID транзакции у нас много небольших ACID транзакций. Нашей задачей будет объединение всех этих транзакций в одну распределенную.

Оптимистичная согласованность


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

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

Варианты обеспечения консистентности


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

С одной стороны мы можем все-таки обойти необходимость обеспечения распределенной транзакции путем стремления к service-based архитектуры (объединяем сервисы таким образом, чтобы транзакция была гомогенная). Такое решение не очень каноничное с точки зрения принципов микросервисной архитектуры, но зато оно технически намного проще из-за чего часто применяется на практике. С другой стороны мы можем оставить каноничные микросервисы, но при этом применить один из механизмов обеспечения распределенных транзакций: двухфазный коммит или сагу. В этой статье будет изучен первый вариант, а второй мы обсудим в следующий раз.

Двухфазный коммит


Механизм предельно прост: есть некоторый transaction manager, который собственно оркестрирует транзакцию. На первом этапе (prepare) transaction manager подает соответствующую команду для resource manager'ов, по которой они в свои журналы записывают данные, которые будут закоммичены. Получив подтверждение ото всех resource manager'ов об успешном завершении первого этапа, transaction manager начинает второй этап и подает следующую команду (commit), по которой resource manager'ы применяют принятые ранее изменения.

Несмотря на кажущуюся простоту, такой подход обладает рядом недостатков. Во-первых, если хотя бы один resource manager даст сбой на втором этапе, вся транзакция должна быть отменена. Таким образом, нарушается один из принципов микросервисной архитектуры устойчивость к отказам (когда мы приходили к распределенной системе, мы сразу закладывались на то, что отказ в ней является нормой а не исключительной ситуацией). Более того, если отказов будет много (а их будет много), то процесс отмены транзакций необходимо будет автоматизировать (в том числе и писать транзакции, откатывающие транзакции). Во-вторых, сам transaction manager является единой точкой отказа. Он должен уметь транзакционно выдавать id-шники транзакциям. В-третьих, поскольку хранилищу подаются специальные команды, логично предположить, что хранилище должно уметь это делать, то есть соответствовать стандарту XA, а не все современные технологии ему соответствуют (такие брокеры как Kafka, RabbitMQ и NoSQL решения как MongoDB и Cassandra не поддерживают двухфазные коммиты).

Вывод, напрашивающийся из всех этих факторов, был отлично сформулирован Крисом Ричардсоном: 2PC not an option (двухфазный коммит не вариант).

Вывод


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



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




Читать ещё:


Подробнее..

Перевод Построение инфраструктуры распределенной трассировки Netflix

15.12.2020 18:16:33 | Автор: admin

Для будущих учащихся на курсе "Highload Architect" и всех желающих подготовили перевод интересной статьи.

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


Наша группа Кевин Лью (Kevin Lew),Нараянан Аруначалам (Narayanan Arunachalam),Элизабет Карретто (Elizabeth Carretto),Дастин Хаффнер (Dustin Haffner), Андрей Ушаков,Сет Кац (Seth Katz),Грег Баррелл (Greg Burrell),Рам Вайтхилингам (Ram Vaithilingam),Майк Смит (Mike Smith)иМаулик Пандей (Maulik Pandey)

@NetflixhelpsПочему "Король тигров" не идет на моем телефоне? подписчик Netflix спрашивает через Twitter

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

Распределенная трассировка: отсутствие контекста при поиске и устранении неисправностей крупномасштабных сервисов

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

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

Рисунок 1. Поиск и устранение сбоя сеанса в EdgarРисунок 1. Поиск и устранение сбоя сеанса в Edgar

Когда четыре года назад мы начинали создавать Edgar, существовало очень мало систем распределенной трассировки с открытым исходным кодом, которые отвечали нашим потребностям. Мы решили подождать, пока эти системы достигнут зрелого состояния, и первое время собирали трассировки от Java-сервисов стриминга с помощью собственных библиотек. К 2017 году такие открытые проекты, как Open-Tracing и Open-Zipkin, достигли достаточного уровня зрелости, чтобы их можно было использовать в многоязычных средах выполнения, применяющихся в Netflix.

Наш выбор пал на Open-Zipkin, поскольку эта система лучше интегрировалась с нашей средой выполнения Java на основе Spring Boot. Мы используемMantisдля обработки потока собранных трассировок, а для хранения трассировок мы используем Cassandra. Наша инфраструктура распределенной трассировки состоит из трех компонентов: инструментарий библиотек трассировки, обработка потоков и хранилище. Трассировки, получаемые от различных микросервисов, обрабатываются как поток и перемещаются в хранилище данных. В следующих разделах описан наш путь по созданию этих компонентов.

Инструментарий трассировки: как он повлияет на наш уровень обслуживания?

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

В основе распределенной трассировки лежит распространение контекста для локальных межпроцессных вызовов и клиентских вызовов к удаленным микросервисам для любого произвольного запроса. При передаче контекста запроса фиксируются причинно-следственные связи между микросервисами во время выполнения. Мы использовали механизм распространения контекста на основезаголовков HTTP B3из Open-Zipkin. Мы следим за тем, чтобы заголовки, используемые для распространения контекста, правильно передавались между микросервисами в разнообразных средах выполнения Java и Node, которые интегрированы в нашу систему разработки ПО (внутри компании она называется paved road). В эту систему входят как базы legacy-кода, так и новые среды, например Spring Boot. Следуя принципу нашей культуры Свобода и ответственность, мы поддерживаем библиотеки трассировки и в других средах (Python, NodeJS, Ruby on Rails и др.), которые не входят в систему paved road. Нашисвободные, но высокоскоординированныеинженерные группы могут по своему усмотрению выбирать подходящую библиотеку трассировки для своей среды выполнения и отвечают за обеспечение правильного распространения контекста и интеграцию перехватчиков сетевых вызовов.

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

Обработка потока: выполнять или нет выборку данных трассировки?

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

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

В большинстве систем распределенной трассировки политика выборки применяется в точке приема запросов (если представить себе диаграмму вызовов микросервисов). Мы выбралигибридный подход к выборке данных на основе заголовков, который позволяет записывать 100% трассировок для определенного, настраиваемого набора запросов. В остальном же трафике выборка производится случайным образом в соответствии с политикой, заданной в точке приема. Такой гибкий подход позволяет библиотекам трассировки записывать все трассировки наших важнейших микросервисов стриминга, собирая при этом минимальное количество трассировок от таких вспомогательных систем, как система отложенной пакетной обработки данных. Наши инженерные группы настроили свои сервисы на максимальную производительность с учетом возросшей из-за трассировки потребности в ресурсах. Следующей проблемой была потоковая передача больших объемов трассировок через масштабируемую платформу обработки данных.

Mantis это основная платформа обработки операционных данных в Netflix. Мы выбрали платформу Mantis в качестве магистрали для передачи и обработки больших объемов данных трассировки, поскольку нам требовалась масштабируемая система потоковой обработки, способная справляться с эффектами backpressure. Наш агент сбора данных трассировки перемещает эти данные в кластер заданий Mantis с помощьюбиблиотеки Mantis Publish. Мы помещаем диапазоны в буфер на определенный период времени, чтобы собрать все диапазоны трассировки в первом задании. Второе задание забирает поток данных из первого задания, выполняет хвостовую выборку данных и записывает трассировки в систему хранения. Такая цепочка заданий Mantis позволяет нам масштабировать все компоненты обработки данных независимо друг от друга. Дополнительное преимущество использования Mantis заключается в возможности выполнять произвольный просмотр данных в режиме реального времени вRavenс помощьюязыка запросов Mantis (MQL). Однако наличие масштабируемой платформы потоковой обработки не особо помогает, если невозможно обеспечить экономичное хранение данных.

Хранилище без переплат

Сначала в качестве хранилища данных мы использовали Elasticsearch: нас привлекли гибкая модель данных и возможности обработки запросов, которые есть в этом продукте. Мы продолжали добавлять в систему все больше сервисов стриминга, и объем данных трассировки начал экспоненциально расти. Из-за высокой скорости записи данных приходилось постоянно масштабировать кластеры Elasticsearch, вследствие чего возрастала операционная нагрузка. Запросы на чтение данных выполнялись все дольше, поскольку кластеры Elasticsearch использовали значительные вычислительные ресурсы для индексирования вносимых в них трассировок. Из-за больших объемов получаемых данных со временем упала производительность и операций чтения, и операций записи. Выйти из этой ситуации нам удалось путем перехода наCassandraв качестве хранилища данных: этот продукт позволил нам справиться с большим объемом получаемых данных. Использование простых поисковых индексов в Cassandra дает нам возможность поддерживать приемлемые задержки чтения, выполняя при этом большие объемы операций записи.

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

  1. Использовать более дешевые томаElastic Block Store(EBS) вместо инстансов EC2 с SSD.

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

  3. Хранить только важные и интересные трассировки, используя для этого простые фильтры на основе правил.

Мы добавляли новые узлы Cassandra, когда на существующих узлах переполнялись хранилища инстансов EC2 с SSD. Использование более дешевых томов EBS Elastic вместо хранилищ на базе инстансов с SSD было привлекательным вариантом, поскольку AWS позволяет динамически увеличивать размер томов EBS без повторного выделения узла EC2. Это позволило нам увеличивать общую емкость хранилища, не добавляя новые узлы Cassandra в существующий кластер. В 2019 году наши замечательные коллеги из группы Cloud Database Engineering (CDE) протестировали производительность EBS для нашего сценария и перенесли существующие кластеры на тома EBS Elastic.

За счет оптимизации параметров Time Window Compaction Strategy (TWCS, стратегия уплотнения временных интервалов) они сократили количество операций записи на диск и объединения для файлов Cassandra SSTable, сократив тем самым нагрузку ввода-вывода на EBS. Эта оптимизация помогла нам сократить объем сетевого трафика, связанного с репликацией данных между узлами кластера, поскольку файлы SSTable создавались реже, чем в нашей предыдущей конфигурации. Кроме того, обеспечение возможности сжатия блоков Zstd в файлах данных Cassandra позволило наполовину уменьшить размер наших файлов данных трассировки. Благодаря этим оптимизированным кластерам Cassandra мы теперь тратим на 71% меньше средств на обеспечение работы кластеров и можем хранить в 35 раз больше данных, чем при использовании предыдущей конфигурации.

Мы заметили, что пользователи Edgar просматривали менее чем 1% собранных трассировок. Зная это, мы полагаем, что можем понизить нагрузку операций записи и помещать больше данных в систему хранения, если будем удалять трассировки, которые не нужны пользователям. В настоящее время мы используем простой фильтр на основе правил в задании Mantis по сохранению данных. Этот фильтр сохраняет интересные трассировки для путей вызовов сервисов, которые очень редко используются в Edgar. Чтобы определить, является ли трассировка интересной единицей данных, фильтр проверяет все диапазоны трассировки, помещенные в буфер, на предмет тегов предупреждений, ошибок и повторных попыток. Хвостовая выборка позволила сократить объем данных трассировки на 20% без влияния на работу пользователей. Существует возможность использовать методы классификации на основе машинного обучения, чтобы еще больше сократить объемы данных трассировки.

Несмотря на то что мы добились значительного прогресса, сейчас мы достигли очередной поворотной точки на пути построения нашей системы хранения данных трассировки. Реализация новых возможностей для пользователей Edgar может потребовать от нас хранить в 10 раз больше данных по сравнению с текущими объемами. С учетом этого сейчас мы экспериментируем с вариантом многоуровневого хранения для нового шлюза данных. Этот шлюз данных имеет интерфейс запросов, который позволяет абстрагироваться от сложностей, связанных с чтением и записью данных в многоуровневые хранилища. Кроме того, шлюз данных направляет получаемые данные в кластер Cassandra и перемещает сжатые файлы данных из кластера Cassandra в S3.Мы планируем сохранять данные за последние несколько часов в кластерах Cassandra, а остальные трассировки, хранящиеся в течение длительного времени, будут находиться в корзинах S3.

Таблица 1. Временная шкала оптимизации хранилищаТаблица 1. Временная шкала оптимизации хранилища

Дополнительные преимущества

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

Мониторинг состояния приложений

Данные трассировки являются основным сигналом, используемымTelltaleпри мониторинге состояния приложений на макроуровне в Netflix. Telltale использует причинно-следственную информацию из трассировок для определения топологии микросервисов и соотнесения трассировок с данными временных рядов изAtlas. Такой подход позволяет получить более детальную картину состояния приложений.

Проектирование устойчивости

Наша группа по хаос-инжинирингу использует трассировки для проверки правильности внесения сбоев, когда наши инженеры выполняют стресс-тесты своих микросервисов с помощью платформытестирования методом внедрения отказов(FIT).

Эвакуация из облачных регионов

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

Оценка затрат на инфраструктуру при выполнении A-/B-тестирования

Группа по data science и исследованию продуктов определяет затраты на выполнениеA-/B-тестированияна микросервисах путем анализа трассировок, в которых есть соответствующие имена и теги тестов A/B.

Что дальше?

По мере роста Netflix объем и сложность наших программных систем продолжают повышаться. При расширении Edgar мы будем уделять основное внимание следующим областям:

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

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

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

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

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


Узнать подробнее о курсе "Highload Architect".

Посмотреть открытый урок "Паттерны горизонтального масштабирования хранилищ".

Подробнее..

Реактивное программирование на Java как, зачем и стоит ли? Часть II

09.03.2021 10:13:46 | Автор: admin

Реактивное программирование один из самых актуальных трендов современности. Обучение ему сложный процесс, особенно если нет подходящих материалов. В качестве своеобразного дайджеста может выступить эта статья. На конференции РИТ++ 2020 эксперт и тренер Luxoft Training Владимир Сонькин рассказал о фишках управления асинхронными потоками данных и подходах к ним, а также показал на примерах, в каких ситуациях нужна реактивность, и что она может дать.

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

Reactivity

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

А теперь представьте, что задачу разбили на множество подзадач. И теперь они плывут непрерывным потоком:

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

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

Идея реактивности построена на паттерне проектирования Observer.

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

В данной схеме есть:

  • Publisher тот, кто публикует новые сообщения;

  • Observer тот, кто на них подписан. В реактивных потоках подписчик обычно называется Subscriber. Термины разные, но по сути это одно и то же. В большинстве сообществ более привычны термины Publisher/Subscriber.

Это базовая идея, на которой все строится.

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

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

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

От детектора дыма идет поток данных: например, значение 10, потом 12, и т.д. Температура тоже меняется, это другой поток данных 20, 25, 15. Каждый раз, когда появляется новое значение, результат пересчитывается, что приводит к включению или выключению системы оповещения. Нам достаточно сформулировать условие, при котором колокольчик должен включиться.

Если вернуться к паттерну Observer, у нас детектор дыма и термометр это публикаторы сообщений, то есть источники данных (Publisher), а колокольчик на них подписан, то есть он Subscriber, или наблюдатель (Observer).

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

Reactive approach

Рассмотрим еще один пример: допустим, мы разрабатываем UI, и нам нужно отслеживать двойные нажатия мышкой. Тройной клик будем считать как двойной.

Клики здесь это поток щелчков мышкой (на схеме 1, 2, 1, 3). Нам нужно их сгруппировать. Для этого мы используем оператор throttle. Говорим, что если два события (два клика) произошли в течение 250 мс, их нужно сгруппировать. На второй схеме представлены сгруппированные значения (1, 2, 1, 3). Это поток данных, но уже обработанных в данном случае сгрупированных.

Таким образом начальный поток преобразовался в другой. Дальше нужно получить длину списка ( 1, 2, 1, 3). Фильтруем, оставляя только те значения, которые больше или равны 2. На нижней схеме осталось только два элемента (2, 3) это и были двойные клики. Таким образом, мы преобразовали начальный поток в поток двойных кликов.

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

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

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

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

Observable example

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

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

Девушка (Publisher) опубликовала эти значения, а Observers на них подписываются и печатают значения из потока.

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

locations.subscribe(s -> System.out.println(s)))

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

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

Implementing and subscribing to an observer

В Java 9 нет реализации реактивных потоков только спецификация. Но есть несколько библиотек реализаций реактивного подхода. В этом примере используется библиотека RxJava. Мы подписываемся на поток данных, и определяем несколько обработчиков, то есть методы, которые будут запущены в начале обработки потока (onSubscribe), при получении каждого очередного сообщения (onNext), при возникновении ошибки (onError) и при завершении потока (onComplete):

Давайте посмотрим на последнюю строчку.

locations.map(String::length).filter(l -> l >= 5).subscribe(observer);

Мы используем операторы map и filter. Если вы работали со стримами Java 8, вам, конечно, знакомы map и filter. Здесь они работают точно так же. Разница в том, что в реактивном программировании эти значения могут появляться постепенно. Каждый раз, когда приходит новое значение, оно проходит через все преобразования. Так, String::length заменит строчки на длину в каждой из строк.

В данном случае получится 5 (Minsk), 6 (Krakow), 6 (Moscow), 4 (Kiev), 5 (Sofia). Фильтруем, оставляя только те, что больше 5. У нас получится список длин строк, которые больше 5 (Киев отсеется). Подписываемся на итоговый поток, после этого вызывается Observer и реагирует на значения в этом итоговом потоке. При каждом следующем значении он будет выводить длину:

public void onNext(Integer value) {

System.out.println("Length: " + value);

То есть сначала появится Length 5, потом Length 6. Когда наш поток завершится, будет вызван onComplete, а в конце появится надпись "Done.":

public void onComplete() {

System.out.println("Done.");

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

Если где-то произойдет ошибка, мы можем на нее отреагировать:

public void onError(Throwable e) {

e.printStackTrace();

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

Reactive Streams spec

Реактивные потоки вошли в Java 9 как спецификация.

Если предыдущие технологии (Completable Future, Fork/Join framework) получили свою имплементацию в JDK, то реактивные потоки имплементации не имеют. Есть только очень короткая спецификация. Там всего 4 интерфейса:

Если рассматривать наш пример из картинки про Твиттер, мы можем сказать, что:

Publisher девушка, которая постит твиты;

Subscriber подписчик. Он определяет , что делать, если:

  • Начали слушать поток (onSubscribe). Когда мы успешно подписались, вызовется эта функция;

  • Появилось очередное значение в потоке (onNext);

  • Появилось ошибочное значение (onError);

  • Поток завершился (onComplete).

Subscription у нас есть подписка, которую можно отменить (cancel) или запросить определенное количество значений (request(long n)). Мы можем определить поведение при каждом следующем значении, а можем забирать значения вручную.

Processor обработчик это два в одном: он одновременно и Subscriber, и Publisher. Он принимает какие-то значения и куда-то их кладет.

Если мы хотим на что-то подписаться, вызываем Subscribe, подписываемся, и потом каждый раз будем получать обновления. Можно запросить их вручную с помощью request. А можно определить поведение при приходе нового сообщения (onNext): что делать, если появилось новое сообщение, что делать, если пришла ошибка и что делать, если Publisher завершил поток. Мы можем определить эти callbacks, или отписаться (cancel).

PUSH / PULL модели

Существует две модели потоков:

  • Push-модель когда идет проталкивание значений.

Например, вы подписались на кого-то в Telegram или Instagram и получаете оповещения (они так и называются push-сообщения, вы их не запрашиваете, они приходят сами). Это может быть, например, всплывающее сообщение. Можно определить, как реагировать на каждое новое сообщение.

  • Pull-модель когда мы сами делаем запрос.

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

Для Push-модели мы определяем callbacks, то есть функции, которые будут вызваны, когда придет очередное сообщение, а для Pull-модели можно воспользоваться методом request, когда мы захотим узнать, что новенького.

Pull-модель очень важна для Backpressure напирания сзади. Что же это такое?

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

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

Implementations

Давайте рассмотрим существующие реализации реактивных потоков:

  • RxJava. Эта библиотека реализована для разных языков. Помимо RxJava существует Rx для C#, JS, Kotlin, Scala и т.д.

  • Reactor Core. Был создан под эгидой Spring, и вошел в Spring 5.

  • Akka-стримы от создателя Scala Мартина Одерски. Они создали фреймворк Akka (подход с Actor), а Akka-стримы это реализация реактивных потоков, которые дружат с этим фреймворком.

Во многом эти реализации похожи, и все они реализуют спецификацию реактивных потоков из Java 9.

Посмотрим подробнее на Springовский Reactor.

Function may return

Давайте обобщим, что может возвращать функция:

  • Single/Synchronous;

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

  • Multipple/Synchronous;

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

  • Single/Asynchronous;

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

  • либо CompletableFuture (Java), и через какое-то время приходит асинхронный ответ;

  • либо Mono, возвращающая одно значение в библиотеке Spring Reactor.

  • Multiple/Asynchronous.

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

Например, вы читаете файл, а он меняется. В случае Single/Asynchronous вы через какое-то время получаете целиком весь файл. В случае Multiple/Asynchronous вы получаете поток данных из файла, который сразу же можно начинать обрабатывать. То есть можно одновременно читать данные, обрабатывать их, и, возможно, куда-то записывать. . Реактивные асинхронные потоки называются:

  • Publisher (в спецификации Java 9);

  • Observable (в RxJava);

  • Flux (в Spring Reactor).

Netty as a non-blocking server

Рассмотрим пример использования реактивных потоков Flux вместе со Spring Reactor. В основе Reactor лежит сервер Netty. Spring Reactor это основа технологии, которую мы будем использовать. А сама технология называется WebFlux. Чтобы WebFlux работал, нужен асинхронный неблокирующий сервер.

Схема работы сервера Netty похожа на то, как работает Node.js. Есть Selector входной поток, который принимает запросы от клиентов и отправляет их на выполнение в освободившиеся потоки. Если в качестве синхронного сервера (Servlet-контейнера) используется Tomcat, то в качестве асинхронного используется Netty.

Давайте посмотрим, сколько вычислительных ресурсов расходуют Netty и Tomcat на выполнение одного запроса:

Throughput это общее количество обработанных данных. При небольшой нагрузке, до первых 300 пользователей у RxNetty и Tomcat оно одинаковое, а после Netty уходит в приличный отрыв почти в 2 фраза.

Blocking vs Reactive

У нас есть два стека обработки запросов:

  • Традиционный блокирующий стек.

  • Неблокирующий стек в нем все происходит асинхронно и реактивно.

В блокирующем стеке все строится на Servlet API, в реактивном неблокирующем стеке на Netty.

Сравним реактивный стек и стек Servlet.

В Reactive Stack применяется технология Spring WebFlux. Например, вместо Servlet API используются реактивные стримы.

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

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

В Reactive Stack мы получаем преимущество за счет реактивности. Netty работает с пользователем, Reactive Streams Adapters со Spring WebFlux, а в конце находится реактивная база: то есть весь стек получается реактивным. Давайте посмотрим на него на схеме:

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

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

Операторы

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

Filter operator

Скорее всего, вы уже знакомы с фильтрами из интерфейса Stream.

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

Take 2 означает, что нужно взять только первые два значения.

Map operator

Оператор Map тоже хорошо знаком:

Это действие, происходящее с каждым значением. Здесь умножить на десять: было 3, стало 30; было 2, стало 20 и т.д.

Delay operator

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

Reduce operator

Еще один всем известный оператор:

Он дожидается конца работы потока (onComplete) на схеме она представлена вертикальной чертой. После чего мы получаем результат здесь это число 15. Оператор reduce сложил все значения, которые были в потоке.

Scan operator

Этот оператор отличается от предыдущего тем, что не дожидается конца работы потока.

Оператор scan рассчитывает текущее значение нарастающим итогом: сначала был 1, потом прибавил к предыдущему значению 2, стало 3, потом прибавил 3, стало 6, еще 4, стало 10 и т.д. На выходе получили 15. Дальше мы видим вертикальную черту onComplete. Но, может быть, его никогда не произойдет: некоторые потоки не завершаются. Например, у термометра или датчика дыма нет завершения, но scan поможет рассчитать текущее суммарное значение, а при некоторой комбинации операторов текущее среднее значение всех данных в потоке.

Merge operator

Объединяет значения двух потоков.

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

Combine latest

Получив новое значение, комбинирует его с последним значением из предыдущего потока.

Если в потоке возникает новое событие, мы его комбинируем с последним полученным значением из другого потока. Скажем, таким образом мы можем комбинировать значения от датчика дыма и термометра: при появлении нового значения температуры в потоке temperatureStream оно будет комбинироваться с последним полученным значением задымленности из smokeStream. И мы будем получать пару значений. А уже по этой паре можно выполнить итоговый расчет:

temperatureStream.combineLatest(smokeStream).map((x, y) -> x > X && y > Y)

В итоге на выходе у нас получается поток значений true или false включить или выключить колокольчик. Он будет пересчитываться каждый раз, когда будет появляться новое значение в temperatureStream или в smokeStream.

FlatMap operator

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

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

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

Buffer operator

Это оператор, который помогает группировать данные. На выходе Buffer получается поток, элементами которого являются списки (List в Java). Он может пригодиться, когда мы хотим отправлять данные не по одному, а порциями.

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

На схеме выше мы группируем отдельные значения по три элемента (так как всего их было пять, получилась коробка из трех, а потом из двух значений). То есть если flatMap распаковывает данные из коробок, buffer, наоборот, упаковывает их.

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

Итого

Есть два подхода:

  • Spring MVC традиционная модель, в которой используется JDBC, императивная логика и т.д.

  • Spring WebFlux, в котором используется реактивный подход и сервер Netty.

Есть кое-что, что их объединяет. Tomcat, Jetty, Undertow могут работать и со Spring MVC, и со Spring WebFlux. Однако дефолтным сервером в Spring для работы с реактивным подходом является именно Netty.

Конференция HighLoad++ 2020 пройдет 20 и 21 мая 2021 года.Приобрести билетыможно уже сейчас.

А совсем скоро состоится еще одно интересное событие, на сей раз онлайн: 18 марта в 17:00 МСК пройдет митап Как устроена самая современная платежная система в МИРе: архитектура и безопасность.

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

Хотите бесплатно получить материалы конференции мини-конференции Saint HighLoad++ 2020?Подписывайтесьна нашу рассылку.

Подробнее..

Выбор архитектурного стиля (часть 1)

28.08.2020 14:05:29 | Автор: admin
Привет, хабр. Прямо сейчас в OTUS открыт набор на новый поток курса Software Architect. В преддверии старта курса хочу поделиться с вами своей авторской статьёй.



Введение


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

Немного истории


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

Если говорить вкратце, то микросервисы в нашем текущем понимании возникли следующим образом: в 2011 Джеймс Льюис, анализируя работы различных компаний, обратил внимание на появление нового паттерна micro-app, который оптимизировал SOA с точки зрения ускорения развертывания сервисов. Несколько позже, в 2012 году, на архитектурном саммите паттерн был переименован в микросервис. Таким образом, первоначальной целью внедрения микросервисов было улучшение пресловутого time to market.

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

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

Монолит


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

Размер


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

Связанность


Монолит представляет из себя большой комок грязи (big ball of mud), изменения в котором могут привести к непредсказуемым последствиям. Внося изменения в одном месте, можно повредить монолит в другом (то самое ухо почесал, *@ отвалилась). Связано это с тем, что компоненты в монолите имеют очень сложные и, главное, неочевидные взаимосвязи.

Развертывание


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

Масштабируемость


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

Еще один пример (более классический): сервис A намного более популярен, чем сервис B, поэтому вы хотите, чтобы сервисов A было 100, а сервисов B было 10. Опять-таки два варианта: либо разворачиваем 100 полноценных монолитов, либо на каких-то из них руками сервисы B придется отключать.

Надежность


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

Косность


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

Заключение


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



Читать ещё:


Подробнее..

Выступает DMN, дирижирует ZeeBe как использовать бизнес-правила в микросервисах

11.03.2021 18:13:13 | Автор: admin

Меня зовут Николай Первухин, я Senior Java Developer в Райффайзенбанке. Так сложилось, что, единожды попробовав бизнес-процессы на Camunda, я стал адептом этой технологии и стараюсь ее применять в проектах со сложной логикой. Действительно сама идея подкупает: рисуешь процесс в удобном GUI-редакторе (моделлере), а фреймворк выполняет эти действия по порядку, соблюдая большой спектр элементов нотации BPMN.

К тому же в Camunda есть встроенная поддержка еще одной нотации DMN (Decision Model and Notation): она позволяет в простой и понятной форме создавать таблицы принятия решений по входящим наборам данных.

Но чего-то все же не хватает... Может, добавим немного скорости?

Почему ускоряем процессы

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

Что обычно характеризует такие процессы:

  • от момента создания до завершения процесса может пройти несколько дней;

  • участвует большое количество сотрудников;

  • осуществляется интеграция со множеством банковских подсистем.

Фреймворк Camunda прекрасно справляется с такими задачами, однако, заглянем под капот: там находитсяклассическая база данных для осуществления транзакционности.

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

Отличные новости: воспользуемся ZeeBe

В июле 2019 года было официально объявлено, что после двух лет разработки фреймворк ZeeBe готов к использованию на боевой среде. ZeeBe специально разрабатывался под задачи highload и, по утверждению автора, был протестирован при 10 000 процессов в секунду. В отличие от Camunda, ядро фреймворка ZeeBe принципиально не использует базу данных из него убраны все вспомогательные подсистемы, в том числе и процессор правил DMN.

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

Итак, дано:

  • микросервис, инициирующий событие и запускающий процесс (event-handler);

  • микросервис обработки бизнес-правил (rules-engine);

  • микросервис, эмулирующий действия (action).

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

Из оркестрации у нас:

  • микросервис с брокером сообщений ZeeBe (zeebe);

  • микросервис визуализации работающих процессов simplemonitor (zeebe-simple-monitor).

А присматривать за всеми микросервисами будет кластер k8s.

Схема взаимодействия

С точки зрения бизнес-логики в примере будет рассмотрен следующий бизнес-сценарий:

  • из внешней системы происходит запрос в виде rest-обращения с передачей параметров;

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

  • в зависимости от полученного решения из бизнес-правил запускается микросервис действий с различными параметрами.

Теперь поговорим подробнее о каждом микросервисе.

Микросервис zeebe

Данный микросервис состоит из брокера сообщений ZeeBe и экспортера сообщений для отображения в simple-monitor. Для ZeeBe используется готовая сборка, которую можно скачать с github. Подробно о сборке контейнера можно посмотреть в исходном коде в файле build.sh

Принцип ZeeBe минимальное число компонентов, входящих в ядро, поэтому по умолчанию ZeeBe это брокер сообщений, работающий по схемам BPMN. Дополнительные модули подключаются отдельно: например, для отображения процессов в GUI понадобится экспортер (доступны разные экспортеры, к примеру, в ElasticSearch, в базу данных и т.п.).

В данном примере возьмемэкспортер в Hazelcast. И подключим его:

  • добавим zeebe-hazelcast-exporter-0.10.0-jar-with-dependencies.jar в папку exporters;

  • добавим в файл config/application.yamlследующие настройки:

exporters:      hazelcast:        className: io.zeebe.hazelcast.exporter.HazelcastExporter        jarPath: exporters/zeebe-hazelcast-exporter-0.10.0-jar-with-dependencies.jar        args:          enabledValueTypes: "JOB,WORKFLOW_INSTANCE,DEPLOYMENT,INCIDENT,TIMER,VARIABLE,MESSAGE,MESSAGE_SUBSCRIPTION,MESSAGE_START_EVENT_SUBSCRIPTION"          # Hazelcast port          port: 5701

Данные активных процессов будут храниться в памяти, пока simplemonitor их не считает. Hazelcast будет доступен для подключения по порту 5701.

Микросервис zeebe-simplemonitor

Во фреймворке ZeeBe есть две версии GUI-интерфейса. Основная версия, Operate, обладает большим функционалом и удобным интерфейсом, однако, использование Operate ограничено специальной лицензией (доступна только версия для разработки, а лицензию для прода следует запрашивать у производителя).

Также есть облегченный вариант simplemonitor (лицензируется по Apache License, Version 2.0)

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

Можно выбрать любую базу данных Spring Data JDBC, в данном примере используется файловая h2, где настройки, как и в любом Spring Boot приложении, вынесены в application.yml

spring:  datasource:    url: jdbc:h2:file:/opt/simple-monitor/data/simple-monitor-db;DB_CLOSE_DELAY=-1

Микросервис event-handler

Это первый сервис в цепочке, он принимает данные по rest и запускает процесс.При старте сервис осуществляет поиск файлов bpmn в папке ресурсов:

private void deploy() throws IOException {        Arrays.stream(resourceResolver.getResources("classpath:workflow/*.bpmn"))                .forEach(resource -> {                    try {                        zeebeClient.newDeployCommand().addResourceStream(resource.getInputStream(), resource.getFilename())                                .send().join();                        logger.info("Deployed: {}", resource.getFilename());                    } catch (IOException e) {                        logger.error(e.getMessage(), e);                    }                });    }

Микросервис имеет endpoint, и для простоты принимает вызовы по rest. В нашем примере передаются 2 параметра, сумма и лимит:

http://адрес-сервиса:порт/start?sum=100&limit=500
@GetMapping    public String getLoad(@RequestParam Integer sum, @RequestParam Double limit) throws JsonProcessingException {        Map<String, Object> variables = new HashMap<>();        variables.put("sum", sum);        variables.put("limit", limit);        zeebeService.startProcess(processName, variables);        return "Process started";    }

Следующий код отвечает за запуск процесса:

 public void startProcess(String processName, Map<String, Object> variables) throws JsonProcessingException {         zeebeClient.newCreateInstanceCommand()                .bpmnProcessId(processName)                .latestVersion()                .variables(variables)                .send();    }

Сам процесс нарисован в специальной программе ZeeBe modeler (почти копия редактора Camunda modeler) и сохраняется в формате bpmn в папке workflow в ресурсах микросервиса. Графически процессвыглядит как:

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

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

После старта процесс продвинется на один шаг, и появится сообщение типа DMN.

Микросервис rules-engine

Благодаря прекрасной модульной архитектуре Camunda есть возможность использовать в своем приложении (отдельно от самого фреймворка Camunda) движок правил принятия решения.

Для его интеграции с вашим приложением достаточно добавить его в зависимости maven:

        <dependency>            <groupId>org.camunda.bpm.dmn</groupId>            <artifactId>camunda-engine-dmn</artifactId>            <version>${camunda.version}</version>        </dependency>

Сами правила создаются в специальном графическом редакторе Camunda modeler. Одна диаграмма DMN имеет два вида отображения.

Entity Relation Diagram (вид сверху) показывает зависимости правил друг от друга и от внешних параметров:

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

Само же бизнес-правило содержит более детальный вид:

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

Посмотрим на примере, где такой файл располагается в папке dmn-models в ресурсах микросервиса. Для регистрации диаграммы при старте микросервиса происходит его однократная загрузка:

 public void init() throws IOException {        Arrays.stream(resourceResolver.getResources("classpath:dmn-models/*.dmn"))                .forEach(resource -> {                    try {                        logger.debug("loading model: {}", resource.getFilename());                        final DmnModelInstance dmnModel = Dmn.readModelFromStream((InputStream) Resources                                .getResource("dmn-models/" + resource.getFilename()).getContent());                        dmnEngine.parseDecisions(dmnModel).forEach(decision -> {                            logger.debug("Found decision with id '{}' in file: {}", decision.getKey(),                                    resource.getFilename());                            registry.put(decision.getKey(), decision);                        });                    } catch (IOException e) {                        logger.error("Error parsing dmn: {}", resource, e);                    }        });    }

Для того, чтобы подписаться на сообщения от ZeeBe, требуется осуществить регистрацию workerа:

private void subscribeToDMNJob() {        zeebeClient.newWorker().jobType(String.valueOf(jobWorker)).handler(                (jobClient, activatedJob) -> {                    logger.debug("processing DMN");                    final String decisionId = readHeader(activatedJob, DECISION_ID_HEADER);                    final Map<String, Object> variables = activatedJob.getVariablesAsMap();                    DmnDecisionResult decisionResult = camundaService.evaluateDecision(decisionId, variables);                    if (decisionResult.size() == 1) {                        if (decisionResult.get(0).containsKey(RESULT_DECISION_FIELD)) {                            variables.put(RESULT_DECISION_FIELD, decisionResult.get(0).get(RESULT_DECISION_FIELD));                        }                    } else {                        throw new DecisionException("Нет результата решения.");                    }                    jobClient.newCompleteCommand(activatedJob.getKey())                            .variables(variables)                            .send()                            .join();                }        ).open();    }

В данном коде осуществляется подписка на событие DMN, вызов модели правил при получении сообщения от ZeeBe и результат выполнения правила сохранятся обратно в бизнес-процесс в виде переменной result (константа RESULT_DECISION_FIELD).

Когда данный микросервис отчитывается ZeeBe о выполнении операции, бизнес-процесс переходит к следующему шагу, где происходит выбор пути в зависимости от выполнения условия, заданного в свойствах стрелочки:

Микросервис action

Микросервис action совсем простой. Он также осуществляет подписку на сообщения от ZeeBe, но другого типа action.

В зависимости от полученного результата будет вызван один и тот же микросервис action, но с различными параметрами. Данные параметры задаются в закладке headers:

Также передачу параметров можно сделать и через закладку Input/Output, тогда параметры придут вместе с переменными процесса, но передача через headers является более каноничной.

Посмотрим на получение сообщения в микросервисе:

private void subscribe() {        zeebeClient.newWorker().jobType(String.valueOf(jobWorker)).handler(                (jobClient, activatedJob) -> {                    logger.debug("Received message from Workflow");                    actionService.testAction(                            activatedJob.getCustomHeaders().get(STATUS_TYPE_FIELD),                            activatedJob.getVariablesAsMap());                    jobClient.newCompleteCommand(activatedJob.getKey())                            .send()                            .join();                }        ).open();    }

Здесь происходит логирование всех переменных бизнес-процесса:

 public void testAction(String statusType, Map<String, Object> variables) {        logger.info("Event Logged with statusType {}", statusType);        variables.entrySet().forEach(item -> logger.info("Variable {} = {}", item.getKey(), item.getValue()));    }

Исходный код

Весь исходный код прототипа можно найти в открытом репозитории GitLab.

Компиляция образов Docker

Все микросервисы проекта собираются командой ./build.sh

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

Загрузка микросервисов в кластер k8s

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

  1. Создать namespace в кластере kubectl create namespace zeebe-dmn-example

  2. Создать config-map общих настроек

kind: ConfigMapapiVersion: v1metadata:  name: shared-settings  namespace: zeebe-dmn-exampledata:  shared_servers_zeebe: <IP адрес кластера>

Далее создаем два персистентных хранилища для хранения данных zeebe и simplemonitor. Это позволит осуществлять перезапуск соответствующих подов без потери информации:

kubectl apply -f zeebe--sm-volume.yml

kubectl apply -f zeebe-volume.yml

Yml-файлы находятся в соответствующих проектах:

Теперь осталось последовательно создать поды и сервисы. Указанные yml-файлы находятся в корне соответствующих проектов.

kubctl apply -f zeebe-deployment.yml

kubctl apply -f zeebe-sm-deployment.yml

kubctl apply -f event-handler-deployment.yml

kubctl apply -f rules-engine-deployment.yml

kubctl apply -f action-deployment.yml

Смотрим, как отображаются наборы подов в кластере:

И мы готовы к тестовому запуску!

Запуск тестового процесса

Запуск процесса осуществляется открытием в браузере соответствующий URL. К примеру, сервис event-handler имеет сервис с внешним IP и портом 81 для быстрого доступа.

http://адрес-кластера:81/start?sum=600&limit=5000
Process started

Далее можно проверить отображение процесса в simplemonitor. У данного микросевиса тоже есть внешний сервис с портом 82.

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

Теперь можно просмотреть лог микросервиса action, там можно увидеть значение переменной statusType, которое соответствует варианту прохождения процесса.

Поделюсь, какими ресурсами пользовался для подготовки прототипа

Небольшое послесловие вместо итогов

Из плюсов:

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

  • простая нотация BPMN и DMN позволяет привлекать аналитиков и бизнес к обсуждению сложной логики;

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

  • Zeebe изначально разрабатывался для работы в кластере, в случае необходимости можно быстро нарастить мощности;

  • без ZeeBe Operate можно вполне обойтись, Simple-Monitor отвечает минимальным требованиям.

Из минусов:

  • хотелось бы иметь возможность редактирования DMN непосредственно в ZeeBe modeler (как это реализовано в Camunda), на данный момент, приходится использовать оба моделлера;

  • к сожалению, только в Enterprise версии Camunda есть возможность просмотра пути, по которому принималось решение:

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

При развертывании прототипа в реальных условиях в кластере k8s необходимо добавить ограничения по ресурсам (CPU и RAM), также нужно будет подобрать лучшую систему хранения исторических данных.

Где применять такие технологии:

  • как оркестрация внутри одной команды или продукта в виде перекладывания сложной логики на диаграммы BPMN / DMN;

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

  • как частичная альтернатива существующего стека ESB или Kafka для интеграции между командами.

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

Подробнее..

Переезжаем на 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.
Подробнее..

Серия мастер-классов по MySQL 1517 декабря

04.12.2020 08:08:46 | Автор: admin


Приглашаем на мастер-классы Тюнинг и масштабирование проекта на MySQL 1517 декабря 2020. Расскажем, что именно настроить, чтобы база не тормозила и не падала, а данные не терялись. Поможем найти медленные запросы и сделать их быстрыми.


Мастер-классы ведет Владимир Федорков, специалист по настройке и эксплуатации СУБД MySQL, эксперт в сфере производительности MySQL, постоянный спикер конференций в России, Европе и США.


Программа


День 1 15 декабря, вторник


  • Ставим и тюним MySQL для работы с высокими нагрузками
  • Версии MySQL и форки
  • Как настраивать MySQL? Важные аспекты при установке и первоначальной настройке
  • Как работает MySQL? Архитектура и настройки InnoDB
  • Другие подсистемы хранения
  • Что не нужно настраивать никогда
  • MySQL tuner и другие скрипты автоматической настройки

День 2 16 декабря, среда


  • Учимся писать самые быстрые в мире запросы для MySQL
  • Запросы в MySQL: что влияет на производительность?
  • Как оптимизировать SELECT?
  • Оптимизатор MySQL
  • Selectivity и Cardinality главные слова, которых никто не знает
  • Кэш запросов в MySQL
  • Оптимизация записи
  • Работа с изменениями схемы

День 3 17 декабря, четверг


  • Строим отказоустойчивую инфраструктуру для MySQL
  • Работа MySQL под высокими нагрузками
  • Масштабирование MySQL
  • Функциональное шардирование
  • Горизонтальное шардирование
  • Репликация в MySQL
  • Master-Master репликация
  • Инструменты объединения MySQL в кластеры (Galera, Group Replication)
  • Маршрутизация запросов и ProxySQL
  • Управление репликацией: MHA и Orchestrator
  • Бэкап и восстановление

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


Стоимость участия: 3 000


Записаться на мастер-классы

Подробнее..

Как использовать 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!

Подробнее..

Эволюция социального фида в iFunny мобильном приложении с UGC-контентом

10.03.2021 20:17:40 | Автор: admin

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

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

Принципиально можно выделить две схемы формирования фида:

  1. Push on change.

  2. Pull on demand.

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

1. Push on change. Для каждого пользователя создаем отдельный денормализованный фид. При добавлении мема вставляем его в фиды пользователей, подписанных на автора.

Плюсы:

  • очень быстро читать фид из базы.

Минусы:

  • долгое добавление и удаление: время линейно зависит от количества подписок на автора.

Формирование фида по схеме push on changeФормирование фида по схеме push on change

2. Pull on demand. Формируем фид на лету: отправляем по запросу на каждого пользователя, на которого подписаны.

Плюсы:

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

Минусы:

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

  • при формировании необходимо обрабатывать избыточное количество данных: чтобы отдать фид из 20 элементов, для каждой подписки приходится выбирать по 20, сортировать и склеивать, а остальное просто выкидывать.

Формирование фида по схеме pull on demandФормирование фида по схеме pull on demand

Первая итерация: push on change на Cassandra

Мы выбрали механизм push on change, а в качестве БД для хранения денормализованных представлений использовали Cassandra. Она использует подход LSM, что позволяет писать с достаточно внушительной скоростью за счет того, что данные просто последовательно пишутся в память (MemTable), а затем сохраняются на диск и сливаются в многоуровневые отсортированные файлы (SSTables).

Была реализована инфраструктура для обработки асинхронных задач наполнения фидов. Суть этих задач сводится к получению подписчиков автора контента и добавления большими пачками нового мема в фид к каждому из них. Тут все логично и хорошо: кластер Cassandra отлично переваривала здоровенные батчи, и по началу проблем с получением тоже не было.

Но спустя время появились проблемы с доступом к данным. Причин несколько:

  1. Требования сторов удалить определённый контент. Например, нарушение копирайта, 18+ или иногда просто лягушонок Пепе.

  2. Удаление самими пользователями.

  3. Отписка пользователей друг от друга. Иногда они отписывались сразу от всех.

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

Всё это создавало в датасете дырки разного размера, которые вынуждали БД в фоне совершать гигантскую работу. Со временем всё становилось только хуже. Cassandra хранит данные в нескольких слоях, каждый из которых больше предыдущего.

Распределение данных по уровням в CassandraРаспределение данных по уровням в Cassandra

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

Cassandra написана на Java, поэтому она могла непредсказуемо и надолго уходить в сбор мусора, особенно когда начинала мержить глубокоуровневые SSTableы. К тому времени в кластере было уже порядка 25 нод, а суммарное количество данных с учетом репликации перевалило за 20 ТБ. Это послужило сигналом к началу второй итерации.

Вторая итерация: pull on demand на Redis с формированием фида на стороне приложения

Провели большое количество экспериментов для улучшения ситуации, например:

  • Тюнинг GC Cassandra.

  • Другие стратегии Cassandra Compaction, рассматривали вариант написания своей стратегии.

  • Другие структуры хранения и БД (например, блобы в PostgreSQL).

Но ничего хорошего не вышло, и решили перейти к схеме pull on demand.

Поставили кластер из Redis, разложили данные в сортированные множества (sorted sets) и начали строить фид прямо в момент запроса слиянием на стороне приложения в отдельном сервисе. Это значительно ускорило появление новых мемов в фиде: больше не надо итерировать по подпискам, вообще не нужны асинхронные задачи.

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

Третья и текущая итерация: pull on demand на Redis с формированием фида на стороне базы

У предыдущего решения была пара недостатков:

  • Redis однопоточный, поэтому не было возможности распараллелить выполнение сотен запросов более чем на количество шардов в кластере;

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

Redis 4 позволил писать свои модули. Мы решили, что это хороший способ оптимизировать работу фидов. Был написан модуль на C, который на стороне БД получал нужные данные, формировал из них фид, выполняя сортировку на структуре MaxHeap. Команду назвали ZREVMERGE: как понятно из названия, выполняет слияние нескольких сортированных множеств.

Формирование фида на основе модуля с ZREVMERGEФормирование фида на основе модуля с ZREVMERGE

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

В итоге получилось более чем в два раза ускорить формирование фида: раньше медиана была около 20 мс, с переносом работы в модуль стала менее 10 мс. Получилось бы лучше, если бы не шардирование данных в кластере: приходится всё же отправлять несколько запросов, по одному на каждый шард и доделывать часть работы в приложении. Получилось увеличить лимит на подписки пользователям с 400 до 5000.

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

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

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

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

Конечно, пока не получилось решить все проблемы на 100%. Всегда есть возможности улучшить что-то, но из начальной точки проделан достаточно большой путь. Хоть писать на C в прод и было страшно, но свои результаты это принесло. Буду рад почитать, как фиды работают у вас. Удачного итерирования!

Подробнее..

Как мы переходили на Java 15, или история одного бага в jvm длинной в 6 лет

12.02.2021 10:14:08 | Автор: admin

Мы готовились к выходу Java 15 ради некоторых её новых возможностей. В частности текстовых блоков. Да, они появились в Java 14 (о новых функциях в Java 14 можно посмотреть здесь), но только как превью-фича, а, начиная с Java 15, она стала доступна в виде окончательно готовой функции.

Мы в hh.ru привыкли внедрять и использовать самые современные технологии в разработке ПО. Пробовать что-то новое одна из ключевых задач команды Архитектура. Пока многие пишут на Java 8, мы уже близки к тому, чтобы отправить на свалку истории Java 11.

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

Переезд с Java 14 на Java 15. Что-то пошло не так

Дождавшись выхода новый Java, мы приступили к переезду. Не мудрствуя лукаво, выбрали один из нагруженных сервисов, который уже крутился на Java 14. В теории никаких сложностей при переходе не должно было возникнуть, на практике так и получилось. Обновление Java 14 на Java 15 не тоже самое, что обновление Java 8 на Java 11.

hh и в продакшн сервис обновлён, работа выполнена. Что дальше? А дальше мониторинг работы. Для сбора метрик мы используем okmeter. С его помощью мы наблюдали за поведением обновленного сервиса. Никаких аномалий по сравнению с предыдущей версией Java не было, кроме одной нативная память. В частности, зона Code Cache выросла почти в 2 раза!

До конца 17 ноября Java 14, после Java 15До конца 17 ноября Java 14, после Java 15

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

Что такое вообще этот ваш Code Cache?

Code Cache область нативной памяти, где хранится интерпретатор байткода Java, JIT-компиляторы C1 и C2, и оптимизированный ими код. Основным пользователем является JIT. Весь перекомпилированный им код будет сохранятся в Code Cache.

Начиная с Java 9 Code Cache поделен на три отдельных сегмента, в каждом из которых хранится свой тип оптимизированного кода (JEP 197). Но на графике выше видно только одну выделенную область, несмотря на то что там Java 14 и Java 15. Почему одну?

Дело в том, что мы тонко настраивали размеры памяти при переводе сервисов в Docker (о том, как это было, можно почитать тут) и умышленно установили флаг размера Code Cache (ReservedCodeCacheSize) равным 72МБ в этом сервисе.

Три сегмента можно получить двумя путями: оставить значение ReservedCodeCacheSize по умолчанию (256Мб) или использовать ключ SegmentedCodeCache. Вот как эти зоны выглядят на графике с другого нашего сервиса:

Поиск утечки нативной памяти в Code Cache

С чего начать расследование? Первое что приходит на ум использовать Native Memory Tracking, функцию виртуальной машины HotSpot, позволяющую отслеживать изменение нативной памяти по конкретным зонам. В нашем случае использовать Native Memory Tracking нет необходимости, так как благодаря собранным метрикам, мы уже выяснили, что проблема в Code Cache. Поэтому мы решаем сделать следующее запустить инстансы сервиса с Java 14 и Java 15 вместе. Так как у нас уже три дня сервис работает на "пятнашке", добавляем один инстанс на 14-ой.

Мы решаем продолжить поиск утечки с помощью утилит Java. Начнем с jcmd. Так как мы знаем, что "течет" у нас Code Cache, мы обращаемся к нему. Если сервис запущен в Docker, можно выполнить команду таким образом для каждого инстанса:

docker exec <container_id> jcmd 1 Compiler.CodeHeap_Analytics

Получаем два очень длинных и подробных отчета о состоянии Code Cache. Кропотливо сравнив их, мы обратили внимание на следующий интересный факт, связанный с очисткой Code Cache:

// Java 14Code cache sweeper statistics:Total sweep time: 9999 msTotal number of full sweeps: 17833Total number of flushed methods: 10681 (thereof 1017 C2 methods)Total size of flushed methods: 20180 kB// Java 15Code cache sweeper statistics:Total sweep time: 5592 msTotal number of full sweeps: 236 Total number of flushed methods: 11925 (thereof 1146 C2 methods)Total size of flushed methods: 44598 kB

Обратите внимание на количество циклов полной очистки Total number of full sweeps. Вспомним, что сервис на Java 15 работает 3 дня, а на Java 14 всего 20 минут. Но количество полных очисток Code Cache поразительно разнится почти 18 тысяч за 20 минут, против 236 за трое суток.

Как работает очистка Code Cache

Пришло время углубиться в детали. За очистку Code Cache отвечает отдельный поток jvm CodeCacheSweeperThread, который вызывается с определенной эвристикой. Поток реализован как бесконечный цикл while, внутри которого он блокируется, пока не истечет 24-часовой таймаут, либо не будет снята блокировка вызовом:

CodeSweeper_lock->notify();

После того, как блокировка снята, поток проверяет, истек ли таймаут и имеет ли хотя бы один из двух флагов, запускающих очистку Code Cache, значение true. Только при выполнении этих условий, поток вызовет очистку Code Cache методом sweep(). Давайте подробнее разберем флаги:

should_sweep. Этот флаг отвечает за две стратегии очистки Code Cache нормальную и агрессивную. О стратегиях поговорим дальше.

force_sweep. Этот флаг устанавливается в true при необходимости принудительно очистить Code Cache без выполнения условий нормальной и агрессивной стратегий очистки. Используется в тестовых классах jdk.

Нормальная очистка

  1. Во время вызова GC хранящиеся в Code Cache методы могут изменить свое состояние по следующему сценарию: alive -> notentrant -> zombie. Методы не-alive помечаются как "должны быть удалены из Code Cache при следующем запуске потока очистки".

  2. В конце своей работы GC передает ссылку на все не-alive объекты в метод report_state_change.

  3. Далее в специальную переменную bytes_changed инкрементируется суммарный размер объектов, помеченных как не-alive в этом проходе GC.

  4. Когда bytes_changed достигает порога, задаваемого в переменной sweep_threshold_bytes, флаг should_sweep помечается как true и блокировка потока очистки снимается.

  5. Запускается алгоритм очистки Code Cache, в начале которого значение bytes_changed сбрасывается. Сам он состоит из двух фаз: сканирование стека на наличие активных методов, удаление из Code Cache неактивных. На этом нормальная очистка завершена.

Начиная с Java 15 пороговым значением можно управлять с помощью флага jvm SweeperThreshold он принимает значение в процентах от общего количества памяти Code Cache, заданном флагом ReservedCodeCacheSize.

Агрессивная очистка

Этот тип очистки появился еще в Java 9, как один из способов борьбы с переполнением Code Cache. Выполняется в тот момент, когда свободного места в памяти Code Cache становится меньше заранее установленного процента. Этот процент можно установить самостоятельно, используя ключ StartAggressiveSweepingAt, по умолчанию он равен 10.

В отличие от нормальной очистки, где мы ждем наполнения буфера "мертвыми" методами, проверка на старт агрессивной очистки выполняется при каждой попытке аллокации памяти в Code Cache. Другими словами, когда JIT-компилятор хочет положить новые оптимизированные методы в Code Cache, запускается проверка на необходимость запуска очистки перед аллокацией. Проверка эта довольно простая, если свободного места меньше, чем указано в StartAggressiveSweepingAt, очистка запускается принудительно. Алгоритм очистки такой же, как и при нормальной стратегии. И только после выполнения очистки, JIT сможет положить новые методы в Code Cache.

Что у нас?

В нашем случае размер Code Cache был ограничен 72 МБ, а флаг StartAggressiveSweepingAt мы не задавали, значит по умолчанию он равен 10. Если взглянуть на статистику очистки Code Cache, может показаться, что на Java 14 работает именно агрессивная стратегия. Дополнительно убедиться в этом нам помог тот же график, но с увеличенным масштабом:

Java 14Java 14

Он имеет зубчатую структуру, которая говорит о том, что очистка происходит часто, и, вероятно, методы по кругу выгружаются из Code Cache, в следующей итерации JIT-компиляции вновь попадают обратно, после снова удаляются etc.

Но как это возможно? Почему работает агрессивная стратегия очистки? По умолчанию она должна запускаться в тот момент, когда свободного места в Code Cache менее 10%, в нашем случаем только при достижении 65 мегабайт, но мы видим, что она происходит и при 30-35 мегабайтах занятой памяти.

Для сравнения, график с запущенной Java 15 выглядит иначе:

Java 15Java 15

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

Утечка не утечка

Так как работой Code Cache управляет jvm, мы отправились искать ответы в исходниках openJDK, сравнивая версии Java 14 и Java 15. В процессе поисков мы обнаружили интересный баг. Там сказано, что агрессивная очистка Code Cache работает неправильно с того момента, как ее внедрили в Java 9. Вместо старта агрессивной очистки при 10% свободного места, она вызывалась при 90% свободного места, то есть почти всегда. Другими словами, оставляя опцию StartAggressiveSweepingAt = 10, на деле мы оставляли StartAggressiveSweepingAt = 90. Баг был исправлен 3 июля 2020 года. А все дело было в одной строчке:

Этот фикс вошел во все версии Java после 9-ки. Но почему тогда его нет в нашей Java 14? Оказывается, наш docker-образ Java 14 был собран 15 апреля 2020 года, и тогда становится понятно, почему фикс туда не вошел:

Так значит и утечки нативной памяти в Code Cache нет? Просто всё время очистка работала неправильно, впустую потребляя ресурсы cpu. Понаблюдав еще несколько дней за сервисом на Java 15, мы сделали вывод, что так и есть. Общий график нативной памяти вышел на плато и перестал показывать тренд к росту:

скачок на графике - это переход на java 15скачок на графике - это переход на java 15

Выводы

  1. Как можно чаще обновляйте свою Java. Это касается не только мажорных версий, но и патчевых. Там могут содержаться важные фиксы

  2. Разумное использование метрик помогает обнаружить потенциальные проблемы и аномалии

  3. Переходите на Java 15, оно того стоит. Вот тут список всех фич, которые появились в пятнашке

  4. Если вы используете Java 8, то у вас проблемы агрессивной очистки Code Cache нет, за отсутствием этого функционала как такового. Однако существует риск, что Code Cache может переполниться и JIT-компиляция будет принудительно отключена

Подробнее..

Перевод Как я разогнал Intel Rocket Lake Core i9-11900K до 7,14 ГГц на всех ядрах

16.05.2021 20:15:10 | Автор: admin

Процессоры семейства Rocket Lake уже доступны, а значит, пришло время для Xtreme OverClocker (XOCer). Я получил ранний доступ к процессорам и занимаюсь их разгоном уже несколько месяцев.

В этом месяце я узнал немало важных моментов о разгоне Rocket Lake, также мне удалось разогнать Intel Rocket Lake Core i9-11900K до 7,14 ГГц на всех ядрах. Еще я установил мировой рекорд на G.Skill Tweakers Contest Extreme. В статье я поделюсь несколькими советами.


Чипсет Z590, похоже, последняя разработка Intel с поддержкой DDR4. Компания уже подтвердила появление процессоров Alder Lake с новой контактной площадкой LGA 1700. Последующие модели плат Intel будут поддерживать уже PCIe 5.0 / DDR5 с новыми процессорами и сокетами AM5 / LGA 1700. В продаже они появятся примерно через год.

Тем не менее, у материнской платы Intel много возможностей. Например, в два раза больше VRM фаз, чем может потребоваться обычному пользователю, а также два слота DIMM со сверхвысокой пропускной способностью и сверхнизкой задержкой. Также есть RGB-подсветка, 12 слоев в PCB и целый набор подписанных свитчей, которые дают огромное количество возможностей.


В общем-то, я здесь не для того, чтобы убеждать в том, что Rocket Lake стоит того, чтобы ее купили. Стоимость несколько не то, что волнует компьютерного энтузиаста. Оправдана ли цена RTX 3090 в $2800? Это то, что ты просто хочешь, верно?

Для меня разгон Rocket Lake развлечение. Контроллеры памяти в этих чипах просто безумны, а новая настройка Intel Gears дает возможность без проблем увеличить частоту памяти до 5000 мГц. Все это имеет особенную ценность, значение для меня.

Наблюдаю ли я разницу в производительности XMP с 3800 мГц до разгона и 5000 мГц после разгона? Нет, но дело не в этом. Производительность, эффективность одно из моих пристрастий, а разгон как раз та сфера, где можно развернуться вовсю, удовлетворяя это пристрастие.

Разгон Rocket Lake с кулером AIO


Никаких сюрпризов, для разгона я использовал Z590 ASRock OC Formula. Дизайнер плат Ник Ши мой большой друг, и он реализовал несколько функций, о которых я просил. Пасхальное яйцо для вас: кнопки профиля 1/2/3 в правом верхнем углу расположены достаточно далеко, чтобы мои пальцы-сосиски случайно не наткнулись на что-нибудь (не шучу).

Эти кнопки профилей присутствуют на плате только потому, что нам с друзьями нужна возможность на ходу менять настройки и частоту для выполнения тестов. Также в нашем распоряжении IDE для SATA в сочетании с портами для мыши и клавиатуры PS2, специально для запуска Windows XP!


Плата очень крутая. У VRM 16 фаз, сама плата 12-слойная. DIMM-разъемы расположены очень близко к слоту. Настолько близко, что мне едва удается установить память с нестандартным радиатором рядом с блоком водяного охлаждения. Но все это вынужденная теснота, которая нужна для максимальной производительности.

Я использовал кулер Enermax LIQMAX III 360 ARGB AIO. Он оснащен отличной подсветкой, которая мне нравится. В качестве источника питания взял надежный MaxTytan 1250W. При пиковых нагрузках процессора потребляемая мощность в два раза меньше максимально возможной, что идеально подходит для меня.


Говоря о результатах обычных тестов, я могут без проблем достичь 5,2 ГГц с Cinebench R20 на пяти из семи чипов 11900K, которые я протестировал. И это без экстремальных условий вроде повышенных напряжения или температуры. Enermax LIQMAX III 360 достаточно силен для восьмиядерного Core i9-11900K. Даже когда вентиляторы работали в бесшумном режиме, температура процессора не достигала 80 C на протяжении всего теста.

Более того, Intel Core i9-11900K удается поддерживать аналогичные тактовые частоты на уровне 10900K, при этом основной компромисс заключается в снижении количества ядер до восьми вместо десяти. Более подробное техническое описание чипа можно найти в обзоре Intel Core i9-11900K Пола Алькорна.


Советы по разгону Rocket Lake


  • 1,51,55 В достаточное напряжение кольцевой шины и контроллера кольцевой шины. Причин превышать этот показатель нет ни для работы с обычным кулером, ни для охлаждения жидким азотом.
  • Не превышайте стандартное напряжение VCCIO. Теперь есть M_VCCIO Voltage или VCCIO 2, которые помогут с разгоном памяти. 1,551,65 В вполне достаточно, и проблем с Ln2 не возникло.
  • B-Die для этого поколения все еще царь горы. tCL 15 и 1t с использованием Gear 2 от 4800 МГц + для большинства тестов должна свести на нет потерю задержки из-за Gear 1.
  • У некоторых процессоров есть слабые ядра, которые являются бутылочным горлышком для многопоточных тестах. В этом случае попробуйте одноядерные тесты, в них чип может показать себя с лучшей стороны.
  • Вы можете проверить максимальное количество ядер, при помощи стандартных инструментов. Например, можно использовать Cinebench R20 с открытым HWMonitor для определения ядер с максимальной частотой 5,3 ГГц.
  • Используйте качественную термопасту. Вам нужна максимальная степень охлаждения, которую только можно получить.


Разгоняем Rocket Lake с жидким азотом


Для экстремального разгона я установил Reaktor 2.2 CPU и объединил его с Thermal Grizzly Extreme для получения лучших результатов.

Intel дала возможность без проблем экстремально разгонять Rocket Lake. Главная проблема в случае жидкого азота добиться того, чтобы уровень жидкости был всегда максимальным с его температурой в -196 C. Это поколение процессора требует лишь адекватного напряжения для CPU PLL. Поставьте 1,6 В+ и все задача выполнена. Затем настраиваем напряжение ядра и все готово для экстремального разгона.


Я протестировал несколько процессоров, находящихся в розничной продаже и смог достичь заветной отметки 7 ГГц на всех ядрах. Если быть точным, то 7140,88 МГц. Затем я занялся PYPrime 2.0, который является частью проводимого в настоящее время G.Skill Tweakers Contest Extreme на hwbot.org. Мне удалось установить мировой рекорд на частоте 6900 МГц и неплохих 1,87 В на ядре. Я решил оставить режим Gear 1 для памяти и использовал задержку для повышения пропускной способности.


Это был интересный опыт. Я заметил несколько важных нюансов при работе с жидким азотом. Так, чипы могут съедать напряжение ядра. Intel Core i9-10900K перестанет масштабироваться при 1,721,74 М виртуального ядра в многопоточных тестах. С Core i9-11900K не будет никакой магии до превышения 1800 vCore. Для меня было странно, что температура системы охлаждения процессора не очень менялась под нагрузкой. В некоторых случаях этот показатель составлял всего 1-2 C при температуре системы в 192 C.

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


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


Вывод Intel удалось выжать все до последней капли из своего 14-нм техпроцесса. Я удивлен, что Rocket Lake способна на такое. Это отличная платформа для энтузиастов, и, возможно, она стимулирует AMD развиваться. В целом, это отличное завершение для 14-нм техпроцесса. Посмотрим, чего удастся достичь с Z690!

Подробнее..

Перевод Инженерная надежность и отказоустойчивость распределенной системы

08.06.2021 16:18:29 | Автор: admin

Это гостевая публикация отПэдди Байерса (Paddy Byers), сооснователя и технического директораAbly платформы для стриминга данных в реальном времени. Оригинал статьи опубликован вблоге Ably.

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

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

Для начала дадим несколько определений:

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

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

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

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

Доступность, устойчивость и состояние компонентов системы

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

В физическом мире традиционно различают:

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

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

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

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

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

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

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

Отказы неизбежны и естественны

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

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

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

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

Сервисы без сохранения состояния

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

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

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

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

  • Как сохранить работоспособность системы после разных типов отказов?

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

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

  • Каких эксплуатационных расходов требует управление этим уровнем избыточности?

Выбранное решение должно удовлетворять следующим критериям:

  • Требования клиентов к высокой доступности сервиса

  • Уровень эксплуатационных расходов для бизнеса

  • Инженерная целесообразность

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

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

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

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

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

Сервисы с сохранением состояния

В Ablyустойчивость определяется бесперебойной работойсервисов с сохранением состояния, и добиться этого намного сложнее, чем просто обеспечить доступность.

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

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

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

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

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

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

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

Когда клиент отправляет сообщение в Ably, сервис принимает его и уведомляет, была попытка публикации успешной или нет. В этом случае главный вопрос в контексте обеспечения доступности будет таким:

В течение какой доли времени сервис может принимать (и обрабатывать) сообщения, а не отклонять их?

Минимальный целевой показатель доступности для нас 99,99; 99,999 или даже 99,9999%.

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

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

Архитектурный подход к обеспечению устойчивости

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

Размещение роли с сохранением состояния

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

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

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

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

Это становится возможным благодаря последовательному алгоритму размещения в хеш-кольце

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

Выявить, хешировать, продолжить

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

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

Слой обеспечения постоянства канала

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

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

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

Благодаря нашей математической модели при отказе узла мы точно знаем, сколько времени уйдет на выявление причины сбоя, достижение консенсуса и последующего перемещения роли. Эти данные вкупе с частотой отказов для каждой зоны доступности позволяют создать вероятностную модель возникновения комплексного сбоя, который может привести к потере текущего состояния сервиса. Описанные базовые принципы позволяют нам гарантироватьустойчивость работы сервиса с вероятностью 99,999999%.

Вопросы внедрения отказоустойчивости

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

Достижение консенсуса в глобально распределенных системах

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

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

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

Работоспособность не определяется двумя состояниями

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

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

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

Проблема доступности ресурсов

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

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

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

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

Проблема масштабирования ресурсов

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

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

Заключение

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

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

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

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

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

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

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


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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    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