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

Data

Как строиласьData-практикавEPAM

19.02.2021 12:19:30 | Автор: admin

Компания EPAM давно работает с данными, первые крупные заказчики с проектами поBigDataпоявилисьв далёком 2001 году.В то время известные аналитические компанииGartnerиForrester, а также крупные поставщикиOracle,Microsoftи IBM отмечали, что компании должны двигаться в сторонуBigData, поскольку эти технологии незаменимы во всех областях, связанных с обработкой больших объёмов данных.С того времени команда экспертовEPAMпостепенно росла, работая над всё более сложными проектами и предлагая проверенные решения и качественные продукты для работы с большими данными. Сегодня только в российскомEPAMболее 500 человек работают вData-практике. О том, как всё начиналось, какие проекты встречались, какие провалы случались,к чему должны готовитьсяData-специалисты и о том, какие вообще бываютData-специалисты,я поговорила с руководителемData-практики EPAM в России Ильей Герасимовым.

Карьера

Расскажи, как ты пришёл в направление Data

ВEPAMя пришёл в 2006 годукакjunior-разработчикна .NETиMSSQLServer, до этого работал в продуктовойкомпаниии занимал должность тимлида, разрабатывал ПО для автоматизациигостиници ресторанов.Но вEPAMяначалкарьерус нуля.К 2013 году я дорос дотимлидаиискал новыевозможностисвоегоразвитиявEPAM,и именно в это времяявстретилсянаSECeв Минскес руководителем центра компетенцийBigData, и мы договорились о том, что в России надо развивать это направление.

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

Почему так долго работаешь в компании?

Ещё до Data я подумывал пару раз уйти, но что-то не отпускало меня. Сейчас я могу сказать точно, что здесь меня держат люди, с которыми пройдено много всего. И здесь всегда появляется что-то новое новые проекты, заказчики.

Почему именно Data?

Потому чтовесь мир этоData, и мы в нейData.:)

Что сейчас представляет собой Data-практика?

ПостепеннопоявлялисьновыеData-компетенции:Data Science,Machine Learning,Business Intelligence, Enterprise Search, DevOps in Data, Data Quality, Business Data Analysis.Сегодняв нашей практике более 500 человек это оченьбольшое подразделение сглубокойэкспертизойв разных областях.

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

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

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

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

Проекты

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

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

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

А самый большой провал и как справились с этим?

Первое время провалов было много. В основном они были связаны с нехваткой людей, потому что мы не могли найти готовых людей с улицы со знанием технологий. Мы набираем разработчиков со знанием Java, Python, DevOps-инжиниринг и потом доучиваем.

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

Помимо общего курса нашими специалистами разработано много внутренних курсов по разным направлениям Data Analytics, облачные решения, Data Engineering, Data Science и другие, доступных всем сотрудникам EPAM.

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

Про технологии

За чем сейчас будущее? На какие технологии появляется и сохраняется спрос?

Если я начну перечислять все технологии, то какие-то пропущу, какие-то не вспомню, на какие-то обращу больше внимания, чем они этого заслуживают. Технологий очень много, они быстро стареют и так же быстро появляются новые. Гнаться за суперсовременными технологиями не всегда получается, да и не всегда нужно. Зачастую новые решения появляются на рынке в довольно сыром состоянии. Однажды на одном проекте мы погнались за технологией Cadence, которая показалась заказчику приемлемой, но в итоге с ней постоянно возникало много проблем с производительностью, она не справлялась с тем объёмом данных, который был на проекте, и мы с трудом находили людей, которые могли бы быстро в ней разобраться.

Был ещё один кейс, на этот раз с технологией Reinforcement Learning. Один из наших заказчиков хотел внедрить систему с использованием этой технологии. Этому фреймворку на тот момент было около 2-х месяцев, он был довольно сырой. В целом было очень мало систем промышленного масштаба, где эта технология используется. В итоге ничего не получилось, и нам пришлось быстро откатить систему и создать решение, которое справлялось с задачей заказчика уже без использования Reinforcement Learning. Хотя технология очень перспективна, мы следим за её развитием и, возможно, даже уже в этом году мы сможем использовать её в проектах.

Но всё же существует некоторый золотой стандарт, который должны знать Data-специалисты. Причём этот стандарт тоже постоянно обновляется. На самом деле, в инкубаторе много проектов, которые завтра взлетят. Многие проекты, которые вошли в золотой стандарт это вчерашний инкубатор. Были, конечно, технологии, которые не взлетели. Так случилось, например, с технологией Theano, она появилась примерно в одно время с TensorFlow, но Theano куда-то исчезла.

  • УApacheесть целый набор инструментов, технологий, которые нужно знать среди нихSpark,Cassandra,Elasticsearchи другие.

  • Yarn, HDFS,MapReduce,Hive,Kafka,ZooKeeper этобазовые технологии, с которых всё начиналось.БазоваятехнологияHadoopникуда не делась,хотя онавыглядит немногоустаревшей, новсе принципы,которые в ней заложены,используются в современных технологиях.

  • Вразличных облачныхтехнологиях вAmazon,MicrosoftAzure,JCPесть свои аналогиHadoop, с которыми мы работаем.

  • Также актуальными являются инструменты защиты данных, такие какKerberos,Knox,Ranger.

  • Понятно, что различныеNoSQLиNewSQLбазы данных Cassandra, например(ужене новая),Snowflake,AmazonRedshift,HBase,MongoDB,Teradata.

  • DevOpsтехнологии Kubernetes, Docker, Jenkins.

  • Технологии визуализации данных:PowerBI,Tableau,QlikView.

  • ВDataScienceтоже множество различных фреймворков,напримерTensorFlowиGoogleBERT (который тоже ужевчерашний день, сегодняесть реализации лучше),PyTorch,Keras.

  • Отдельно стоит перечислить технологииStreaming.Streamingэто новый вызовмираData, поэтомуинструментыстоит знать Spark Streaming, KafkaStreams, ApacheFlink, Apache Storm.

Во многом набор знаний зависит от направления специалиста.

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

Для всех обязательно понимание процесса промышленной разработки, знание систем контроля версий. В последние годы обязательным становиться опыт работы с облаками, хотя бы с одним из наиболее популярных AWS,Azure, GCP.

Для тех, кто занимается ETL (загрузка и преобразование данных перед их использованием) обязательно понимание разницы ETL и ELT, стадий загрузки, способов проверки и очистки данных, понятияslowlychangeddimension. Также обязательно знание как минимум одного языка программирования для написания ETL вручную (PL/SQL, T-SQL,pgSQL,Python,Spark), оркестраторов для запуска процессов (например,Airflow), специализированных программ, каккомерческих, так и бесплатных (Talend,InformaticaPowerCenter,Pentaho,etc.).

Для репортеров (DataAnalyticsandVisualization), помимо знания хотя бы 2-х репортинговых программ (PowerBI,Tableau, TIBCOSpotfire,MicroStrategy,Pentaho, ит.д.) необходимо знание различных подходов в создании отчётов идашбордов(например,Storytelling).

А вы сами участвуете в разработке каких-то технологий?

Наши сотрудникиконтрибьютятвApache Spark, NiFi, Elasticsearch и многие другие. Любой сотрудник может принять участие в проекте. Даже врамках нашего обучающего курса, о котором ярассказывал,одно из заданий доработать какую-то фичу или исправить решение в Open Source проекте.

Кроме того,мы разрабатываемисвоиOpenSourceпродукты, например, Open Data Analytics Hub (ODAHU) проект, предоставляющий компоненты для создания систем автоматизации полного жизненного цикла ML моделей.

Какие технологии используются у вас на проектах?

Мы немного по-другому смотрим на то, как долженстроитьсяподход к управлению проектами в Data он основанненавыборетехнологий,ана методологиях. У нас есть несколько шаблонов (blueprint) для решения тех или иных задач. Это решения задач,с которыми мы часто сталкивались на наших проектах, ужепроверенныевременем.Грубо говоря, у нас естьшаблон,который мынаполняемтехнологиямив зависимостиот задач заказчика, от его приоритетов, инфраструктуры.

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

Отличаются ли подходы к проектам в разных отраслях?

Мыработаемснефтегазовой отраслью, с банками, сфармацевтическими компаниями,e-commerce,с медиа,со страховым бизнесом, в областиLifeScienceмногопроектоводним словом, в различных бизнес-направлениях.Может показаться, что всё это абсолютно разные направления задач,ноblueprintsпозволяют нам мыслить в одних шаблонах, решать разные задачи с помощью одних и тех же подходов.

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

Что изменил 2020 год?

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

The diffusion of innovations according to Rogers. (From Wikipedia)The diffusion of innovations according to Rogers. (From Wikipedia)

Про обучение

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

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

Чтобы начать учиться, необходимоиметьжелание,уверенность в будущемData, и умение программировать хотя бы на одном из языковJava,ScalaилиPython.

В тренинг-центре EPAM есть бесплатные курсы для начинающих специалистов, в том числе по направлениям Data Engineering, Data Science, BI, а также Python и другим языкам, которые помогут стартовать в профессии.

Что должен знать идеальный инженер, претендующий на место в команде Data в EPAM?

Выше подробно описан стек технологий. Если кратко, идеальныйDataгерой должен уметь программировать наJava,ScalaилиPython(вообще, большинство ребят полиглоты в терминах языков программирования),знатьSQL, понимать различные подходы к хранению и обработке данных, их плюсы и минусы, знать различные архитектуры построения гетерогенных систем, обязательно знатьDevOps-инструменты и методологии ведения проектов,умениеработать с облачными технологиямиипониманиеMachineLearningтакже приветствуются.

Подробнее..

Перевод Kindle собирает подозрительно много информации

27.08.2020 22:12:02 | Автор: admin


Я люблю читать и у меня есть несколько моделей Kindle, от самого первого до Paperwhite, они мне все нравятся.

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

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

Мне стало интересно, отправляет ли Kindle только самую необходимую информацию для поддержки этого функционала или может быть он шлет какую-то более персонализированную информацию обо мне?



Как оказалось, Kindle собирает много информации


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

Моя книжка отправила 100 запросов к Амазоновским серверам, а я лишь открыл книжку и перелистнул пару страниц:


(откройте в новой вкладке для большего размера)

Необоснованное количество информации


Kindle собирает информацию буквально обо всем в плоть до каждого тапа (клика) пользователя.

Каждый раз когда вы открываете страницу на сервер отправляется такая информация:

  • Время открытия страницы (каждый раз когда вы открываете новую страницу генерируется timestamp)
  • Первый символ на странице (индекс, например 7705 в книге)
  • Последний символ на странице
  • Является ли страница текстом или картинкой


Пример отправляемых данных при открытии страницы:
{    "created_timestamp": 1597743233808,    "payload": {        "context": "Reading",        "continuous_scroll_state": "disabled",        "end_position": 4708,        "is_scrolled_over_span": false,        "span_type": "Text",        "start_position": 4193    },    "schema_name": "kindle_positions_consumed_v2",    "schema_version": 0,    "sent_timestamp": 1597743233855,    "sequence_number": 26}


Каждая сессия так же записывается. Kindle отправляет информацию о том, сколько страницы вы прочитали, в какой ориентации (горизонтально или вертикально):

{    "created_timestamp": 1597743255324,    "payload": {        "action_type": "PageTurn",        "book_length": 2003478,        "context": "Reading",        "count": 10,        "navigation_end_location": 7884,        "navigation_mode": "Horizontal",        "navigation_start_location": 3599    },    "schema_name": "reader_in_book_navigation_v2",    "schema_version": 0,    "sent_timestamp": 1597743265854,    "sequence_number": 36}


Похожая информация отправляется, когда происходит открытия приложения, находится ли оно в фоновом режиме, когда было открыто, когда закрыто. Так же отправляется время смены шрифта, его размера. Каждое слово которое вы выделяете отправляется на сервера перевода Bing или Wikipedia, а затем к Амазону.

Эта информация вряд ли используется для определения на какой странице вы остановились.

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

Информация об устройстве


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

  • Страна проживания
  • Попытки подключения к сети (10 адресов)
  • Информация девайса версия, размеры, модель (читать можно на iphone, android, Kindle и тд).
  • Информацию об аккаунте Good Reads
  • Ориентацию устройства


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

Выводы


Kindle далек от того, что бы обвинять его в сборе персональных данных. Тем не менее он собирает много лишней поведенческой информации. В течение года я пытался избавиться от экосистемы Kindle и перешел на Marvin читая с iPhone. Я больше не использую Kindle, но скучаю за e-Ink.
К сожалению, достаточно трудно найти DRM-free книги.
Подробнее..

Перевод DataHub универсальный инструмент поиска и обнаружения метаданных

28.09.2020 12:18:44 | Автор: admin

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


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


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


Масштабирование метаданных


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


С момента нашего первого выпуска WhereHows в 2016 году, в отрасли наблюдается растущий интерес к повышению продуктивности специалистов по обработке данных с помощью метаданных. Например, инструменты, разработанные в этой области, включают Dataportal AirBnb, Databook Uber, Metacat Netflix, Amundsen Lyft и совсем недавно Data Catalog от Google. В LinkedIn мы также были заняты расширением объема сбора метаданных для новых вариантов использования при сохранении конфиденциальности. Однако мы пришли к выводу, что у WhereHows были фундаментальные ограничения, которые не позволяли удовлетворить наши растущие потребности в метаданных. Вот то, что мы смогли узнать во время работы с масштабированием WhereHows:


  1. Push лучше, чем pull: хотя получение метаданных непосредственно из источника кажется наиболее простым способом сбора метаданных. Более масштабируемым является использование отдельных поставщиков метаданных для передачи информации в центральный репозиторий через API или сообщения. Такой подход на основе push также обеспечивает более своевременное отображение новых и обновленных метаданных.
  2. Общее лучше, чем конкретное: WhereHows категорически придерживается мнения о том, как должны выглядеть метаданные для набора данных или задания. Это приводит к упрямому API, модели данных и формату хранения. Небольшое изменение модели метаданных приведет к каскаду необходимых изменений вверх и вниз по стеку. Он был бы более масштабируемым, если бы мы разработали общую архитектуру, не зависящую от модели метаданных, которую она хранит и обслуживает. Это, в свою очередь, позволило бы нам сосредоточиться на адаптации и развитии строго самоуверенных моделей метаданных, не беспокоясь о нижних уровнях стека.
  3. Онлайн так же важен, как и офлайн. После того, как метаданные собраны, естественно необходимо проанализировать эти метаданные, чтобы извлечь из них пользу. Одно из простых решений сбросить все метаданные в автономную систему, такую как Hadoop, где можно выполнять произвольный анализ. Однако вскоре мы обнаружили, что одной только поддержки автономного анализа недостаточно. Есть много вариантов использования, таких как управление доступом и обработка конфиденциальности данных, для которых необходимо запрашивать последние метаданные в Интернете.
  4. Взаимоотношения действительно важны. Метаданные часто передают важные взаимосвязи (например, происхождение, владение и зависимости), которые обеспечивают мощные возможности, такие как анализ воздействия, объединение данных, повышение релевантности поиска и т. д.
  5. Многоцентровая вселенная: мы поняли, что недостаточно просто моделировать метаданные, сосредоточенные вокруг одного объекта (набора данных). Существует целая экосистема данных, кода и человеческих сущностей (наборы данных, специалисты по обработке данных, команды, код, API микросервисов, показатели, функции ИИ, модели ИИ, информационные панели, записные книжки и т. Д.), Которые необходимо интегрировать и связать через единый граф метаданных.

Встречайте DataHub


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


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


Модульный интерфейс


Веб-приложение DataHub это то, как большинство пользователей взаимодействуют с метаданными. Приложение написано с использованием Ember Framework и работает на среднем уровне Play. Чтобы сделать разработку масштабируемой, мы используем различные современные веб-технологии, включая ES9, ES.Next, TypeScript, Yarn with Yarn Workspaces, а также инструменты качества кода, такие как Prettier и ESLint. Уровни представления, управления и данных разделены на пакеты, так что определенные представления в приложении построены на основе композиции соответствующих пакетов.


Структура обслуживания компонентов


Применяя модульную инфраструктуру пользовательского интерфейса, мы создали веб-приложение DataHub как серию связанных компонентов, согласованных по функциям, которые сгруппированы в устанавливаемые пакеты. Эта архитектура пакета использует в основе Yarn Workspaces и надстройки Ember и разбита на компоненты с использованием компонентов и сервисов Ember. Вы можете думать об этом как о пользовательском интерфейсе, который построен с использованием небольших строительных блоков (например, компонентов и сервисов) для создания более крупных строительных блоков (например, надстроек Ember и пакетов npm / Yarn), которые при объединении в конечном итоге составляют веб-приложение DataHub .


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


Взаимодействие с DataHub


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








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


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


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


Обобщенная архитектура метаданных


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


  1. Моделирование: моделируйте все типы метаданных и отношений в удобной для разработчиков манере.
  2. Прием: прием большого количества изменений метаданных в любом масштабе как через API, так и через потоки.
  3. Обслуживание: обслуживайте собранные необработанные и производные метаданные, а также множество сложных запросов к метаданным в любом масштабе.
  4. Индексирование: индексируйте метаданные в масштабе, а также автоматически обновляйте индексы при изменении метаданных.

Моделирование метаданных


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


  1. Метаданные это также данные: для моделирования метаданных нам нужен язык, который по крайней мере так же многофункциональн, как те, которые используются для моделирования данных общего назначения.
  2. Метаданные распределены: нереально ожидать, что все метаданные поступают из одного источника. Например, система, которая управляет списком управления доступом (ACL) набора данных, скорее всего, будет отличаться от той, которая хранит метаданные схемы. Хорошая среда моделирования должна позволять нескольким командам независимо развивать свои модели метаданных, одновременно представляя единое представление всех метаданных, связанных с объектом данных.

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


Чтобы продемонстрировать, как использовать Pegasus для моделирования метаданных, давайте рассмотрим простой пример, проиллюстрированный следующей измененной диаграммой сущностей-отношений (ERD).



Пример содержит три типа сущностей Пользователь, Группа и Набор данных представленных синими кружками на диаграмме. Мы используем стрелки для обозначения трех типов отношений между этими объектами, а именно OwnedBy, HasMember и HasAdmin. Другими словами, группа состоит из одного администратора и нескольких членов пользователя, которые, в свою очередь, могут владеть одним или несколькими наборами данных.


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


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


Чтобы смоделировать пример в Pegasus, мы переведем каждую из сущностей, отношений и аспектов метаданных в отдельный файл схемы Pegasus (PDSC). Для краткости мы включим сюда только по одной модели из каждой категории. Во-первых, давайте взглянем на PDSC для объекта User:


{  "type": "record",  "name": "User",  "fields": [    {      "name": "urn",      "type": "com.linkedin.common.UserUrn",    },    {      "name": "firstName",      "type": "string",      "optional": true    },    {      "name": "lastName",      "type": "string",      "optional": true    },    {      "name": "ldap",      "type": "com.linkedin.common.LDAP",      "optional": true    }  ]}

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


Далее следует модель PDSC для отношения OwnedBy:


{  "type": "record",  "name": "OwnedBy",  "fields": [    {      "name": "source",      "type": "com.linkedin.common.Urn",    },    {      "name": "destination",      "type": "com.linkedin.common.Urn",    },    {      "name": "type",      "type": "com.linkedin.common.OwnershipType",    }  ],  "pairings": [    {      "source": "com.linkedin.common.urn.DatasetUrn",      "destination": "com.linkedin.common.urn.UserUrn"    }  ]}

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


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


{  "type": "record",  "name": "Ownership",  "fields": [    {      "name": "owners",      "type": {        "type": "array",        "items": {          "name": "owner",          "type": "record",          "fields": [            {              "name": "type",              "type": "com.linkedin.common.OwnershipType"            },            {              "name": "ldap",              "type": "string"            }          ]        }      }    }  ]}

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


Получение метаданных


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


API DataHub основан на Rest.li, масштабируемой строго типизированной сервисной архитектуре RESTful, широко используемой в LinkedIn. Поскольку Rest.li использует Pegasus в качестве определения интерфейса, все модели метаданных, определенные в предыдущем разделе, могут использоваться дословно. Прошли те времена, когда требовалось преобразование нескольких уровней моделей от API до хранилища API и модели всегда будут синхронизироваться.


Ожидается, что для приема на основе Kafka производители метаданных будут генерировать стандартизированное событие изменения метаданных (MCE), которое содержит список предлагаемых изменений конкретных аспектов метаданных, введенных с помощью соответствующего URN объекта. Схема для MCE находится в Apache Avro, но автоматически создается из моделей метаданных Pegasus.


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


В LinkedIn мы склонны больше полагаться на поток Kafka из-за слабой связи, которую он обеспечивает между производителями и потребителями. Ежедневно мы получаем миллионы MCE от различных производителей, и ожидается, что их объем будет расти экспоненциально только по мере того, как мы расширяем объем нашей коллекции метаданных. Чтобы построить конвейер приема потоковых метаданных, мы использовали Apache Samza в качестве нашей платформы обработки потоковой информации. Задание Samza приема специально разработано, чтобы быть быстрым и простым для достижения высокой пропускной способности. Он просто преобразует данные Avro обратно в Pegasus и вызывает соответствующий API Rest.li для завершения приема.



Обслуживание метаданных


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


  1. Документно-ориентированные запросы
  2. Графические запросы
  3. Сложные запросы, включающие соединения
  4. Полнотекстовый поиск

Для этого DataHub необходимо использовать несколько типов систем данных, каждая из которых специализируется на масштабировании и обслуживании ограниченных типов запросов. Например, Espresso это база данных NoSQL LinkedIn, которая особенно хорошо подходит для масштабируемого документально-ориентированного CRUD. Точно так же Galene может легко индексировать и обслуживать полнотекстовый поиск в Интернете. Когда дело доходит до нетривиальных запросов к графам, неудивительно, что специализированная графовая БД может выполнять на порядки лучше, чем реализации на основе СУБД. Однако оказывается, что структура графа также является естественным способом представления отношений внешнего ключа, позволяя эффективно отвечать на сложные запросы соединения.


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



Еще одно ключевое преимущество абстракции DAO стандартизированный сбор данных об изменениях (CDC). Независимо от типа базовой системы хранения данных, любая операция обновления через DAO ключ-значение автоматически генерирует событие аудита метаданных (MAE). Каждый MAE содержит URN соответствующего объекта, а также изображения до и после определенного аспекта метаданных. Это позволяет использовать лямбда-архитектуру, в которой MAE могут обрабатываться как пакетами, так и потоками. Подобно MCE, схема MAE также автоматически генерируется из моделей метаданных.


Индексирование метаданных


Последний недостающий элемент головоломки конвейер индексации метаданных. Это система, которая объединяет модели метаданных и создает соответствующие индексы в графической БД и поисковой системе для облегчения эффективных запросов. Эти бизнес-логики фиксируются в форме построителя индексов и построителей графиков и выполняются как часть задания Samza, обрабатывающего MAE. Каждый разработчик зарегистрировал свой интерес к конкретным аспектам метаданных в задании и будет вызван с соответствующим MAE. Затем построитель возвращает список идемпотентных обновлений, которые будут применяться к БД индекса поиска или графа.


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



Заключение и с нетерпением жду


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


DataHub работает в LinkedIn в течение последних шести месяцев. Каждую неделю его посещают более 1500 сотрудников, которые поддерживают поиск, обнаружение и различные рабочие процессы для конкретных действий. График метаданных LinkedIn содержит более миллиона наборов данных, 23 системы хранения данных, 25 тысяч показателей, более 500 функций искусственного интеллекта и, что наиболее важно, всех сотрудников LinkedIn, которые являются создателями, потребителями и операторами этого графика.


Мы продолжаем улучшать DataHub, добавляя в продукт больше интересных пользовательских историй и алгоритмов релевантности. Мы также планируем добавить встроенную поддержку GraphQL и использовать язык Pegasus Domain Specific Language (PDL) для автоматизации генерации кода в ближайшем будущем. В то же время мы активно работаем над тем, чтобы поделиться этой эволюцией WhereHows с сообществом разработчиков ПО с открытым исходным кодом, а после публичного выпуска DataHub мы сделаем объявление.

Подробнее..

Перевод Как визуализируют своевременность данных в Airbnb

18.03.2021 18:13:59 | Автор: admin

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

  1. Когда считать, что набор опоздал?

  2. Какие данные часто опаздывают?

  3. По какой причине набор опоздал?

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


Данные запаздывают

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

Для своевременной поставки данных Airbnb мы стремимся к тому, чтобы владельцы каждого промежуточного шага фиксировали соглашения об уровне обслуживания (SLA) по доступности данных к конкретному времени. Например, владелец набора обещает, что метрика "бронирование" будет содержать самые актуальные данные к 5 утра по UTC, и если набор к этому времени недоступен, то он опоздал.

Как часто наборы опаздывают?

Сначала мы решили, что, опираясь на представление отчёта, Report поставщики данных должны понимать, когда данные выгружены и как часто они соответствуют SLA (рис. 1).

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

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

Рис. 1 SLA Report предоставляет высокоуровневый обзор производительности SLA по спискам наборов. Каждая строка содержит индикатор состояния последнего раздела данных, а также гистограммы, отражающие данные о времени выгрузки (красные столбцы показывают дни, когда время выгрузки не соответствует SLA).Рис. 1 SLA Report предоставляет высокоуровневый обзор производительности SLA по спискам наборов. Каждая строка содержит индикатор состояния последнего раздела данных, а также гистограммы, отражающие данные о времени выгрузки (красные столбцы показывают дни, когда время выгрузки не соответствует SLA).

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

Отчётность только вершина айсберга

Хотя Report сильно упрощает понимание того, действительно ли набор опаздывает, это представление не решило главные проблемы SLA:

  • Каково разумное SLA набора?

  • Как понять причину опоздания?

Это проблемные вопросы, потому что наборы зависят друг от друга и возникают последовательно: сначала одно преобразование, затем другое (рис. 2).

Рис. 2 Пример происхождения данных набора "A". "A" зависит от "B", который зависит от "C" и "D", и так далее.Рис. 2 Пример происхождения данных набора "A". "A" зависит от "B", который зависит от "C" и "D", и так далее.

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

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

Почему набор опоздал?

Ранний дизайн

Чтобы поставщики данных видели зависимости набора и временные рамках этих зависимостей, разработано представление о происхождении набора Lineage.

Информация о происхождении данных это от 10 до 100 таблиц, а каждая таблица это 30 дней исторических данных, а также SLA и связей между ними, поэтому мы нуждались в краткой форме представления, а это от 1,000 до 10,000 отдельных точек данных.

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

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

Фокус на времени с помощью представления Timeline

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

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

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

  • Если набор имеет SLA, время обозначается вертикальной линией.

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

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

  • Выделенные дуги представляют важнейшие узкие места.

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

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

Ищем иголку в стоге сена "узкие" места

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

Рис. 5 Сравнение всей линии происхождения (слева, n=82) и отфильтрованного пути к "узкому" месту (справа, n=8). Пути узких мест значительно улучшают соотношение сигнал шум и облегчают поиск проблемных этапов больших конвейерах.Рис. 5 Сравнение всей линии происхождения (слева, n=82) и отфильтрованного пути к "узкому" месту (справа, n=8). Пути узких мест значительно улучшают соотношение сигнал шум и облегчают поиск проблемных этапов больших конвейерах.

Погружение в историческое представление (Historical)

Итак, узкое место выявлено. Теперь важный вопрос вызвана задержка на этом этапе длительностью самой работы или замедлениями в зависимостях? Ответ на этот вопрос помогает поставщикам данных понять, нужно ли оптимизировать именно их конвейер, или, чтобы сократить время SLA, нужны переговоры с владельцами зависимостей. Чтобы позволить отслеживать причины, мы построили подробное представление выполнения выгрузки набора, показывающее длительность и выполнения, и задержки (рис. 6).

Рис. 6 Исторические распределения времени выполнения и задержек помогают быстро отличить SLA (красным цветом) из-за позднего начала вверху и сравнить с длительным выполнением внизу. Объединив эти взаимодополняющие представления в SLA Tracker, мы получаем полную перспективу своевременности данных (рис. 7).Рис. 6 Исторические распределения времени выполнения и задержек помогают быстро отличить SLA (красным цветом) из-за позднего начала вверху и сравнить с длительным выполнением внизу. Объединив эти взаимодополняющие представления в SLA Tracker, мы получаем полную перспективу своевременности данных (рис. 7).Рис. 7 Трекер SLA состоит из нескольких представлений. Представление Report даёт обзор состояния набора данных, Lineage позволяет провести анализ первопричин времени выгрузки, а Historical фиксирует исторические тенденции в подробностях.Рис. 7 Трекер SLA состоит из нескольких представлений. Представление Report даёт обзор состояния набора данных, Lineage позволяет провести анализ первопричин времени выгрузки, а Historical фиксирует исторические тенденции в подробностях.

Процесс и оснастка

Почти год мы потратили на разработку концепции, проектирование, создание прототипов и внедрения SLA Tracker в производственную среду. Большая часть этого времени потрачена на разработку API данных в UI и на итерации Lineage.

Чтобы упростить Report, мы использовали статические конструкции и прототипы экранов с хот-спотами (инструмент Clickthrough Prototypes) и универсальные поддельные данные. В альфа- и бета-релизах мы выполняли итерации визуального языка, то есть визуализировали данные так, чтобы их было проще охватить и понять (рис. 8).

Рис. 8 Эволюция визуального отображения времени выгрузки; отображены текущее и типичное время.Рис. 8 Эволюция визуального отображения времени выгрузки; отображены текущее и типичное время.

Совершенно иначе мы подошли к проектированию Lineage. Его информационная иерархия продиктована формой данных. Таким образом, критично прототипирование на выборках реальных данных. Мы разработали эти прототипы на TypeScript, используя низкоуровневый набор компонентов визуализации visx для React, этот набор позволяет повторно использовать код при внедрении в производственную среду (рис. 9).

Рис. 9 Эволюция диаграммы Ганта Lineage (слева направо): первые ящики с усами, множество промежутков; простые промежутки с дугами зависимостей; упрощение поиска узких мест.Рис. 9 Эволюция диаграммы Ганта Lineage (слева направо): первые ящики с усами, множество промежутков; простые промежутки с дугами зависимостей; упрощение поиска узких мест.

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

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

Заключение

В этом проекте мы применили визуализацию данных и UI/UX-дизайн междисциплинарную область, которую называем "Data Experience", в отношении важных проблем своевременности данных, требующих глубокого понимания сложной временной и иерархической информации. Это позволило сделать анализ своевременности данных доступным даже в сложной экосистеме данных крупной компании. Для разработки сложных инструментов визуального анализа требуются время и итерации, но результат работы может принести большую пользу.
Если хотите научиться работать с данными не хуже специалистов из Airbnb то приходите учиться. Будет сложно, но интересно!

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

К порядку правила создания конвейеров обработки данных

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

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

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

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

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

Правило наименьшего шага

Первое правило сформулировать легко: каждое отдельное взятое преобразование должно быть как можно проще и меньше.

Допустим, данные поступают на машину с POSIX-совместимой операционной системой. Каждая единица данных это JSON-объект, и эти объекты собираются в большие файлы-пакеты, содержащие по одному JSON-объекту на строку. Пускай каждый такой пакет весит около 10 Гб.

Над пакетом надо произвести три преобразования:

  1. Проверить ключи и значения каждого объекта.

  2. Применить к каждому объекту первую трансформацию (скажем, изменить схему объекта).

  3. Применить вторую трансформацию (внести новые данные).

Совершенно естественно всё это делать с помощью единственного скрипта на Python:

python transform.py < /input/batch.json > /output/batch.json

Блок-схема такого конвейера не выглядит сложной:

Проверка объектов в transform.py занимает около 10% времени, первое преобразование 70%, на остальное уходит 20% времени.

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

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

python validate.py < /input/batch.json > /tmp/validated.jsonpython transform1.py < /input/batch.json > /tmp/transformed1.jsonpython transform2.py < /input/transformed1.json > /output/batch.json

Блок-схема превращается в симпатичный паровозик:

Выгоды очевидны:

  • конкретные преобразования проще понять;

  • каждый этап можно протестировать отдельно;

  • промежуточные результаты отлично кешируются;

  • систему легко дополнить механизмами обработки ошибок;

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

Правило атомарности

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

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

python transform.py < /input/batch.json > /output/batch.json

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

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

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

В POSIX-совместимых файловых системах всегда есть атомарные операции (скажем, mv или ln), с помощью которых можно имитировать транзакции:

python transform.py < /input/batch.json > /output/batch.json.tmpmv /output/batch.json.tmp /output/batch.json

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

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

Правило идемпотентности

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

Википедия

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

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

python transform.py < /input/batch.json > /output/batch1.jsonpython transform.py < /input/batch.json > /output/batch2.jsondiff /input/batch1.json /output/batch2.json# файлы те жеpython transform.py < /input/batch.json > /output/batch3.jsondiff /input/batch2.json /output/batch3.json# никаких изменений

На входе у нас /input/batch.json, а на выходе /output/batch.json. И вне зависимости от того, сколько раз мы применим преобразование, мы должны получить одни и те же данные:

Так что если только transform.py не зависит от каких-то неявных входных данных, этап transform.py является идемпотентным (своего рода перезапускаемым).

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

Чем важна идемпотентность? В первую очередь это свойство упрощает обслуживание конвейера. Оно позволяет легко перезагружать подмножества данных после изменений в transform.py или входных данных в /input/batch.json. Информация будет идти по тем же маршрутам, попадёт в те же таблицы базы данных, окажется в тех же файлах и т. д.

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

Правило избыточности

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

Пример:

python transform1.py < /input/batch.json > /tmp/batch-1.jsonpython transform2.py < /tmp/batch-1.json > /tmp/batch-2.jsonpython transform3.py < /tmp/batch-2.json > /tmp/batch-3.jsoncp /tmp/batch-3.json /output/batch.json.tmp # не атомарно!mv /output/batch.json.tmp /output/batch.json # атомарно

Сохраняйте сырые (input/batch.json) и промежуточные (/tmp/batch-1.json, /tmp/batch-2.json, /tmp/batch-3.json) данные как можно дольше по меньшей мере до завершения цикла работы конвейера.

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

Другими словами: избыточность избыточных данных ваш лучший избыточный друг.

Заключение

Давайте подведём итоги:

  • разбивайте конвейер на изолированные маленькие этапы;

  • стремитесь делать этапы атомарными и идемпотентными;

  • сохраняйте избыточность данных (в разумных пределах).

Так обрабатываем данные и мы в Badoo и Bumble: они приходят через сотни тщательно подготовленных этапов преобразований, 99% из которых атомарные, небольшие и идемпотентные. Мы можем позволить себе изрядную избыточность, поэтому держим данные в больших холодном и горячем хранилищах, а между отдельными ключевыми преобразованиями имеем и сверхгорячий промежуточный кеш.

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

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

Подробнее..

Перевод Сравнение Java-записей, Lombok Data и Kotlin data-классов

16.06.2021 20:20:54 | Автор: admin

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

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

class Range {private final int low;private final int high;public Range(int low, int high) {this.low = low;this.high = high;}public int getLow() {return low;}public int getHigh() {return high;}@Overridepublic boolean equals(Object o) {if (this == o)return true;if (o == null || getClass() != o.getClass())return false;Range range = (Range) o;return low == range.low &&high == range.high;}@Overridepublic int hashCode() {return Objects.hash(low, high);}@Overridepublic String toString() {return "[" + low + "; " + high + "]";}}

в одну строчку кода:

//          это компоненты записи (components)record Range (int low, int hight) { }

Конечно, аннотации @Data и @Value из Lombok обеспечивают аналогичную функциональность с давних пор, хоть и с чуть большим количеством строк:

@Dataclass Range {private final int low;private final int high;}

А если вы знакомы с Kotlin, то знаете, что то же самое можно получить, используя data-класс:

data class Range(val low: Int, val high: Int)

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

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

Семантика записей (records)

В JEP 395 говорится:

Записи (records) это классы, которые действуют как прозрачные носители неизменяемых данных.

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

Если такая семантика не применима к нужному вам типу, то не используйте записи. А если вы все равно будете их использовать (возможно, соблазнившись отсутствием бойлерплейта или потому что вы думаете, что записи эквивалентны @Data / @Value и data-классам), то только испортите свою архитектуру, и велики шансы, что это обернется против вас. Так что лучше так не делать.

(Извините за резкость, но я должен был это сказать.)

Прозрачность и ограничения

Давайте подробнее поговорим о прозрачности (transparency). По этому поводу у записей есть даже девиз (перефразированный из Project Amber):

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

Для реализации этого необходимы ряд ограничений:

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

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

  • не должно быть никаких дополнительных полей (иначе API не будет моделировать состояние)

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

И Lombok и data-классы Kotlin позволяют создавать дополнительные поля, а также приватные "компоненты" (в терминах записей Java, а Kotlin называет их параметрами первичного конструктора). Так почему же Java относится к этому так строго? Чтобы ответить на этот вопрос, нам понадобится вспомнить немного математики.

Математика

Множество (set) это набор некоторых элементов. Например, можно сказать, что C это множество всех цветов {синий, золотой, ...}, а N множество всех натуральных чисел {0, 1, ...}. Конечное множество {-2147483648, ..., 0, ..., 2147483647} это то, что в Java мы называем типом int. А если добавить к этому множеству null, то получим Integer. Аналогично бесконечное множество всех возможных строк (плюс null) мы называем String.

Итак, как вы поняли, тип это множество, значения которого допустимы для данного типа. Это также означает, что теория множеств "раздел математики, в котором изучаются общие свойства множеств" (как говорит Википедия), связана с теорией типов "академическим изучением систем типов" (аналогично), на которую опирается проектирование языков программирования.

class Pair {private final int first;private final int second;}

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

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

// given: bijective function from int to intIntUnaryOperator increment =i -> i == Integer.MAX_VALUE ? Integer.MIN_VALUE : ++i;// then: combining two `increment`s yields a bijective function//       (this requires no additional proof or consideration)UnaryOperator<Pair> incrementPair =pair -> new Pair(increment.applyAsInt(pair.first()),increment.applyAsInt(pair.second()));

Вы обратили внимание на аксессоры Pair::first и Pair::second? Их не было в классе выше, поэтому их пришлось добавить. Иначе нельзя было бы применить функции к отдельным компонентам / операндам, и использовать Pair в качестве пары целых чисел. Аналогично, но с другой стороны, мне нужен конструктор, принимающий в качестве аргументов оба целых числа, чтобы можно было воспроизвести pair.

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

На самом деле записи лучше кортежей. В JEP 395 говорится:

Записи можно рассматривать как номинативные кортежи.

Где "номинативность" означает, что записи идентифицируются по их именам, а не по их структуре. Таким образом, два типа записей, которые моделируют int int, например, Pair(int first, int second) и Range(int low, int high) будут разными типами. А также обращение к компонентам записи идет не по индексу (range.get1()), а по имени (record.low()).

Следствия

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

Итак, если подытожить:

  • Аксессоры (методы доступа) генерируются компилятором.

  • Мы не можем изменять их имена или возвращаемый тип.

  • Мы должны быть очень осторожны с их переопределением.

  • Компилятор генерирует канонический конструктор.

  • Наследование отсутствует.

Преимущества записей

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

Деструктурирующие паттерны

if (range instanceof Range(int low, int high) && high < low)    return new Range(high, low);

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

Блок with

Range range = new Range(5, 10);// SYNTAX IS MADE UP!Range newRange = range with { low = 0; }// range: [5; 10]// newRange: [0; 10]

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

  • объявить переменные для компонент (например, low, high) и присвоить значения с помощью аксессоров

  • выполнить блок with

  • передать переменные в канонический конструктор

(Обратите внимание, что этот функционал далек от реальности и может быть не реализован или быть значительно изменен.)

Сериализация

Для представления объекта в виде потока байт, JSON / XML-документа или в виде любого другого внешнего представления и обратной конвертации, требуется механизм разбивки объекта на его значения, а затем сборки этих значений снова вместе. И вы сразу же можете увидеть, как это просто и хорошо работает с записями. Они не только раскрывают все свое состояние и предлагают канонический конструктор, но и делают это структурированным образом, что делает использование Reflection API очень простым.

Более подробно том, как записи изменили сериализацию, слушайте в подкасте Inside Java Podcast, episode 14 (также в Spotify). Если вы предпочитаете короткие тексты, то читайте твит.

Бойлерплейт код

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

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

  • аксессоры (методы доступа)

  • отсутствие наследования

Я не сказал об этом явно, но было бы неплохо, если (0, 0) = (0, 0), то есть должна быть правильная реализация equals, которая сразу же требует реализации hashCode.

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

Недостатки записей

Семантика записей ограничивает возможности по работе с классами. Как уже говорилось, вы не можете добавлять скрытое состояние через добавление полей, не можете переименовывать аксессоры и изменять тип возвращаемого значения и, вероятно, не должны менять возвращаемое ими значение. Записи также не позволяют изменять значения компонент, так как соответствующие им поля объявлены final. И отсутствует наследование классов (хотя вы можете реализовать интерфейсы).

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

Преимущества Lombok @Data/@Value

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

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

Преимущества data-классов Kotlin

Вот что говорится в документации о data-классах:

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

Вы можете видеть, что здесь также присутствует семантика хранения данных, но она довольно слабая, и основное внимание уделяется получению функциональности, то есть генерации кода. Действительно, data-классы предлагают больше возможностей по работе с классами, чем записи (мутабельные "компоненты", скрытое состояние, ...), но в отличие от Lombok не все (не могут расширяться, нельзя создавать свой метод copy, ...). С другой стороны, data-классы не дают сильных гарантий как записи, поэтому Kotlin не может построить на их основе аналогичную функциональность. Это разные подходы с разной ценой и выгодой.

Некоторые указывали на @JvmRecord в Kotlin как на большую ошибку: "Видите, data-классы могут быть записями шах и мат ответ" (я перефразировал, но смысл был такой). Если у вас возникли такие же мысли, то я прошу вас остановиться и подумать на секунду. Что именно это дает вам?

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

Для чего тогда нужен @JvmRecord? Просто для совместимости. Как говорится в proposal:

В Kotlin нет большого смысла использовать JVM-записи, за исключением двух случаев:

  • перенос существующей Java-записи на Kotlin с сохранением ее ABI;

  • генерация атрибута класса записи с информацией о компоненте записи для класса Kotlin для последующего чтения каким-либо фреймворком, использующим Java reflection для анализа записей.

Рефлексия

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

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


В преддверии старта курса "Java Developer. Professional" приглашаю всех желающих на бесплатный демоурок по теме: Система получения курсов валют ЦБ РФ.

Подробнее..

Apache Airflow делаем ETL проще

27.07.2020 12:16:39 | Автор: admin

Привет, я Дмитрий Логвиненко Data Engineer отдела аналитики группы компаний Везёт.


Я расскажу вам о замечательном инструменте для разработки ETL-процессов Apache Airflow. Но Airflow настолько универсален и многогранен, что вам стоит присмотреться к нему даже если вы не занимаетесь потоками данных, а имеете потребность периодически запускать какие-либо процессы и следить за их выполнением.


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



Что обычно видишь, когда гуглишь слово Airflow / Wikimedia Commons


Введение


Apache Airflow он прямо как Django:


  • написан на Python,
  • есть отличная админка,
  • неограниченно расширяем,

только лучше, да и сделан совсем для других целей, а именно (как написано до ката):


  • запуск и мониторинг задач на неограниченном количестве машин (сколько вам позволит Celery/Kubernetes и ваша совесть)
  • с динамической генерацией workflow из очень легкого для написания и восприятия Python-кода
  • и возможностью связывать друг с друг любые базы данных и API с помощью как готовых компонентов, так и самодельных плагинов (что делается чрезвычайно просто).

Мы используем Apache Airflow так:


  • собираем данные из различных источников (множество инстансов SQL Server и PostgreSQL, различные API с метриками приложений, даже 1С) в DWH и ODS (у нас это Vertica и Clickhouse).
  • как продвинутый cron, который запускает процессы консолидации данных на ODS, а также следит за их обслуживанием.

До недавнего времени наши потребности покрывал один небольшой сервер на 32 ядрах и 50 GB оперативки. В Airflow при этом работает:


  • более 200 дагов (собственно workflows, в которые мы набили задачки),
  • в каждом в среднем по 70 тасков,
  • запускается это добро (тоже в среднем) раз в час.

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


Есть три исходных SQL Serverа, на каждом по 50 баз данных инстансов одного проекта, соответственно, структура у них одинаковая (почти везде, муа-ха-ха), а значит в каждой есть таблица Orders (благо таблицу с таким названием можно затолкать в любой бизнес). Мы забираем данные, добавляя служебные поля (сервер-источник, база-источник, идентификатор ETL-задачи) и наивным образом бросим их в, скажем, Vertica.

Поехали!


Часть основная, практическая (и немного теоретическая)


Зачем оно нам (и вам)


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


  • Informatica Power Center крайне развесистая система, чрезвычайно производительная, со своими железками, собственным версионированием. Использовал я дай бог 1% её возможностей. Почему? Ну, во-первых, этот интерфейс где-то из нулевых психически давил на нас. Во-вторых, эта штуковина заточена под чрезвычайно навороченные процессы, яростное переиспользование компонентов и другие очень-важные-энтерпрайз-фишечки. Про то что стоит она, как крыло Airbus A380/год, мы промолчим.


    Осторожно, скриншот может сделать людям младше 30 немного больно




  • SQL Server Integration Server этим товарищем мы пользовались в своих внутрипроектных потоках. Ну а в самом деле: SQL Server мы уже используем, и не юзать его ETL-тулзы было бы как-то неразумно. Всё в нём в хорошо: и интерфейс красивый, и отчётики выполнения Но не за это мы любим программные продукты, ох не за это. Версионировать его dtsx (который представляет собой XML с перемешивающимися при сохранении нодами) мы можем, а толку? А сделать пакет тасков, который перетащит сотню таблиц с одного сервера на другой? Да что сотню, у вас от двадцати штук отвалится указательный палец, щёлкающий по мышиной кнопке. Но выглядит он, определенно, более модно:




Мы безусловно искали выходы. Дело даже почти дошло до самописного генератора SSIS-пакетов...


а потом меня нашла новая работа. А на ней меня настиг Apache Airflow.


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


Собираем кластер


Давайте не устраивать совсем уж детский сад, и не говорить тут о совершенно очевидных вещах, вроде установки Airflow, выбранной вами БД, Celery и других дел, описанных в доках.


Чтобы мы могли сразу приступить к экспериментам, я набросал docker-compose.yml в котором:


  • Поднимем собственно Airflow: Scheduler, Webserver. Там же будет крутится Flower для мониторинга Celery-задач (потому что его уже затолкали в apache/airflow:1.10.10-python3.7, а мы и не против);
  • PostgreSQL, в который Airflow будет писать свою служебную информацию (данные планировщика, статистика выполнения и т. д.), а Celery отмечать завершенные таски;
  • Redis, который будет выступать брокером задач для Celery;
  • Celery worker, который и займется непосредственным выполнением задачек.
  • В папку ./dags мы будет складывать наши файлы с описанием дагов. Они будут подхватываться на лету, поэтому передёргивать весь стек после каждого чиха не нужно.

Кое-где код в примерах приведен не полностью (чтобы не загромождать текст), а где-то он модифицируется в процессе. Цельные работающие примеры кода можно посмотреть в репозитории https://github.com/dm-logv/airflow-tutorial.

docker-compose.yml
version: '3.4'x-airflow-config: &airflow-config  AIRFLOW__CORE__DAGS_FOLDER: /dags  AIRFLOW__CORE__EXECUTOR: CeleryExecutor  AIRFLOW__CORE__FERNET_KEY: MJNz36Q8222VOQhBOmBROFrmeSxNOgTCMaVp2_HOtE0=  AIRFLOW__CORE__HOSTNAME_CALLABLE: airflow.utils.net:get_host_ip_address  AIRFLOW__CORE__SQL_ALCHEMY_CONN: postgres+psycopg2://airflow:airflow@airflow-db:5432/airflow  AIRFLOW__CORE__PARALLELISM: 128  AIRFLOW__CORE__DAG_CONCURRENCY: 16  AIRFLOW__CORE__MAX_ACTIVE_RUNS_PER_DAG: 4  AIRFLOW__CORE__LOAD_EXAMPLES: 'False'  AIRFLOW__CORE__LOAD_DEFAULT_CONNECTIONS: 'False'  AIRFLOW__EMAIL__DEFAULT_EMAIL_ON_RETRY: 'False'  AIRFLOW__EMAIL__DEFAULT_EMAIL_ON_FAILURE: 'False'  AIRFLOW__CELERY__BROKER_URL: redis://broker:6379/0  AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@airflow-db/airflowx-airflow-base: &airflow-base  image: apache/airflow:1.10.10-python3.7  entrypoint: /bin/bash  restart: always  volumes:    - ./dags:/dags    - ./requirements.txt:/requirements.txtservices:  # Redis as a Celery broker  broker:    image: redis:6.0.5-alpine  # DB for the Airflow metadata  airflow-db:    image: postgres:10.13-alpine    environment:      - POSTGRES_USER=airflow      - POSTGRES_PASSWORD=airflow      - POSTGRES_DB=airflow    volumes:      - ./db:/var/lib/postgresql/data  # Main container with Airflow Webserver, Scheduler, Celery Flower  airflow:    <<: *airflow-base    environment:      <<: *airflow-config      AIRFLOW__SCHEDULER__DAG_DIR_LIST_INTERVAL: 30      AIRFLOW__SCHEDULER__CATCHUP_BY_DEFAULT: 'False'      AIRFLOW__SCHEDULER__MAX_THREADS: 8      AIRFLOW__WEBSERVER__LOG_FETCH_TIMEOUT_SEC: 10    depends_on:      - airflow-db      - broker    command: >      -c " sleep 10 &&           pip install --user -r /requirements.txt &&           /entrypoint initdb &&          (/entrypoint webserver &) &&          (/entrypoint flower &) &&           /entrypoint scheduler"    ports:      # Celery Flower      - 5555:5555      # Airflow Webserver      - 8080:8080  # Celery worker, will be scaled using `--scale=n`  worker:    <<: *airflow-base    environment:      <<: *airflow-config    command: >      -c " sleep 10 &&           pip install --user -r /requirements.txt &&           /entrypoint worker"    depends_on:      - airflow      - airflow-db      - broker

Примечания:


  • В сборке композа я во многом опирался на известный образ puckel/docker-airflow обязательно посмотрите. Может, вам в жизни больше ничего и не понадобится.
  • Все настройки Airflow доступны не только через airflow.cfg, но и через переменные среды (слава разработчикам), чем я злостно воспользовался.
  • Естественно, он не production-ready: я намеренно не ставил heartbeats на контейнеры, не заморачивался с безопасностью. Но минимум, подходящий для наших экспериментиков я сделал.
  • Обратите внимание, что:
    • Папка с дагами должна быть доступна как планировщику, так и воркерам.
    • То же самое касается и всех сторонних библиотек они все должны быть установлены на машины с шедулером и воркерами.

Ну а теперь просто:


$ docker-compose up --scale worker=3

После того, как всё поднимется, можно смотреть на веб-интерфейсы:



Основные понятия


Если вы ничего не поняли во всех этих дагах, то вот краткий словарик:


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


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


  • DAG (он же даг) направленный ацикличный граф, но такое определение мало кому что скажет, а по сути это контейнер для взаимодействующих друг с другом тасков (см. ниже) или аналог Package в SSIS и Workflow в Informatica.


    Помимо дагов еще могут быть сабдаги, но мы до них скорее всего не доберёмся.


  • DAG Run инициализированный даг, которому присвоен свой execution_date. Даграны одного дага могут вполне работать параллельно (если вы, конечно, сделали свои таски идемпотентными).


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


    • action, как например наш любимый PythonOperator, который в силах выполнить любой (валидный) Python-код;
    • transfer, которые перевозят данные с места на место, скажем, MsSqlToHiveTransfer;
    • sensor же позволит реагировать или притормозить дальнейшее выполнение дага до наступления какого-либо события. HttpSensor может дергать указанный эндпойнт, и когда дождется нужный ответ, запустить трансфер GoogleCloudStorageToS3Operator. Пытливый ум спросит: зачем? Ведь можно делать повторы прямо в операторе! А затем, чтобы не забивать пул тасков подвисшими операторами. Сенсор запускается, проверяет и умирает до следующей попытки.

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


  • Task instance когда генерал-планировщик решил, что таски пора отправлять в бой на исполнители-воркеры (прямо на месте, если мы используем LocalExecutor или на удалённую ноду в случае с CeleryExecutor), он назначает им контекст (т. е. комплект переменных параметров выполнения), разворачивает шаблоны команд или запросов и складывает их в пул.



Генерируем таски


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


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


from datetime import timedelta, datetimefrom airflow import DAGfrom airflow.operators.python_operator import PythonOperatorfrom commons.datasources import sql_server_dsdag = DAG('orders',          schedule_interval=timedelta(hours=6),          start_date=datetime(2020, 7, 8, 0))def workflow(**context):    print(context)for conn_id, schema in sql_server_ds:    PythonOperator(        task_id=schema,        python_callable=workflow,        provide_context=True,        dag=dag)

Давайте разбираться:


  • Сперва импортируем нужные либы и кое что ещё;
  • sql_server_ds это List[namedtuple[str, str]] с именами коннектов из Airflow Connections и базами данных из которых мы будем забирать нашу табличку;
  • dag объявление нашего дага, которое обязательно должно лежать в globals(), иначе Airflow его не найдет. Дагу также нужно сказать:
    • что его зовут orders это имя потом будет маячить в веб-интерфейсе,
    • что работать он будет, начиная с полуночи восьмого июля,
    • а запускать он должен, примерно каждые 6 часов (для крутых парней здесь вместо timedelta() допустима cron-строка 0 0 0/6 ? * * *, для менее крутых выражение вроде @daily);
  • workflow() будет делать основную работу, но не сейчас. Сейчас мы просто высыпем наш контекст в лог.
  • А теперь простая магия создания тасков:
    • пробегаем по нашим источникам;
    • инициализируем PythonOperator, который будет выполнять нашу пустышку workflow(). Не забывайте указывать уникальное (в рамках дага) имя таска и подвязывать сам даг. Флаг provide_context в свою очередь насыпет в функцию дополнительных аргументов, которые мы бережно соберём с помощью **context.

Пока на этом всё. Что мы получили:


  • новый даг в веб-интерфейсе,
  • полторы сотни тасков, которые будут выполняться параллельно (если то позволят настройки Airflow, Celery и мощности серверов).

Ну, почти получили.



Зависимости кто будет ставить?


Чтобы всё это дело упростить я вкорячил в docker-compose.yml обработку requirements.txt на всех нодах.


Вот теперь понеслась:



Серые квадратики task instances, обработанные планировщиком.


Немного ждем, задачи расхватывают воркеры:



Зеленые, понятное дело, успешно отработавшие. Красные не очень успешно.


Кстати, на нашем проде никакой папки ./dags, синхронизирующейся между машинами нет всё даги лежат в git на нашем Gitlab, а Gitlab CI раскладывает обновления на машины при мёрдже в master.

Немного о Flower


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


Самая первая страничка с суммарной информацией по нодам-воркерам:



Самая насыщенная страничка с задачами, отправившимися в работу:



Самая скучная страничка с состоянием нашего брокера:



Самая яркая страничка с графиками состояния тасков и их временем выполнения:



Догружаем недогруженное


Итак, все таски отработали, можно уносить раненых.



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


Нужно смотреть лог и перезапускать упавшие task instances.


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



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



Понятно, что делать так мышкой со всеми красными квадратами не очень гуманно не этого мы ждем от Airflow. Естественно, у нас есть оружие массового поражения: Browse/Task Instances



Выберем всё разом и обнулим нажмем правильный пункт:



После очистки наши такси выглядят так (они уже ждут не дождутся, когда шедулер их запланирует):



Соединения, хуки и прочие переменные


Самое время посмотреть на следующий DAG, update_reports.py:


from collections import namedtuplefrom datetime import datetime, timedeltafrom textwrap import dedentfrom airflow import DAGfrom airflow.contrib.operators.vertica_operator import VerticaOperatorfrom airflow.operators.email_operator import EmailOperatorfrom airflow.utils.trigger_rule import TriggerRulefrom commons.operators import TelegramBotSendMessagedag = DAG('update_reports',          start_date=datetime(2020, 6, 7, 6),          schedule_interval=timedelta(days=1),          default_args={'retries': 3, 'retry_delay': timedelta(seconds=10)})Report = namedtuple('Report', 'source target')reports = [Report(f'{table}_view', table) for table in [    'reports.city_orders',    'reports.client_calls',    'reports.client_rates',    'reports.daily_orders',    'reports.order_duration']]email = EmailOperator(    task_id='email_success', dag=dag,    to='{{ var.value.all_the_kings_men }}',    subject='DWH Reports updated',    html_content=dedent("""Господа хорошие, отчеты обновлены"""),    trigger_rule=TriggerRule.ALL_SUCCESS)tg = TelegramBotSendMessage(    task_id='telegram_fail', dag=dag,    tg_bot_conn_id='tg_main',    chat_id='{{ var.value.failures_chat }}',    message=dedent("""\         Наташ, просыпайся, мы {{ dag.dag_id }} уронили        """),    trigger_rule=TriggerRule.ONE_FAILED)for source, target in reports:    queries = [f"TRUNCATE TABLE {target}",               f"INSERT INTO {target} SELECT * FROM {source}"]    report_update = VerticaOperator(        task_id=target.replace('reports.', ''),        sql=queries, vertica_conn_id='dwh',        task_concurrency=1, dag=dag)    report_update >> [email, tg]

Все ведь когда-нибудь делали обновлялку отчетов? Это снова она: есть список источников, откуда забрать данные; есть список, куда положить; не забываем посигналить, когда всё случилось или сломалось (ну это не про нас, нет).


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


  • from commons.operators import TelegramBotSendMessage нам ничто не мешает делать свои операторы, чем мы и воспользовались, сделав небольшую обёрточку для отправки сообщений в Разблокированный. (Об этом операторе мы еще поговорим ниже);
  • default_args={} даг может раздавать одни и те же аргументы всем своим операторам;
  • to='{{ var.value.all_the_kings_men }}' поле to у нас будет не захардкоженным, а формируемым динамически с помощью Jinja и переменной со списком email-ов, которую я заботливо положил в Admin/Variables;
  • trigger_rule=TriggerRule.ALL_SUCCESS условие запуска оператора. В нашем случае, письмо полетит боссам только если все зависимости отработали успешно;
  • tg_bot_conn_id='tg_main' аргументы conn_id принимают в себя идентификаторы соединений, которые мы создаем в Admin/Connections;
  • trigger_rule=TriggerRule.ONE_FAILED сообщения в Telegram улетят только при наличии упавших тасков;
  • task_concurrency=1 запрещаем одновременный запуск нескольких task instances одного таска. В противном случае, мы получим одновременный запуск нескольких VerticaOperator (смотрящих на одну таблицу);
  • report_update >> [email, tg] все VerticaOperator сойдутся в отправке письма и сообщения, вот так:


    Но так как у операторов-нотификаторов стоят разные условия запуска, работать будет только один. В Tree View всё выглядит несколько менее наглядно:



Скажу пару слов о макросах и их друзьях переменных.


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


SELECT    id,    payment_dtm,    payment_type,    client_idFROM orders.paymentsWHERE    payment_dtm::DATE = '{{ ds }}'::DATE

{{ ds }} развернется в содержимое переменной контекста execution_date в формате YYYY-MM-DD: 2020-07-14. Самое приятное, что переменные контекста прибиваются гвоздями к определенному инстансу таска (квадратику в Tree View), и при перезапуске плейсхолдеры раскроются в те же самые значения.


Присвоенные значения можно смотреть с помощью кнопки Rendered на каждом таск-инстансе. Вот так у таска с отправкой письма:



А так у таски с отправкой сообщения:



Полный список встроенных макросов для последней доступной версии доступен здесь: Macros Reference


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


Помимо предопределенных штук, мы можем подставлять значения своих переменных (выше в коде я уже этим воспользовался). Создадим в Admin/Variables пару штук:



Всё, можно пользоваться:


TelegramBotSendMessage(chat_id='{{ var.value.failures_chat }}')

В значении может быть скаляр, а может лежать и JSON. В случае JSON-а:


bot_config{    "bot": {        "token": 881hskdfASDA16641,        "name": "Verter"    },    "service": "TG"}

просто используем путь к нужному ключу: {{ var.json.bot_config.bot.token }}.


Скажу буквально одно слово и покажу один скриншот про соединения. Тут всё элементарно: на странице Admin/Connections создаем соединение, складываем туда наши логины/пароли и более специфичные параметры. Вот так:



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


А еще можно сделать несколько соединений с одним именем: в таком случае метод BaseHook.get_connection(), который достает нам соединения по имени, будет отдавать случайного из нескольких тёзок (было бы логичнее сделать Round Robin, но оставим это на совести разработчиков Airflow).


Variables и Connections, безусловно, классные средства, но важно не потерять баланс: какие части ваших потоков вы храните собственно в коде, а какие отдаете на хранение Airflow. C одной стороны быстро поменять значение, например, ящик рассылки, может быть удобно через UI. А с другой это всё-таки возврат к мышеклику, от которого мы (я) хотели избавиться.

Работа с соединениями это одна из задач хуков. Вообще хуки Airflow это точки подключения его к сторонним сервисам и библиотекам. К примеру, JiraHook откроет для нас клиент для взаимодействия с Jira (можно задачки подвигать туда-сюда), а с помощью SambaHook можно запушить локальный файл на smb-точку.


Разбираем кастомный оператор


И мы вплотную подобрались к тому, чтобы посмотреть на то, как сделан TelegramBotSendMessage


Код commons/operators.py с собственно оператором:


from typing import Unionfrom airflow.operators import BaseOperatorfrom commons.hooks import TelegramBotHook, TelegramBotclass TelegramBotSendMessage(BaseOperator):    """Send message to chat_id using TelegramBotHook    Example:        >>> TelegramBotSendMessage(        ...     task_id='telegram_fail', dag=dag,        ...     tg_bot_conn_id='tg_bot_default',        ...     chat_id='{{ var.value.all_the_young_dudes_chat }}',        ...     message='{{ dag.dag_id }} failed :(',        ...     trigger_rule=TriggerRule.ONE_FAILED)    """    template_fields = ['chat_id', 'message']    def __init__(self,                 chat_id: Union[int, str],                 message: str,                 tg_bot_conn_id: str = 'tg_bot_default',                 *args, **kwargs):        super().__init__(*args, **kwargs)        self._hook = TelegramBotHook(tg_bot_conn_id)        self.client: TelegramBot = self._hook.client        self.chat_id = chat_id        self.message = message    def execute(self, context):        print(f'Send "{self.message}" to the chat {self.chat_id}')        self.client.send_message(chat_id=self.chat_id,                                 message=self.message)

Здесь, как и остальное в Airflow, всё очень просто:


  • Отнаследовались от BaseOperator, который реализует довольно много Airflow-специфичных штук (посмотрите на досуге)
  • Объявили поля template_fields, в которых Jinja будет искать макросы для обработки.
  • Организовали правильные аргументы для __init__(), расставили умолчания, где надо.
  • Об инициализации предка тоже не забыли.
  • Открыли соответствующий хук TelegramBotHook, получили от него объект-клиент.
  • Оверрайднули (переопределили) метод BaseOperator.execute(), который Airfow будет подергивать, когда наступит время запускать оператор в нем мы и реализуем основное действие, на забыв залогироваться. (Логируемся, кстати, прямо в stdout и stderr Airflow всё перехватит, красиво обернет, разложит, куда надо.)

Давайте смотреть, что у нас в commons/hooks.py. Первая часть файлика, с самим хуком:


from typing import Unionfrom airflow.hooks.base_hook import BaseHookfrom requests_toolbelt.sessions import BaseUrlSessionclass TelegramBotHook(BaseHook):    """Telegram Bot API hook    Note: add a connection with empty Conn Type and don't forget    to fill Extra:        {"bot_token": "YOuRAwEsomeBOtToKen"}    """    def __init__(self,                 tg_bot_conn_id='tg_bot_default'):        super().__init__(tg_bot_conn_id)        self.tg_bot_conn_id = tg_bot_conn_id        self.tg_bot_token = None        self.client = None        self.get_conn()    def get_conn(self):        extra = self.get_connection(self.tg_bot_conn_id).extra_dejson        self.tg_bot_token = extra['bot_token']        self.client = TelegramBot(self.tg_bot_token)        return self.client

Я даже не знаю, что тут можно объяснять, просто отмечу важные моменты:


  • Наследуемся, думаем над аргументами в большинстве случаев он будет один: conn_id;
  • Переопределяем стандартные методы: я ограничился get_conn(), в котором я получаю параметры соединения по имени и всего-навсего достаю секцию extra (это поле для JSON), в которую я (по своей же инструкции!) положил токен Telegram-бота: {"bot_token": "YOuRAwEsomeBOtToKen"}.
  • Создаю экземпляр нашего TelegramBot, отдавая ему уже конкретный токен.

Вот и всё. Получить клиент из хука можно c помощью TelegramBotHook().clent или TelegramBotHook().get_conn().


И вторая часть файлика, в котором я сделать микрообёрточку для Telegram REST API, чтобы не тащить тот же python-telegram-bot ради одного метода sendMessage.


class TelegramBot:    """Telegram Bot API wrapper    Examples:        >>> TelegramBot('YOuRAwEsomeBOtToKen', '@myprettydebugchat').send_message('Hi, darling')        >>> TelegramBot('YOuRAwEsomeBOtToKen').send_message('Hi, darling', chat_id=-1762374628374)    """    API_ENDPOINT = 'https://api.telegram.org/bot{}/'    def __init__(self, tg_bot_token: str, chat_id: Union[int, str] = None):        self._base_url = TelegramBot.API_ENDPOINT.format(tg_bot_token)        self.session = BaseUrlSession(self._base_url)        self.chat_id = chat_id    def send_message(self, message: str, chat_id: Union[int, str] = None):        method = 'sendMessage'        payload = {'chat_id': chat_id or self.chat_id,                   'text': message,                   'parse_mode': 'MarkdownV2'}        response = self.session.post(method, data=payload).json()        if not response.get('ok'):            raise TelegramBotException(response)class TelegramBotException(Exception):    def __init__(self, *args, **kwargs):        super().__init__((args, kwargs))

Правильный путь сложить всё это: TelegramBotSendMessage, TelegramBotHook, TelegramBot в плагин, положить в общедоступный репозиторий, и отдать в Open Source.

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



В нашем даге что-то сломалось! А ни этого ли мы ждали? Именно!


Наливать-то будешь?


Чувствуете, что-то я пропустил? Вроде бы обещал данные из SQL Server в Vertica переливать, и тут взял и съехал с темы, негодяй!


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


План у нас был такой:


  1. Сделать даг
  2. Нагенерить таски
  3. Посмотреть, как всё красиво
  4. Присваивать заливкам номера сессий
  5. Забрать данные из SQL Server
  6. Положить данные в Vertica
  7. Собрать статистику

Итак, чтобы всё это запустить, я сделал маленькое дополнение к нашему docker-compose.yml:


docker-compose.db.yml
version: '3.4'x-mssql-base: &mssql-base  image: mcr.microsoft.com/mssql/server:2017-CU21-ubuntu-16.04  restart: always  environment:    ACCEPT_EULA: Y    MSSQL_PID: Express    SA_PASSWORD: SayThanksToSatiaAt2020    MSSQL_MEMORY_LIMIT_MB: 1024services:  dwh:    image: jbfavre/vertica:9.2.0-7_ubuntu-16.04  mssql_0:    <<: *mssql-base  mssql_1:    <<: *mssql-base  mssql_2:    <<: *mssql-base  mssql_init:    image: mio101/py3-sql-db-client-base    command: python3 ./mssql_init.py    depends_on:      - mssql_0      - mssql_1      - mssql_2    environment:      SA_PASSWORD: SayThanksToSatiaAt2020    volumes:      - ./mssql_init.py:/mssql_init.py      - ./dags/commons/datasources.py:/commons/datasources.py

Там мы поднимаем:


  • Vertica как хост dwh с самыми дефолтными настройками,
  • три экземпляра SQL Server,
  • наполняем базы в последних кое-какими данными (ни в коем случае не заглядывайте в mssql_init.py!)

Запускаем всё добро с помощью чуть более сложной, чем в прошлый раз, команды:


$ docker-compose -f docker-compose.yml -f docker-compose.db.yml up --scale worker=3

Что нагенерировал наш чудорандомайзер, можно, воспользовавшись пунктом Data Profiling/Ad Hoc Query:



Главное, не показывать это аналитикам


Подробно останавливаться на ETL-сессиях я не буду, там всё тривиально: делаем базу, в ней табличку, оборачиваем всё менеджером контекста, и теперь делаем так:


with Session(task_name) as session:    print('Load', session.id, 'started')    # Load worflow    ...    session.successful = True    session.loaded_rows = 15

session.py
from sys import stderrclass Session:    """ETL workflow session    Example:        with Session(task_name) as session:            print(session.id)            session.successful = True            session.loaded_rows = 15            session.comment = 'Well done'    """    def __init__(self, connection, task_name):        self.connection = connection        self.connection.autocommit = True        self._task_name = task_name        self._id = None        self.loaded_rows = None        self.successful = None        self.comment = None    def __enter__(self):        return self.open()    def __exit__(self, exc_type, exc_val, exc_tb):        if any(exc_type, exc_val, exc_tb):            self.successful = False            self.comment = f'{exc_type}: {exc_val}\n{exc_tb}'            print(exc_type, exc_val, exc_tb, file=stderr)        self.close()    def __repr__(self):        return (f'<{self.__class__.__name__} '                 f'id={self.id} '                 f'task_name="{self.task_name}">')    @property    def task_name(self):        return self._task_name    @property    def id(self):        return self._id    def _execute(self, query, *args):        with self.connection.cursor() as cursor:            cursor.execute(query, args)            return cursor.fetchone()[0]    def _create(self):        query = """            CREATE TABLE IF NOT EXISTS sessions (                id          SERIAL       NOT NULL PRIMARY KEY,                task_name   VARCHAR(200) NOT NULL,                started     TIMESTAMPTZ  NOT NULL DEFAULT current_timestamp,                finished    TIMESTAMPTZ           DEFAULT current_timestamp,                successful  BOOL,                loaded_rows INT,                comment     VARCHAR(500)            );            """        self._execute(query)    def open(self):        query = """            INSERT INTO sessions (task_name, finished)            VALUES (%s, NULL)            RETURNING id;            """        self._id = self._execute(query, self.task_name)        print(self, 'opened')        return self    def close(self):        if not self._id:            raise SessionClosedError('Session is not open')        query = """            UPDATE sessions            SET                finished    = DEFAULT,                successful  = %s,                loaded_rows = %s,                comment     = %s            WHERE                id = %s            RETURNING id;            """        self._execute(query, self.successful, self.loaded_rows,                      self.comment, self.id)        print(self, 'closed',              ', successful: ', self.successful,              ', Loaded: ', self.loaded_rows,              ', comment:', self.comment)class SessionError(Exception):    passclass SessionClosedError(SessionError):    pass

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


source_conn = MsSqlHook(mssql_conn_id=src_conn_id, schema=src_schema).get_conn()query = f"""    SELECT         id, start_time, end_time, type, data    FROM dbo.Orders    WHERE        CONVERT(DATE, start_time) = '{dt}'    """df = pd.read_sql_query(query, source_conn)

  1. С помощью хука получим из Airflow pymssql-коннект
  2. В запрос подставим ограничение в виде даты в функцию её подбросит шаблонизатор.
  3. Скармливаем наш запрос pandas, который достанет для нас DataFrame он нам пригодится в дальнейшем.

Я использую подстановку {dt} вместо параметра запроса %s не потому, что я злобный Буратино, а потому что pandas не может совладать с pymssql и подсовывает последнему params: List, хотя тот очень хочет tuple.
Также обратите внимание, что разработчик pymssql решил больше его не поддерживать, и самое время съехать на pyodbc.

Посмотрим, чем Airflow нашпиговал аргументы наших функций:



Если данных не оказалось, то продолжать смысла нет. Но считать заливку успешной тоже странно. Но это и не ошибка. А-а-а, что делать?! А вот что:


if df.empty:    raise AirflowSkipException('No rows to load')

AirflowSkipException скажет Airflow, что ошибки, собственно нет, а таск мы пропускаем. В интерфейсе будет не зеленый и не красный квадратик, а цвета pink.


Подбросим нашим данным несколько колонок:


df['etl_source'] = src_schemadf['etl_id'] = session.iddf['hash_id'] = hash_pandas_object(df[['etl_source', 'id']])

А именно:


  • БД, из которой мы забрали заказы,
  • Идентификатор нашей заливающей сессии (она будет разной на каждый таск),
  • Хэш от источника и идентификатора заказа чтобы в конечной базе (где всё ссыпется в одну таблицу) у нас был уникальный идентификатор заказа.

Остался предпоследний шаг: залить всё в Vertica. А, как ни странно, один из самых эффектных эффективных способов сделать это через CSV!


# Export data to CSV bufferbuffer = StringIO()df.to_csv(buffer,          index=False, sep='|', na_rep='NUL', quoting=csv.QUOTE_MINIMAL,          header=False, float_format='%.8f', doublequote=False, escapechar='\\')buffer.seek(0)# Push CSVtarget_conn = VerticaHook(vertica_conn_id=target_conn_id).get_conn()copy_stmt = f"""    COPY {target_table}({df.columns.to_list()})     FROM STDIN     DELIMITER '|'     ENCLOSED '"'     ABORT ON ERROR     NULL 'NUL'    """cursor = target_conn.cursor()cursor.copy(copy_stmt, buffer)

  1. Мы делаем спецприёмник StringIO.
  2. pandas любезно сложит в него наш DataFrame в виде CSV-строк.
  3. Откроем соединение к нашей любимой Vertica хуком.
  4. А теперь с помощью copy() отправим наши данные прямо в Вертику!

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


session.loaded_rows = cursor.rowcountsession.successful = True

Вот и всё.


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

create_schema_query = f'CREATE SCHEMA IF NOT EXISTS {target_schema};'create_table_query = f"""    CREATE TABLE IF NOT EXISTS {target_schema}.{target_table} (         id         INT,         start_time TIMESTAMP,         end_time   TIMESTAMP,         type       INT,         data       VARCHAR(32),         etl_source VARCHAR(200),         etl_id     INT,         hash_id    INT PRIMARY KEY     );"""create_table = VerticaOperator(    task_id='create_target',    sql=[create_schema_query,         create_table_query],    vertica_conn_id=target_conn_id,    task_concurrency=1,    dag=dag)

Я с помощью VerticaOperator() создаю схему БД и таблицу (если их еще нет, естественно). Главное, правильно расставить зависимости:

for conn_id, schema in sql_server_ds:    load = PythonOperator(        task_id=schema,        python_callable=workflow,        op_kwargs={            'src_conn_id': conn_id,            'src_schema': schema,            'dt': '{{ ds }}',            'target_conn_id': target_conn_id,            'target_table': f'{target_schema}.{target_table}'},        dag=dag)    create_table >> load

Подводим итоги


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

Джулия Дональдсон, Груффало


Думаю, если бы мы с моими коллегами устроили соревнование: кто быстрее составит и запустит с нуля ETL-процесс: они со своими SSIS и мышкой и я с Airflow А потом бы мы еще сравнили удобство сопровождения Ух, думаю, вы согласитесь, что я обойду их по всем фронтам!


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


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


Часть заключительная, справочно-информационная


Грабли, которые мы собрали за вас


  • start_date. Да, это уже локальный мемасик. Через главный аргумент дага start_date проходят все. Кратко, если указать в start_date текущую дату, а в schedule_interval один день, то DAG запустится завтра не раньше.


    start_date = datetime(2020, 7, 7, 0, 1, 2)
    

    И больше никаких проблем.


    С ним же связана и еще одна ошибка выполнения: Task is missing the start_date parameter, которая чаще всего говорит о том, что вы забыли привязать к оператору даг.


  • Всё на одной машине. Да, и базы (самого Airflow и нашей обмазки), и веб-сервер, и планировщик, и воркеры. И оно даже работало. Но со временем количество задач у сервисов росло, и когда PostgreSQL стал отдавать ответ по индексу за 20 с вместо 5 мс, мы его взяли и унесли.


  • LocalExecutor. Да, мы сидим на нём до сих пор, и мы уже подошли к краю пропасти. LocalExecutorа нам до сих пор хватало, но сейчас пришла пора расшириться минимум одним воркером, и придется поднапрячься, чтобы переехать на CeleryExecutor. А ввиду того, что с ним можно работать и на одной машиной, то ничего не останавливает от использования Celery даже не сервере, который естественно, никогда не пойдет в прод, чесслово!


  • Неиспользование встроенных средств:


    • Connections для хранения учетных данных сервисов,
    • SLA Misses для реагирования на таски, которые не отработали вовремя,
    • XCom для обмена метаданными (я сказал метаданными!) между тасками дага.

  • Злоупотребление почтой. Ну что тут сказать? Были настроены оповещения на все повторы упавших тасков. Теперь в моём рабочем Gmail >90k писем от Airflow, и веб-морда почты отказывается брать и удалять больше чем по 100 штук за раз.



Больше подводных камней: Apache Airflow Pitfails

Средства ещё большей автоматизации


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


  • REST API он до сих пор имеет статус Experimental, что не мешает ему работать. С его помощью можно не только получать информацию о дагах и тасках, но остановить/запустить даг, создать DAG Run или пул.


  • CLI через командную строку доступны многие средства, которые не просто неудобны в обращении через WebUI, а вообще отсутствуют. Например:


    • backfill нужен для повторного запуска инстансов тасков.
      Например, пришли аналитики, говорят: А у вас, товарищ, ерунда в данных с 1 по 13 января! Чини-чини-чини-чини!. А ты такой хоба:
      airflow backfill -s '2020-01-01' -e '2020-01-13' orders
      
    • Обслуживание базы: initdb, resetdb, upgradedb, checkdb.
    • run, который позволяет запустить один инстанс таска, да еще и забить на всё зависимости. Более того, можно запустить его через LocalExecutor, даже если у вас Celery-кластер.
    • Примерно то же самое делает test, только еще и в баз ничего не пишет.
    • connections позволяет массово создавать подключения из шелла.

  • Python API довольно хардкорный способ взаимодействия, который предназначен для плагинов, а не копошения в нём ручёнками. Но кто ж нам помешает пойти в /home/airflow/dags, запустить ipython и начать беспредельничать? Можно, например, экспортировать все подключения таком кодом:


    from airflow import settingsfrom airflow.models import Connectionfields = 'conn_id conn_type host port schema login password extra'.split()session = settings.Session()for conn in session.query(Connection).order_by(Connection.conn_id):  d = {field: getattr(conn, field) for field in fields}  print(conn.conn_id, '=', d)
    

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


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


    Осторожно, SQL!
    WITH last_executions AS (SELECT    task_id,    dag_id,    execution_date,    state,        row_number()        OVER (            PARTITION BY task_id, dag_id            ORDER BY execution_date DESC) AS rnFROM public.task_instanceWHERE    execution_date > now() - INTERVAL '2' DAY),failed AS (    SELECT        task_id,        dag_id,        execution_date,        state,        CASE WHEN rn = row_number() OVER (            PARTITION BY task_id, dag_id            ORDER BY execution_date DESC)                 THEN TRUE END AS last_fail_seq    FROM last_executions    WHERE        state IN ('failed', 'up_for_retry'))SELECT    task_id,    dag_id,    count(last_fail_seq)                       AS unsuccessful,    count(CASE WHEN last_fail_seq        AND state = 'failed' THEN 1 END)       AS failed,    count(CASE WHEN last_fail_seq        AND state = 'up_for_retry' THEN 1 END) AS up_for_retryFROM failedGROUP BY    task_id,    dag_idHAVING    count(last_fail_seq) > 0
    



Ссылки


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



И ссылки, задействованные в статье:


Подробнее..

Перевод Как загрузить данные в Google BigQuery

16.09.2020 12:14:05 | Автор: admin
Перевод статьи подготовлен в преддверии старта курса Нереляционные базы данных.




В этой статье мы рассмотрим варианты загрузки данных в облачное хранилище Google BigQuery. Сюда входят простые способы загрузки данных из CSV/JSON файлов и способы загрузки через API или расширение.

С помощью Google BigQuery (GBQ) можно собирать данные из разных источников и анализировать их с помощью SQL-запросов. Среди преимуществ GBQ высокая скорость вычислений даже на больших объемах данных и низкая стоимость.

Зачем нужно загружать данные в единое хранилище? Если вы хотите использовать сквозную аналитику, генерировать отчеты из сырых данных и оценивать эффективность вашего маркетинга, то вам нужен Google BigQuery.

Если вам нужно проанализировать терабайты данных за секунды, Google BigQuery самый простой и доступный выбор. Вы можете узнать больше об этом сервисе, посмотрев короткий видеоролик на YouTube-канале Google Developers.

Создание набора данных и таблицы


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


Изображения, используемые в статье, предоставлены автором

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



Затем создайте таблицу в наборе данных.



Готово! Теперь можете начать загрузку данных.

Загрузка данных с помощью Google Таблиц (расширение OWOX BI BigQuery Reports).


Если вам нужно загрузить данные из Google Таблиц в Google BigQuery, самый простой способ сделать это установить бесплатное расширение OWOX BI BigQuery Reports.

Вы можете установить это расширение прямо из Google Таблиц или из Chrome Web Store.



После его установки появится диалоговое окно с подсказками и запросом разрешений.



Теперь пора вернуться к Google Таблицам. Чтобы загрузить данные в BigQuery, просто выберите Upload data to BigQuery в меню Add-ons -> OWOX BI BigQuery Reports.



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

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

Чтобы создавать отчеты на основе точных сырых данных из всех источников и автоматически загружать их в репозиторий Google BigQuery, мы рекомендуем использовать сервис OWOX BI Pipeline.

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



Просто выберите источники данных и разрешите доступ; остальное оставьте OWOX BI.

С OWOX BI вы можете создавать отчеты на любой вкус и цвет, от ROI, ROPO-эффекта и когортного анализа до LTV и RFM анализа.

Загрузка данных из CSV-файлов


Чтобы загрузить данные из CSV-файла, в окне Create table нужно выбрать источник данных и использовать опцию Upload.



Затем выберите файл и его формат.



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

Примечание: В Google BigQuery вы можете выбрать два типа таблиц: в нативном формате и внешнем (external).




Google BigQuery автоматически определит структуру таблицы, но если вы хотите добавить поля вручную, вы можете использовать либо функцию редактирования текста, либо кнопку + Add field.

Примечание: Если вы хотите вмешаться в процесс парсинга данных из CSV-файла в Google BigQuery, вы можете воспользоваться расширенными параметрами.



Для получения дополнительной информации о формате CSV см. подробную документацию от Internet Society.

Загрузка данных из JSON-файлов


Чтобы загрузить данные из JSON-файла, повторите все шаги, приведенные выше: создайте или выберите набор данных и таблицу, с которыми вы работаете, только в качестве формата файла выберете JSON.
Вы можете загрузить JSON-файл со своего компьютера, Google Cloud Storage или диска Google Drive.



Примечание: Дополнительную информацию о формате JSON см. в документации Google Cloud.

Загрузка данных из Google Cloud Storage.


Google Cloud Storage позволяет безопасно хранить и передавать данные онлайн.

Полезная информация о работе с этим сервисом:

Начало работы с Google Cloud Storage
Документация Cloud Storage
Краткие руководства
Выбор хранилища и базы данных на Google Cloud Platform

Вы можете загружать файлы из Google Cloud Storage в Google BigQuery в следующих форматах:

  • CSV
  • JSON (с разделителями новой строки)
  • Avro
  • Parquet
  • ORC
  • Cloud Datastore




Подробнее об использовании Cloud Storage с big data можно прочитать в официальной документации.

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

Загрузка данных из других сервисов Google, таких как Google Ads и Google Ad Manager.


Чтобы загрузить данные из различных сервисов Google, сначала необходимо настроить BigQuery Data Transfer Service. Прежде чем вы сможете его использовать, вы должны выбрать или создать проект данных и, в большинстве случаев, включить для него биллинг. Например, биллинг является обязательным для следующих служб:

  • Campaign Manager
  • Google Ad Manager
  • Google Ads
  • Google Play (бета)
  • YouTube Channel Reports
  • YouTube Content Owner Reports


Примечание: Подробнее о настройке и изменении оплаты вы можете узнать в справочном центре Google Cloud.

Для того чтобы запустить BigQuery Data Transfer Service, на главной странице BigQuery выберите пункт Transfers в меню слева.



Примечание: Вам потребуются права администратора для создания Transferа.

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



Примечание: Доступ к BigQuery Data Transfer Service можно получить не только из консоли платформы, но и из:
  • сlassic bq_ui
  • bq command-line tool
  • BigQuery Data Transfer Service API


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

Загрузка данных с помощью API


Благодаря Cloud Client Libraries вы можете использовать свой любимый язык программирования для работы с API Google BigQuery.

Примечание: Более подробную информацию о загрузке данных с помощью API можно найти в документации Google Cloud.

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



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



В библиотеке вы можете использовать поиск по полю или отфильтровать API по категории.



Можно использовать набор скриптов на Python из OWOX BI для автоматизации импорта данных в Google BigQuery.
Там есть скрипты для автоматизации импорта данных в Google BigQuery из следующих источников:

  • amoCRM
  • FTP
  • FTPS
  • HTTP(S)
  • Intercom
  • ExpertSender
  • MySQL
  • SFTP


Эти Python-скрипты можно загрузить с GitHub.

Примечание: Узнайте, как использовать Python при работе с Google API, из этого видеоурока от Google Developers на YouTube.

Выводы


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

Подробнее..

Перевод Путеводитель по базам данных в 2021г

04.06.2021 20:14:20 | Автор: admin

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

Основные понятия баз данных

Что такое данные?

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

Что такое база данных?

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

Зачем нужна база данных?

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

Система управления базами данных (СУБД)

Итак, мы уже знаем, что данные и базы данных важны, но как осуществляется работа с базами данных в компьютерных системах? Вот тут на сцену и выходит СУБД. СУБД это программное обеспечение, предоставляющее нам способ взаимодействия с базами данных на компьютере для выполнения различных операций, таких как создание, редактирование, вставка данных и т.д. Для этого СУБД предоставляет нам соответствующие API. Редко какие программы не используют СУБД для работы с данными, хранящимися на диске.Помимо операций с данными СУБД также берет на себя резервное копирование, проверку допуска, проверку состояния базы данных и т.д. Поэтому рекомендуется всегда использовать СУБД при работе с базами данных.

Пространственные данные и база данных

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

Типы баз данных

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

Реляционные базы данных и РСУБД

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

Образец таблицы с информациейОбразец таблицы с информацией

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

Связь между двумя столбцамиСвязь между двумя столбцами

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

По сравнению с базами данных NoSQL, недостатком реляционных баз данных является относительно медленное получение результатов, когда количество данных стремительно увеличивается (по мнению автора статьи прим. пер.). Еще один недостаток заключается в том, что при добавлении каждой записи нужно следовать определенным правилам (типы столбцов, количество столбцов и т.д.), мы не можем просто добавить отдельный столбец только для одной записи.В реляционных базах данных используется SQL(Structured Query Language язык структурированных запросов), с помощью которого пользователи могут взаимодействовать с данными, хранящимися в таблицах. SQL стал одним из наиболее широко используемых языков для этой цели. Мы подробнее поговорим об SQL чуть позже.Вот примеры некоторых известных и часто используемых реляционных баз данных: PostgreSQL, MySQL, MSSQL и т.д. У каждой крупной компании, занимающейся реляционными базами данных, есть собственная версия SQL. В большинстве аспектов они выглядят одинаково, но иногда требуется немного изменить какой-нибудь запрос, чтобы получить те же результаты в другой базе данных (например, при переходе из PostgreSQL в MySQL).

Нереляционные базы данных (NoSQL)

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

  1. Пара ключ-значение

  2. Формат JSON, XML

  3. Графовый формат

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

База данных NoSQL реального времени в Google FirebaseБаза данных NoSQL реального времени в Google Firebase

База данных NoSQL реального времени в Google Firebase

При использовании баз данных NoSQL пользователям иногда приходится прописывать собственную логику, чтобы добавить уникальный ключ к каждой записи и тем самым обеспечить доступ к записям. В большинстве стандартных баз данных NoSQL, таких как Firebase и MongoDB, для хранения данных используется формат JSON. Благодаря этому очень легко и удобно выполнять операции с данными из веб-приложений, используя JavaScript, Python, Ruby и т.д.

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

Очевидно, что нам хотелось бы сохранить точку, линию, многоугольник, растры и т.д. так, чтобы это имело смысл, вместо того чтобы сохранять просто координаты. Нам нужна СУБД, которая позволяет не только сохранять данные, но и запрашивать их пространственными методами (буфер, пересечение, вычисление расстояния и т.д.). На сегодняшний день для этого лучше всего подходят реляционные базы данных, поскольку в SQL есть функции, помогающие выполнять подобные операции. Использование таких дополнительных средств, как PostGIS для PostgreSQL, открывает разработчикам возможности для написания сложных пространственных запросов. С другой стороны, NoSQL тоже работает в области геопространственных технологий: например, MongoDB предоставляет кое-какие функции для выполнения геопространственных операций. Однако реляционные базы данных все же лидируют на рынке с большим отрывом.

Работа с РСУБД

Основное внимание мы уделим РСУБД, так как именно эти системы в большинстве случаев мы будем использовать для хранения пространственных данных и работы с ними. В качестве примера мы будем использовать PostgreSQL, поскольку это самая перспективная реляционная база данных с открытым исходным кодом, а ее расширение PostGIS позволяет работать и с пространственными данными. Вы можете установить PostgreSQL, следуя инструкциям из документации. Помимо PostgreSQL рекомендуется также загрузить и установить pgAdmin. Платформа pgAdmin предоставляет веб-интерфейс для взаимодействия с базой данных. Также для этого можно загрузить и установить какое-либо другое совместимое ПО или использовать командную строку.

pgAdmin4 на MacpgAdmin4 на Mac

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

Создание базы данных. Нам нужно создать базу данных (в идеале должно быть по одной базе данных для каждого проекта).

Создание новой базы данных для проектаСоздание новой базы данных для проекта

В инструменте запросов (Query Tool) база данных создается следующим образом:

CREATE DATABASE <database_name>

Создание таблиц. Создание таблицы требует некоторых дополнительных соображений, поскольку именно здесь нам нужно определить все столбцы и типы данных в них. Все типы данных, которые можно использовать в PostgreSQL, вы найдете здесь.

pgAdmin позволяет нам выбрать в таблице различные ключи и ограничения, например Not Null (запрет на отсутствующие значения), Primary Key (первичный ключ) и т.д. Обсудим это подробнее чуть позже.

Создание таблицы пользователейСоздание таблицы пользователей

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

CREATE TABLE <table_name> (<column_1> <datatype>,<column_2> <datatype>,.....<column_n> <datatype>PRIMARY KEY (<column>));

CRUD-операции с данными в таблицах

CRUD-операции (создание, чтение, обновление и удаление Create, Retrieve, Update, Delete) это своего рода hello world в мире СУБД. Поскольку эти операции используются наиболее часто, команды для их выполнения одинаковы во всех РСУБД. Мы будем писать и выполнять запросы в инструменте запросов в pgAdmin, который вызывается следующим образом:

Инструмент запросов (Query Tool) в pgAdminИнструмент запросов (Query Tool) в pgAdmin

1. Создание новой записи

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

INSERT INTO <tablename> (column1, column2, column3,...) VALUES (value1, value2, value3,...);

INSERT, INTO, VALUE являются ключевыми словами в SQL, поэтому их нельзя использовать в качестве переменных, значений и т.д. Чтобы добавить новую запись в нашу таблицу пользователей, мы напишем в инструменте запросов следующий запрос:

INSERT INTO users(name, employed, address) VALUES ('Sheldon Cooper', true, 'Pasadena');

Обратите внимание: строки всегда следует заключать в'' (одинарные кавычки), а не в"" (двойные кавычки).

2. Получение записей (всех или нескольких)

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

select <column1, column2 ,...> from <tablename> 

Этот код извлекает весь набор данных. Если вы хотите получить только 20записей, напишите:

select <column1, column2 ,...> from <tablename> limit 20

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

select * from <tablename>

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

select * from <tablename> where <key> = <value>

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

--Retrieving Specific columns for all usersselect name,employed from users--Retrieving all columns for all usersselect * from users--Retrieving all columns for first 3 usersselect * from users limit 3--Retrieving all columns for all users where employed = trueselect * from users where employed = true

3. Обновление записей (всех или нескольких)РСУБД позволяет нам обновить все или только некоторые записи данных, указав новые значения для столбцов.

UPDATE <tablename> SET <column1> = <value1>, <column2> = <value2> 

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

UPDATE <tablename> SET <column1> = <value1>, <column2> = <value2>WHERE <column> = <value> 

В нашем случае мы обновим таблицы с помощью следующих запросов:

-- Make all rows as  employed = trueupdate users set employed = true-- change employed = false for entries with address = 'nebraska'update users set employed = false where address = 'nebraska'
Обновление записейОбновление записей

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

-- Deleting all entries Delete from <tablename> -- Deleting entries based on conditionsDelete from <tablename> where <column> = <value> 
-- Deleting all entries Delete from users-- Deleting entries based on conditionsDelete from users where employed = false
Удаление записей из таблицыУдаление записей из таблицы

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


Перевод подготовлен в рамках курса Базы данных. Все желающих приглашаем на бесплатный двухдневный онлайн-интенсив Бэкапы и репликация PostgreSQL. Практика применения. Цели занятия: настроить бэкапы; восстановить информацию после сбоя. Регистрация здесь.

Подробнее..

Перевод 5 вещей о наблюдаемости данных, которые должен знать каждый дата-инженер

26.05.2021 14:12:47 | Автор: admin

Как быть уверенным в своих рабочих процессах, конвейер за конвейером

В преддверии старта онлайн-курса "Data Engineer" подготовили перевод материала.


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

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

Джесси Андерсон, управляющий директор Big Data Institute и автор книги Команды инженерии данных: создание успешных Big Data команд и продуктов, и Барр Мозес, соучредитель и генеральный директор Monte Carlo, делятся всем, что вам нужно знать, чтобы начать работу на этом новом уровне стека данных.

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

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

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

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

Сообщение, которое вы никогда не получите: данные в этом отчете идеальны. Так держать!

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

Один из способов выбраться из это порочного круга ночных писем - наблюдаемость данных (Data Observability).

#1. Что такое наблюдаемость данных и почему это важно

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

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

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

#2. Как DevOps заложил наблюдаемость данных

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

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

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

  • Свежесть (Freshness) показывает, насколько актуальны ваши таблицы данных.

  • Распределение (Distribution) сообщает вам, попадают ли ваши данные в ожидаемый диапазон.

  • Объем (Volume ) предполагает понимание полноты ваших таблиц данных и состояния ваших источников данных.

  • Схема (Schema) позволяет понять, кто и когда вносит изменения в таблицы данных.

  • Происхождение (Lineage) сопоставляет вышестоящие источники и нижележащие приемники ваших данных, помогая определить, где произошли ошибки или сбои.

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

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

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

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

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

#4. Наблюдаемость данных - это больше, чем просто тщательное тестирование и мониторинг

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

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

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

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

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

#5. Когда дело доходит до ваших данных, иметь в основном плохие данные хуже, чем их вообще не иметь

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

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

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


- Узнать подробнее о курсе "Data Engineer"

Подробнее..

Лучшие data-продукты рождаются в полях

08.07.2020 16:06:56 | Автор: admin

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



image

Меня зовут Марина Калабина, яруководитель проектов вЛеруа Мерлен. Пришла вкомпанию в2011 году. Первые пять лет открывала магазины (когда япришла, ихбыло 13, сейчас 107), потом работала вмагазине вкачестве руководителя торгового сектора ивот уже полтора года занимаюсь тем, что спозиции Data-продакта помогаю магазинам организовывать операции.


Леруализмы


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


  • Сток запас товаров вмагазине.
  • Доступный для продажи сток количество товара, свободное отблокировок ирезервов для клиента.
  • Экспо витринный образец.
  • Артикулы товары.
  • Оперативная инвентаризация ежедневный пересчет 5 артикулов вкаждом отделе каждого магазина.

Гарантированный сток


Возможно, вынезнаете, нокогда выоформляете заказ вЛеруа Мерлен, в98% случаев онприходит вмагазин исобирается изторгового зала.


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


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


Для того чтобы бороться сразными проблемами, ивтом числе сэтой, впрошлом году вкомпании было запущено подразделение Data Accelerator. Его миссия привить data-культуру, чтобы принимаемые вкомпании решения были data-driven. ВData Accelerator было заявлено 126 идей, изних было выбрано 5 иодна изэтих идей это тот продукт Гарантированный сток, окотором ябуду рассказывать.


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


Унас была классная команда: Data Scientist, Data Engineer, Data Analysis, Product Owner иScrum-мастер.


Целями нашего продукта были:


  • сократить количество несобранных заказов, при этом неповредив количеству заказов впринципе (чтобы оно несократилось);
  • сохранить товарооборот вeCom, поскольку мыбудем меньше показывать товаров насайте.

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


Бюро расследований


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


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


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


image

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


image

Валидация


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


Что касается примеров, когда мынаходили слишком большое количество витринных образцов, практически в60% случаев мыбыли правы, предполагая ошибку. Акогда мыискали недостаточное количество экспо или ихотсутствие, тобыли правы в81%, что, вобщем-то, очень хорошие показатели.


Запуск MVP. Первый этап


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


Правило -1. Второй этап


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


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


ML-модель. Третий этап


Итак, мысделали ML-модель, запустили еевпрод в6магазинах. Какая унас получилась ML-модель?


  • Модель реализована спомощью градиентного бустинга наCatboost, иэто дает предсказание вероятности того, что сток товара вданном магазине вданный момент является некорректным.
  • Модель была обучена нарезультатах оперативной иежегодной инвентаризаций, ивтом числе наданных поотмененным заказам.
  • Вкачестве косвенных указаний навозможность некорректного стока использовались такие признаки, как данные опоследних движениях постоку данного товара, опродажах, возвратах изаказах, одоступном для продажи стоке, ономенклатуре, онекоторых характеристиках товара ипрочем.
  • Всего вмодели использовано около 70 фичей.
  • Среди всех признаков были отобраны важные сиспользованием различных подходов коценки важности, втом числе Permutation Importance иподходов, реализованных вбиблиотеке Catboost.
  • Чтобы проверить качество иподобрать гиперпараметры модели, данные были разбиты натестовую ивалидационную выборки всоотношении 80/20.
  • Модель была обучена наболее старых данных, апроверялась наболее новых.
  • Финальная модель, которая витоге пошла впрод, была обучена наполном датасете сиспользованием гиперпараметров, подобранных спомощью разбиения наtrain/valid-части.
  • Модель иданные для обучения модели версионируются спомощью DVC, версии модели идатасетов хранятся наS3.

Итоговые метрики полученной модели навалидационном наборе данных:


  • ROC-AUC: 0.68
  • Recall: 0.77

Архитектура


Немного про архитектуру как это унас реализуется впроде. Для обучения модели используются реплики операционных ипродуктовых систем компании, консолидированные ведином DataLake наплатформе GreenPlum. Наоснове реплик рассчитываются фичи, хранящиеся вMongoDB, что позволяет организовать горячий доступ кним. Оркестрация расчета фичей иинтеграция GreenPlum иMongoDB реализована сиспользованием opensource-стекаApache-инструментами Apache AirFlow иApache NiFi.


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


image

Результаты


Унас было 6магазинов ирезультаты показали, что изплановых 15% мысмогли сократить количество несобранных заказов на12%, при этом унас выросли товарооборот E-com иколичество заказов. Так что, мыненавредили, акак раз улучшили качество сборки заказов.


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


p.s.Статья написана по выступлению на митапе Avito.Tech, посмотреть видео можно по ссылке.

Подробнее..

Как распознать промышленные детали по фотографиям с помощью машинного зрения

14.10.2020 12:12:47 | Автор: admin

Привет, Хабр! Сегодня поговорим о том, как нейронные сети могут помочь в распознавании деталей и зачем это вообще нужно. Недавно к нам обратился один из наших клиентов - крупная промышленная компания, производитель грузовых автомобилей и их комплектующих. Детали насчитывали большое количество возможных наименований. Из-за этого при визуальном распознавании сотрудники совершали ошибки. Решили создать приложение на основе компьютерного зрения и нейронных сетей. С его помощью стали проверять правильный ли выбор сделал рабочий (рис 1). Так же дополнительно было необходимо сверять наименование распознанной детали с наименованием, указанным в накладной на заказ.

Рис. 1Рис. 1

Данные

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

Выбор модели

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

Использовать только ограничивающие рамки (Bounding Box) было бы недостаточно (Object detection). Они могли сильно перекрывать друг друга при разметки, обучении и распознавании, поэтому для обучения решили выбрать одну из моделей, с поддержкой метода сегментации изображения (Image segmentation) . (Рис. 2)

Рис. 2Рис. 2

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

В нашем случае разметка объектов на изображении выполнялась вручную на стороннем облачном сервисе. Он предоставлял возможность нескольким сотрудникам размечать один и тот же набор данных удаленно и после завершения разметки скачивать весь набор данных. (Рис. 3, 4, 5, 6)

Рис. 3Рис. 3Рис. 4Рис. 4Рис. 5Рис. 5Рис.6Рис.6

После разметки достаточного количества фотографий для экспериментов проводилось обучение первых моделей для распознавания деталей на сервере HPE DL380 c двумя видеокартами NVIDIA Tesla v100. В среднем, на обучение первых моделей было затрачено от 8 до 12 часов.

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

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

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

Что делать с ликами?

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

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

Рис.7. Пример фотографий первого классаРис.7. Пример фотографий первого классаРис. 8. Пример фотографий второго класса Рис. 8. Пример фотографий второго класса

Что делать с зеркальными деталями?

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

Рис.9Рис.9

Как создать модель для разметки

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

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

После этого модели для распознавания были обучены и подобраны параметры. (Рис. 10, 11).

Рис..10Рис..10Рис.11Рис.11

Распознавание бирок и накладных

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

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

Для удобного развёртывания моделей весь backend был перенесен на SAP Data Intelligence.

Интерфейс и SAP Data intelligence

SAP Data Intelligence позволяет не только публиковать и встраивать модели, но и создавать на их базе новые собственные операторы (например, от python оператора). Это помогает переиспользовать существующие модели и встраивать их в необходимых форматах (батч-обработка, стриминг, или публикация REST-сервисов). Кроме этого, основанный на flow-based подходе пайплайн может быть быстро адаптирован под меняющиеся условия. Например, если в будущем потребуется интеграция с ERP / MES или любой другой системой, это будет сделать довольно просто. Также все получаемые фотографии можно собирать в используемое Озеро Данных для дообучения модели и улучшения качества распознавания. Если потребуется масштабировать данный сервис, это будет сделать легко. Достаточно настроить уровень параллелизма (параметр multiplicity) и под модель будет поднято соответствующее количество реплик на уровне kubernetes. Есть встроенные инструменты для отладки пайплайна, логирования, трассировки, мониторинга.

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

Так как SAP Data Intelligence полностью построен на контейнерах, важно, чтобы были решены задачи отказоустойчивости кластера Kubernetes и его интеграция с сетями центра обработки данных, в котором будет развернуто решение. Фактически, мы полностью повторили в лаборатории типовой валидированный дизайн Cisco&SAP, описанный здесь и голова за инфраструктуру у нас больше не болела.

В SAP Data Intelligence был создан контейнер со всеми необходимыми библиотеками. Для публикации сервиса использовался стандартный оператор OpenAPI. Весь backend работал в контейнере на сервере. Пайплайн можно было так же запускать на любом другом сервере Data Intelligence (рис. 12).

Рис.12. Архитектура, используемая для реализации задачРис.12. Архитектура, используемая для реализации задачРис .13. Пайплайн в Data intelligenceРис .13. Пайплайн в Data intelligence

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

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

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

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

Подробнее..

Перевод Need for speed. Пакетная обработка данных с TiSpark

01.02.2021 14:09:27 | Автор: admin
TiSpark это подключаемый модуль Apache Spark, который работает с платформой TiDB и отвечает на запросы сложной интерактивной аналитической обработки (OLAP). Этот плагин Spark широко используется для пакетной обработки больших объёмов данных и для получения аналитических инсайтов. Я старший архитектор решений в PingCAP и бывший разработчик TiSpark. В этом посте я объясню, как он работает и почему TiSpark лучше традиционных решений для пакетной обработки.



Пакетная обработка: традиции и TiSpark


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

Традиционная архитектура пакетной обработки

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

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

Пакетная обработка с помощью TiSpark

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

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

  • Он очень быстрый. TiSpark обходит TiDB и конкурентно записывает данные в TiKV в режиме многие ко многим. Это даёт горизонтальную масштабируемость. Если узкое место TiKV или Apache Spark, вы можете просто добавить ещё один узел TiKV или Spark, чтобы сделать хранилище больше или увеличить вычислительную мощность.
  • Его легко конфигурировать. Единственное, что вы настраиваете, указание Spark о том, как использовать TiSpark. Логика пакетной обработки в TiSpark в основном совместима с DataSource API в Spark, поэтому вы настроите TiSpark без труда, как только разберётесь с DataSource API и DataFrame API.
  • Транзакции гарантируются. Запись данных будет успешной или неудачной. Реальный кейс показывает, что TiSpark может записать 60 миллионов строк данных TPC-H LINEITEM за 8 минут.

Что под капотом?


Архитектура


Рисунок ниже показывает роль TiSpark во всём кластере TiDB:

Компоненты кластера TiDB

Компоненты на рисунке маркированы цветами:


Когда TiSpark получает задачу и обрабатывает данные, перед записью данных он блокирует таблицы. Это предотвращает откат TiSpark его собственной транзакции из-за конфликтов с другими транзакциями. Нам не хочется никаких откатов вроде этого, потому что TiSpark обычно обрабатывает сотни миллионов строк данных и это отнимает много времени. Такое поведение блокировки таблицы применимо только к TiDB 3.0.14 и выше. В версии 4.0.x TiDB мы изменили протокол транзакций, и теперь он поддерживает крупные транзакции до 10 GB. Когда TiSpark совместим с модификацией протокола, нет необходимости блокировать таблицы. Далее TiSpark классифицирует, подсчитывает, сэмплирует и рассчитывает данные для записи и оценивает, сколько новых регионов должно генерироваться при пакетной записи. Затем он передаёт информацию в TiDB. TiDB взаимодействует с другими компонентами и предварительно разделяется на нужное количество регионов. Предварительное разделение регионов позволяет избежать таких проблем, как:

  • Горячие точки (hot spots).
  • Деградация производительности TiSpark при записи, вызванная разбиением региона в то же самое время.

Записывая данные, TiSpark также взаимодействует с PD двумя способами:

  • Получает мета-информацию. TiKV хранит пары ключ значение, поэтому перед записью TiSpark преобразует все строки данных в пары ключ значение. TiSpark должен знать, в какой регион записывать пары, то есть ему нужно получить соответствующий адрес региона.
  • Запрашивает временную метку от PD для гарантии транзакций. Вы можете рассматривать эту временную метку как идентификатор транзакции. Чтобы конкурентно записывать сгенерированные пары в TiKV, TiSpark использует Spark Worker.

Реализация


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

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

  • Клиент реализует интерфейс сопроцессора. Он может взаимодействовать с TiKV или TiFlash и выполнять некоторые вычисления, такие как вычисления лимита, порядка и агрегации. Клиент также обрабатывает некоторые предикаты, индексы и поля ключ значение. Например, он может оптимизировать запрос с индексом, чтобы не сканировалась вся таблица.
  • Клиент реализует протокол двухфазной фиксации, гарантируя, что записи TiSpark соответствуют ACID. Клиент также поддерживает некоторую статистику и информацию об индексах, которые, когда создаётся план выполнения, помогают Spark выбрать лучший путь, чтобы выполнить запрос.

Клиент TiKV позволяет TiSpark взаимодействовать с TiKV и TiFlash. Другая ключевая проблема как сообщить Spark результат этого взаимодействия.

TiSpark использует Extensions Point в Spark как входную точку, что снижает стоимость поддержки полного набора кода Spark и позволяет настраивать оптимизатор Spark Catalyst. Например, в план выполнения Spark можно легко внедрить логику доступа к TiKV или TiFlash.

TiSpark гарантирует транзакциям ACID-свойства как для записи одной и нескольких таблиц. Для записи в одну таблицу TiSpark полностью совместим с Spark DataSource API, потому что фрейм данных Spark подобен одной таблице. Для записи нескольких таблиц вы можете использовать дополнительный интерфейс, поддерживаемый TiSpark, для сопоставления таблиц базы данных со Spark DataFrame. Например, вы можете сопоставить таблицу с фреймом данных через имя базы данных и имя таблицы, а затем поместить эту информацию в сопоставление. Предположим, вам нужно записать три таблицы, тогда в сопоставлении должно быть три элемента.

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

Если вы знакомы со Spark, вы можете задаться вопросом: DataFrames в Spark похожи на одну таблицу. Не сложно ли будет объединить их из-за несовместимой структуры таблиц? Что ж, не волнуйтесь. Формат данных TiKV это пары ключ значение. Во время записи нескольких таблиц они объединяются только после преобразования DataFrames в пары ключ значение.

Приложение


Как TiSpark сочетается с вашей существующей системой распределённых приложений?
Предположим, у вас есть распределённая система, состоящая из трёх частей:

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

Вы можете интегрировать TiSpark во фреймворк пакетного приложения, чтобы планировать и обрабатывать пакетные задачи.

TiSpark обрабатывает данные через интерфейсы DataFrame или Spark SQL.

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

// DataFrame API implementationval dfWithDeducted = df.withColumn("toBeDeducted",                    df("loan") * df("interestRate"))val finalDF = dfWithDeducted                    .withColumn("balance",                        dfWithDeducted("balance")                        - dfWithDeducted("toBeDeducted"))                    .drop("toBeDeducted")// Spark SQL implementationval df = spark.sql("select *, (balance - load * interestRate) as newBala from a").drop("balance")val finalDF = df.withColumnRenamed("newBala", "balance")

  1. Найти столбцы ссуды и процентной ставки с помощью интерфейса DataFrame.
  2. Воспользоваться простой арифметической операцией, чтобы вычислить проценты.
  3. Создать новый столбец с именем toBeDeducted при помощи интерфейса withColumn.
  4. Вычесть значение toBeDeducted из исходного баланса и получить новый баланс.
  5. Удалить столбец toBeDeducted.

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

  • Таблица бонусных баллов: хранит текущие баллы пользователя.
  • Таблица расходов: хранит ежемесячные расходы пользователя.
  • Таблица правил: хранит правила скидок. У разных продавцов правила скидок различаются. Скидка в ювелирных магазинов 1:2; то есть 1 доллар это 2 балла.

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

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

Визуализация


Отправляя задачу в TiSpark, вы можете следить за её выполнением. Рисунок ниже показывает пакетную обработку, которая записывает 4 миллиона строк данных:

Мониторинг задач в TiSpark

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



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



image



Подробнее..

Recovery mode Как компании выбрать инструменты для дата-инженеров и не превратить всё в технологический зоопарк опыт PROFI.RU

21.08.2020 12:12:17 | Автор: admin
Редактор Нетологии побеседовала с тимлидом команды BI в Profi.ru Павлом Саяпиным о том, какие задачи решают дата-инженеры в его команде, что за инструменты для этого используют и как же всё-таки правильно подойти к выбору инструментария для решения дата-задач, в том числе нетипичных. Павел преподаватель на курсе Дата-инженер.

Чем занимаются дата-инженеры в Profi.ru


Profi.ru это сервис, который помогает встретиться клиентам и специалистам самых различных сфер. В базе сервиса более 900 тысяч специалистов по 700 видам услуг: репетиторы, мастера по ремонту, тренеры, мастера красоты, артисты и другие. Ежедневно регистрируется более 10 тысяч новых заказов всё это даёт порядка 100 млн событий в день. Поддерживать порядок в таком количестве данных невозможно без профессиональных дата-инженеров.

В идеале Data Engineer развивает культуру работы с данными, с помощью которой компания может получать дополнительную прибыль или сокращать издержки. Приносит ценность бизнесу, работая в команде и выступая важным звеном между различными участниками от разработчиков до бизнес-потребителей отчётности. Но в каждой компании задачи могут отличаться, поэтому рассмотрим их на примере Profi.ru.

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


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


Место процесса Data Quality в общей структуре хранилища данных

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

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

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

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

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

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


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

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

Делают дашборды


Визуализацию данных лучше делать в профессиональном инструменте например, в Tableau.

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

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

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


Пример продакт-дашборда Profi.ru (один из листов). В целях конфиденциальности информации названия метрик и осей скрыты

Примеры реальных задач


Задача 1 перелить данные из исходных (операционных) систем в хранилище данных или ETL


Одна из рутинных задач дата-инженера.

Для этого могут использоваться:

  • самописные скрипты, запускаемые по cron или с помощью специального оркестровщика типа Airflow или Prefect;
  • ETL-решения с открытым кодом: Pentaho Data Integration, Talend Data Studio и другие;
  • проприетарные решения: Informatica PowerCenter, SSIS и другие;
  • облачные решение: Matillion, Panoply и другие.

В простом исполнении задача решается написанием YML-файла строк на 20. Занимает это минут 5.

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

В Profi эта простая задача при отлаженном процессе состоит из следующих шагов:

  • Выяснить у заказчика, какие нужны данные и где они лежат.
  • Понять, есть ли доступы к этим данным.
  • Если доступов нет, запросить у админов.
  • Добавить новую ветку в Git с кодом задачи в Jira.
  • Создать миграцию на добавление данных в якорную модель через интерактивный Python-скрипт.
  • Добавить файлы прогрузок (YML-файл с описанием того, откуда данные берутся и в какую таблицу записываются).
  • Протестировать на стенде.
  • Залить данные в репозиторий.
  • Создать pull request.
  • Пройти код ревью.
  • После прохождения код-ревью данные заливаются в мастер-ветку и автоматически раскатываются в продуктив (CI/CD).

Задача 2 удобно разместить загруженные данные


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

Для этого могут применяться решения из 1-й задачи, так как это также ETL-процесс. В самом простом варианте обновление DDS осуществляется с помощью SQL-скриптов.

Задача 3 из разряда нетипичных задач


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

Используем движок на базе Apache Flink. Пока порядок действий такой: движок обрабатывает входящий поток событий складывает их пачками в ClickHouse на ходу считает количество событий за 15 минут передаёт их сервису, который определяет, есть ли аномалии сравнивает со значениями за аналогичные 15 минут с глубиной в 3 месяца если есть, кидает оповещение в Slack.


Схема для фронтовой аналитики (часть загрузки)

Фреймворк Apache Flink гарантирует доставку как минимум один раз. Тем не менее появляется вероятность дубликатов. В случае с RabbitMQ это можно решить, используя Correlation ID. Тогда гарантируется единичная доставка целостность данных.

Считаем количество событий опять же с помощью Apache Flink, выводим через самописный дашборд, написанный на NodeJS, + фронт на ReactJS. Быстрый поиск не дал похожих решений. Да и сам код получился простым написание не заняло много времени.

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

Основные инструменты дата-инженеров


С задачами дата-инженеров более-менее понятно, теперь немного об инструментах, которые используются для их решения. Конечно, инструменты в разных компаниях могут (и должны) отличаться всё зависит от объема данных, их скорости поступления и неоднородности. Также может зависеть от пристрастности специалиста к какому-то одному инструменту только потому, что он с ним работал и хорошо его знает. В Profi.ru остановились на таких вариантах

Для визуализации данных Tableau, Metabase


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

Про Metabase знают немногие, между тем для прототипирования он очень хорош.

Из инструментов визуализации ещё можно сказать про Superset от Airbnb. Его особенная фича много коннектов к базам данных и возможностей для визуализации. Однако для рядового пользователя он менее удобен, чем Metabase, в нём нельзя соединять таблицы, для этого нужно делать отдельные представления.

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

Инструментов очень много просто найдите свой :-)

Для хранения данных ClickHouse, Vertica


ClickHouse бесплатный быстрый инструмент для хранения продуктовых событий. На нём аналитики сами делают обособленную аналитику (если им хватает данных) или дата-инженеры берут агрегаты и перезаливают их в Vertica для построения витрин.

Vertica крутой удобный продукт для отображения конечных витрин.

Для управления потоками данных и проведения вычислений Airflow


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

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

Основной язык программирования Python


У Python намного ниже порог вхождения + в компании есть компетенции по этому языку. Другая причина под Airflow DAGи пишутся на Python. Эти скрипты просто обёртка над загрузками, основная работа делается через консольные скрипты.

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

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


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

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


Карта технологий и компаний в сфере данных и ИИ видно, как много дублирующих решений может быть

Также многие для личных целей могут использовать разные ETL-инструменты. В Profi как раз такая ситуация. Основной ETL на Airflow, но кто-то для личных прогрузок использует Pentaho. Они тестируют гипотезы, и через инженеров эти данные им прогонять не нужно. В основном инструменты самообслуживания используют достаточно опытные специалисты, которые занимаются исследовательской деятельностью изучают новые пути развития продукта. Набор их данных для анализа интересен в основном им, к тому же, он постоянно меняется. Соответственно нет смысла заносить эти прогрузки в основную платформу.

Возвращаясь к зоопарку. Часто использование дублирующих технологий связано с человеческим фактором. Обособленные внутренние команды привыкли работать с тем или иным инструментом, который другая команда может и не использовать. И иногда автономия единственный путь для решения особых задач. Например, команде R&D нужно что-то протестировать при помощи определённого инструмента он просто удобен, кто-то из команды уже использовал его или есть другая причина. Ждать ресурса системных администраторов на установку и настройку этого инструмента долго. При этом вдумчивым и дотошным администраторам ещё нужно доказать, что это действительно нужно. Вот команда и ставит инструмент на своих виртуалках и решает свои специфические задачи.

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

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

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

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

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

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

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

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

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

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

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

  • Не принимать решение в одиночку. Когда человек что-то выбирает, он автоматически убеждён в своей правоте. Другое дело убедить других, когда нужно привести серьёзные доводы в защиту. Это помогает в том числе увидеть слабые стороны инструмента.
  • Советоваться с главным специалистом по данным (диалог по вертикали). Это может быть главный дата-инженер (Chief Data Engineer), руководитель BI-команды. Топы видят ситуацию более широко.
  • Общаться с другими командами (диалог по горизонтали). Какие инструменты они используют и насколько удачно. Возможно, инструмент коллег может решить и ваши задачи и не придётся устраивать зоопарк решений.


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


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

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

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

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

В Profi внедрение BI было in-house. Основная сложность была в том, что бизнес хотел запустить BI быстро. Но на построение такого проекта требовалось время: нарастить компетенции, залить данные, построить удобную схему хранилища, выбрать инструменты и освоить их.

Основная горячая фаза, когда всё строилось и кристаллизовывалось, длилась где-то год. А развивается проект до сих пор.

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

С большой болью мы переделывали большую часть проекта, которую пришлось тогда сделать по-быстрому.

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

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

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

Если кто хочет поделиться решением третьей задачки из описанных выше welcome :-)
Подробнее..

А вы все еще генерируете данные руками? Тогда GenRocket идет к вам

06.01.2021 20:05:30 | Автор: admin

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

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

Проблема генерации тестовых данных

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

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

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

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

Функциональность идеального сервиса генерации данных

Идеальный сервис по генерации тестовых данных должен иметь возможность:

  • генерировать данные в разных форматах (JSON, XML, CSV и т.д.)

  • генерировать данные с зависимостями (parent, child)

  • генерировать сложные зависиммые данные (if a then 1 or 2 else 3 or 5)

  • генерировать большие обьемы данный за небольшой промежуток времени

Хотелось бы иметь возможность:

  • загрузки данные в прямо в БД

  • интерграции в CI/CD

  • создавать модель данных автоматом из схем

GenRocket университет

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

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

GenRocket сервис

GenRocket - это сервис для генерации данных, созданный в 2011 году Hycel Taylor и Garth Rose для решения проблемы создания реалистичных тестовых данных для любой модели данных. Сервис обладает функциональностью генерировать данные для автоматического тестирования, для тестирования нагрузки, тестирования безопасности и др.

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

Что бы начать работу с GenRocket пользователь должен быть авторизирован, затем что бы начать работу с GenRocket необходимо скачать архив для Runtime* для cli части, распаковать его и прописать в системных переменных путь к папке с GenRocket в переменную GEN_ROCKET_HOME и в переменно PATH прописать %GEN_ROCKET_HOME%\bin значение.

Затем открываем командную строку, набираем genrocket и видим картинку ниже.

GenRocket cli часть работает в двух режимах on-line и off-line, но для работы с off-line надо скачать сертификат, который будет валиден только 24 часа.

GenRocket домен и его атрибуты

Первые два из основных компонентов - это Домен и Атрибуты домена. Домен - это существительное, например, пользователь: адрес, кредитная карта и т.д. Каждый домен описывается атрибутами, например: имя, фамилия, e-mail, пароль и день рождения. На картинке ниже вы видите домент User (1), описанный атрибутами (2) и пример сгенерированных данных (3).

Атрибуты к домену могут добавляться вручную по одному (2), с помощью блокнота или импортируя DDL, CSV, JSON или другие форматы. Если мы говорим о табличных данных, то можно сказать, что домен - это таблица, а атрибуты - это клонки этой таблицы.

GenRocket генераторы

Следующий компонент - генератор (generator) - это функциональность, которая непосредственно отвечает за генерацию данных в различных форматах. Генераторов в GenRocket 150+ для различных типов данных. Например:

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

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

GenRocket получатель (receiver)

Следующий компонент - получатель (receiver) - это функциональность, которая отвечает за выгрузку данных в необходимом формате: XML, JSON, SQL, CSV, JDBC, REST, SOAP. В GenRocket 35+ подобных получателелей.

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

GenRocket сценарий (scenarios)

Следующий компонент - сценарий (scenarios) - это набор инструкций, которые определяют сколько и в каком порядке будут созданы данные. Сценарии бывают одиночные (2) и сценарии-цепочки (1), которые позволяют генерировать данные для несколько связанных доменов одновременно. За количество данных отвечает переменная loopCount в настройках домена. Причем у каждого домена значение этой переменной устаналивается отдельно, что позволяет генерировать разное количество данных для каждого домена в сценариях-цепочках.

Сценарий выгружается в виде grs файла (3) и должен быть исполнен на машине, где был установлен GenRocket. Открываем командную строку и выполняем сценарий при помощи команды genrocket -r UserInsertScenario.grs.

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

Применение GenRocket на реальном проекте

Возьмем небольшую схему данных, в которой есть таблицы user, grantHistory и notificationSetting.

Используя импорт DDL создадим домен для user.

create table `user` ( id int(10) not null auto_increment,  external_id varchar(50) not null unique,  first_name varchar(25) not null,  last_name varchar(25) not null,  middle_initial char(1),  username varchar(100) not null,  ssn varchar(15) not null,  password varchar(255) not null,  activation_date date,  primary key (id));

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

Аналогичные действия проделываем для grant_history и notification_setting. Сгенерированные данные будут сохраняться в базу данных, для которой настроено JBDC соединение.

driver=org.h2.Driveruser=sapassword=saurl=jdbc:h2:file:~/lms_course/lms_alpha;AUTO_SERVER=TRUE;batchCount=1000

И так же для этой базы настраивается специфичные получатели H2InsertV2Receiver для вставки и SQLUpdateV2Receiver для модификации.

После всех манипуляций с настройками получаем файлы сценарии InsertScenarioChain.grs для вставки и UpdateScenarioChain.grs для модификации, после выполнения которых получаем картинку ниже.

И вуаля, данные в таблицах:

Заключение

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

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

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

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

Ниже расценка на доступ для GenRocket

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

https://www.datprof.com/solutions/test-data-generation/ - только 14 дней бесплатного использования, похоже что цена договорная

http://generatedata.com/ - бесплатно, но возможна генерация только 100 записей

https://www.mockaroo.com/ - бесплатна возможна генерация только 1000 записей, остальное платно - для самого дорогого доступа $5000/year

Подробнее..

Как сделать памятку по родословной греческих богов в SAP HANA Cloud

29.06.2020 14:12:48 | Автор: admin
В этом году у компании SAP появилось новое решение SAP HANA Cloud, которое предоставляет широкий спектр возможностей для работы с данными, позволяет создавать, запускать, развертывать новые и обновлять существующие приложения. Основу этого решения составляет SAP HANA, применяемая для работы с данными, требующими высокую скорость обработки. Мы называем такие данные горячими, поскольку они размещены в оперативной памяти. Это гарантирует быстрый доступ и высокую производительность. Кроме этого, в SAP HANA Cloud интегрировано озеро данных, и его развертывание происходит автоматически, а управление не вызывает затруднений. Оно реляционное и позволяет оптимизировать стоимость хранения структурированной информации. Там находятся холодные данные, то есть они будут обрабатываться несколько медленнее, чем горячие. SAP HANA Cloud предлагает и промежуточный уровень хранения данных SAP HANA Native Storage Extension, хранение данных на диске и загрузка через буферный кеш. Возможности многоуровневого хранения обеспечивают высокий показатель масштабирования и эластичности, оптимизации затрат без ущерба для производительности. Предлагаю разобраться как работает новинка на примере создания родословной греческих богов и героев.

image

За основу возьмем скрипты из приложения Appendix B Greek Mythology Graph Example документации SAP HANA Graph Reference для обычной платформы SAP HANA, которая развертывается локально в ЦОДе. Основное назначение этого примера показать аналитические возможности SAP HANA, показать, как можно анализировать взаимосвязь объектов и событий с помощью алгоритмов работы с графами. Мы не будем останавливаться подробно на этой технологии, основная идея будет понятна из дальнейшего изложения. Кому интересно могут разобраться самостоятельно, испытав возможности SAP HANA express edition или пройти бесплатный курс Analyzing Connected Data with SAP HANA Graph.
Давайте разместим данные в реляционном облаке SAP HANA Cloud и посмотрим возможности по анализу родственных связей греческих героев. Помните, в Мифах и легендах Древней Греции было очень много персонажей и к середине уже не помнишь кто сын и брат кого? Вот мы сделаем себе памятку и никогда уже не забудем.
Для начала создадим экземпляр SAP HANA Cloud. Это сделать достаточно просто, надо заполнить параметры будущей системы и подождать несколько минут, пока экземпляр будет для вас развернут (рис.1).

image
Рисунок 1

Итак, нажимаем кнопку Create Instance и перед нами открывается первая страница мастера создания, на которой надо указать краткое название экземпляра, задать пароль и привести описание (рис.2)
image
Рисунок 2

Нажимаем кнопку Step 2, теперь наша задача указать параметры будущего экземпляра SAP HANA. Здесь можно задать только размер оперативной памяти будущей системы, все остальные параметры будут определены автоматически (рис.3).

image
Рисунок 3

Мы видим, что сейчас у нас есть возможность выбрать минимальное значение 30Гб и максимальное 900Гб. Выбираем 30Гб и автоматически определяется, что при таком объеме памяти необходимо два виртуальных процессора для поддержки расчетов и 120Гб для хранения данных на диске. Здесь места выделяется больше, поскольку мы можем применять технологию SAP HANA Native Storage Extension (NSE). Если выбрать размер памяти больше, например, 255Гб, то потребуется уже 17 виртуальных процессоров и 720ГБ дисковой памяти (рис. 4).

image
Рисунок 4

Но нам столько памяти для примера не требуется. Возвращаем параметры в исходное значение и нажимаем Step 3. Теперь мы должны выбрать, будем ли использовать озеро данных. Для нас ответ очевиден. Конечно, будем. Именно такой эксперимент мы и хотим провести (рис.5).

image
Рисунок 5

На этом шаге у нас значительно больше возможностей и свободы по созданию экземпляра озера данных. Вы можете выбирать размеры необходимых вычислительных ресурсов и дискового хранилища. Параметры используемых компонент/узлов выберутся автоматически. Система сама определит необходимые вычислительные ресурсы для координатора и рабочих узлов. Если вы хотите побольше узнать об этих компонентах, то лучше обратится к ресурсам SAP IQ и озеру данных SAP HANA Cloud. А мы двигаемся дальше, нажимаем Step 4 (рис.6).

image
Рисунок 6

На этом шаге мы определим или ограничим IP адреса, которые могут получить доступ к будущему экземпляру SAP HANA. Как видим, это последний шаг нашего мастера (рис.7), осталось нажать Create Instance и пойти налить себе кофе.

image
Рисунок 7

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

image
Рисунок 8

У нас есть два варианта: открыть SAP HANA Cockpit или SAP HANA Database Explorer. Мы знаем, что запустить второй продукт можно будет из Cockpit. Поэтому открываем SAP HANA Cockpit, заодно и посмотрим, что там есть. Но сначала необходимо будет указать пользователя и его пароль. Обратите внимание, что пользователь SYSTEM вам недоступен, вы должны применять DBADMIN. При этом указать пароль, который вы задали при создании экземпляра, как на рис.9.

image
Рисунок 9

Мы зашли в Cockpit и видим традиционный интерфейс SAP в виде плиточек, когда каждая из них отвечает за свою задачу. Справа в верхнем углу видим ссылку на SQL Console (рис.10).

image
Рисунок 10

Именно она нам позволяет перейти к SAP HANA Database Explorer.
image
Интерфейс этого инструмента похож на SAP Web IDE, но предназначен только для работы с объектами базы данных. В первую очередь, конечно, нас интересует как попасть в озеро данных. Ведь сейчас мы открыли инструмент для работы с HANA. Перейдем в навигаторе на пункт Remote Source и увидим ссылку на озеро (SYSRDL, RDL Relation Data Lake). Вот он желанный доступ (рис.11).

image
Рисунок 11

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

image
Рисунок 12
СКРИПТ:
CREATE USER tstuser PASSWORD Password1 NO FORCE_FIRST_PASSWORD_CHANGE SET USERGROUP DEFAULT;

Мы планируем работать с озером данных, поэтому надо обязательно дать права, например, HANA_SYSRDL#CG_ADMIN_ROLE, чтобы можно свободно создавать объекты, делать все, что нам вздумается.
image
СКРИПТ:
GRANT HANA_SYSRDL#CG_ADMIN_ROLE TO tstuser;
Теперь работа под администратором SAP HANA завершена, SAP HANA Database Explorer можно закрыть и нам надо войти в него под новым созданным пользователем: tstuser. Для простоты, вернемся в SAP HANA Cockpit и завершим сессию администратора. Для этого в левом верхнем углу есть такая ссылка Clear Credentials (рис.12).

image
Рисунок 12

После нажатия на нее нам снова надо войти в систему, но теперь под пользователем tstuser (рис.13)
image
Рисунок 13

И мы снова можем открыть SQL Console, чтобы вернуться в SAP HANA Database Explorer, но под новым пользователем (рис.14).

image
image
Рисунок 14

СКРИПТ:
SELECT SESSION_USER, CURRENT_SCHEMA FROM DUMMY;
Все, теперь мы уверены, что работаем с HANA под нужным пользователем. Пора создавать таблицы в озере данных. Для этого есть специальная процедура SYSRDL#CG.REMOTE_EXECUTE, в которую надо передать один параметр строку = команду. Используя, эту функцию создаем в озере данных таблицу (рис. 15), в которой будут хранится все наши персонажи: герои, греческие Боги и титаны.

image
Рисунок 15
СКРИПТ:
CALL SYSRDL#CG.REMOTE_EXECUTE ('
BEGIN

CREATE TABLE MEMBERS (
NAME VARCHAR(100) PRIMARY KEY,
TYPE VARCHAR(100),
RESIDENCE VARCHAR(100)
);

END');
И затем создаем таблицу в которой будем хранить родственные связи этих персонажей (рис. 16).

image
Рисунок 16

СКРИПТ:
CALL SYSRDL#CG.REMOTE_EXECUTE ('
BEGIN
CREATE TABLE RELATIONSHIPS (
KEY INTEGER UNIQUE NOT NULL,
SOURCE VARCHAR(100) NOT NULL,
TARGET VARCHAR(100) NOT NULL,
TYPE VARCHAR(100),
FOREIGN KEY RELATION_SOURCE (SOURCE) references MEMBERS(NAME) ON UPDATE RESTRICT ON DELETE RESTRICT,
FOREIGN KEY RELATION_TARGET (TARGET) references MEMBERS(NAME) ON UPDATE RESTRICT ON DELETE RESTRICT
);
END');
Мы не будем сейчас заниматься вопросами интеграции, это отдельная история. В исходном примере есть команды INSERT для создания греческих Богов и их родственных отношений. Используем эти команды. Надо только помнить, что команду мы передаем через процедуру в озеро данных, поэтому надо кавычки удвоить, как показано на рис.17.

image
Рисунок 17

СКРИПТ: CALL SYSRDL#CG.REMOTE_EXECUTE ('
BEGIN
INSERT INTO MEMBERS(NAME, TYPE)
VALUES (''Chaos'', ''primordial deity'');
INSERT INTO MEMBERS(NAME, TYPE)
VALUES (''Gaia'', ''primordial deity'');
INSERT INTO MEMBERS(NAME, TYPE)
VALUES (''Uranus'', ''primordial deity'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Rhea'', ''titan'', ''Tartarus'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Cronus'', ''titan'', ''Tartarus'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Zeus'', ''god'', ''Olympus'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Poseidon'', ''god'', ''Olympus'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Hades'', ''god'', ''Underworld'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Hera'', ''god'', ''Olympus'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Demeter'', ''god'', ''Olympus'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Athena'', ''god'', ''Olympus'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Ares'', ''god'', ''Olympus'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Aphrodite'', ''god'', ''Olympus'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Hephaestus'', ''god'', ''Olympus'');
INSERT INTO MEMBERS(NAME, TYPE, RESIDENCE)
VALUES (''Persephone'', ''god'', ''Underworld'');
END');
И вторая таблица (рис. 18)

image
Рисунок 18

СКРИПТ:
CALL SYSRDL#CG.REMOTE_EXECUTE ('
BEGIN
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (1, ''Chaos'', ''Gaia'', ''hasDaughter'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (2, ''Gaia'', ''Uranus'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (3, ''Gaia'', ''Cronus'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (4, ''Uranus'', ''Cronus'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (5, ''Gaia'', ''Rhea'', ''hasDaughter'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (6, ''Uranus'', ''Rhea'', ''hasDaughter'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (7, ''Cronus'', ''Zeus'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (8, ''Rhea'', ''Zeus'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (9, ''Cronus'', ''Hera'', ''hasDaughter'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (10, ''Rhea'', ''Hera'', ''hasDaughter'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (11, ''Cronus'', ''Demeter'', ''hasDaughter'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (12, ''Rhea'', ''Demeter'', ''hasDaughter'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (13, ''Cronus'', ''Poseidon'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (14, ''Rhea'', ''Poseidon'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (15, ''Cronus'', ''Hades'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (16, ''Rhea'', ''Hades'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (17, ''Zeus'', ''Athena'', ''hasDaughter'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (18, ''Zeus'', ''Ares'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (19, ''Hera'', ''Ares'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (20, ''Uranus'', ''Aphrodite'', ''hasDaughter'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (21, ''Zeus'', ''Hephaestus'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (22, ''Hera'', ''Hephaestus'', ''hasSon'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (23, ''Zeus'', ''Persephone'', ''hasDaughter'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (24, ''Demeter'', ''Persephone'', ''hasDaughter'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (25, ''Zeus'', ''Hera'', ''marriedTo'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (26, ''Hera'', ''Zeus'', ''marriedTo'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (27, ''Hades'', ''Persephone'', ''marriedTo'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (28, ''Persephone'', ''Hades'', ''marriedTo'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (29, ''Aphrodite'', ''Hephaestus'', ''marriedTo'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (30, ''Hephaestus'', ''Aphrodite'', ''marriedTo'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (31, ''Cronus'', ''Rhea'', ''marriedTo'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (32, ''Rhea'', ''Cronus'', ''marriedTo'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (33, ''Uranus'', ''Gaia'', ''marriedTo'');
INSERT INTO RELATIONSHIPS(KEY, SOURCE, TARGET, TYPE)
VALUES (34, ''Gaia'', ''Uranus'', ''marriedTo'');
END');
Откроем теперь снова Remote Source. Нам надо на основании описания таблиц в озере данных создать виртуальные таблицы в HANA (рис. 19).

image
Рисунок 19

Находим обе таблицы, устанавливаем галочки напротив таблиц и нажимаем кнопку Create Virtual Object(s), как показано на рис.20.

image
Рисунок 20
У нас есть возможность указать схему, в которой виртуальные таблицы будут созданы. И там надо указать префикс, чтобы эти таблицы легче было найти. После этого мы можем в навигаторе выбрать Table, увидеть наши таблицы и посмотреть данные (рис. 21).

image
Рисунок 21

На этом шаге важно обратить внимание на фильтр внизу слева. Там должно быть указано имя нашего пользователя или наша схема TSTUSER.
Вот почти все готово. Мы создали в озере таблицы и загрузили в них данные, а для доступа к ним с уровня HANA у нас есть виртуальные таблицы. Мы готовы создать новый объект граф (рис. 22).

image
Рисунок 22

СКРИПТ:
CREATE GRAPH WORKSPACE GREEK_MYTHOLOGY
EDGE TABLE TSTUSER.RDL_RELATIONSHIPS
SOURCE COLUMN SOURCE
TARGET COLUMN TARGET
KEY COLUMN KEY
VERTEX TABLE TSTUSER.RDL_MEMBERS
KEY COLUMN NAME;
Все сработало, граф готов. И сразу можно попробовать сделать какой-нибудь простой запрос к данным графа, например, найти всех дочерей Хаоса и всех дочерей этих дочерей. Для этого нам поможет Cypher язык для анализа графов. Он был специально создан для работы с графами, удобный, простой и помогает решать сложные задачи. Нам только надо помнить, что скрипт Cypher надо обернуть в SQL запрос с помощью табличной функции. Посмотрите, как на этом языке решается наша задача (рис.23).

image
Рисунок 23

СКРИПТ:
SELECT * FROM OPENCYPHER_TABLE( GRAPH WORKSPACE GREEK_MYTHOLOGY QUERY
'
MATCH p = (a)-[*1..2]->(b)
WHERE a.NAME = ''Chaos'' AND ALL(e IN RELATIONSHIPS(p) WHERE e.TYPE=''hasDaughter'')
RETURN b.NAME AS Name
ORDER BY b.NAME
'
)
Проверим, как работает визуальный инструмент SAP HANA для анализа графов. Для этого в навигаторе выберем Graph Workspace (рис. 24).

image
Рисунок 24

И теперь можно увидеть наш граф (рис. 25).

image
Рисунок 25

Вы видите уже раскрашенный граф. Это мы сделали с помощью настроек в правой стороне экрана. Слева в верхнем углу показывается детальная информация по узлу, который в данный момент выделен.
Что ж Мы сделали это. Данные находятся в озере данных, а их анализ мы проводим инструментами в SAP HANA. Одна технология вычисляет данные, а другая отвечает за их хранение. Когда происходит обработка данных графа, они запрашиваются из озера данных и передаются в SAP HANA. Можем ли мы ускорить наши запросы? Как сделать так, чтобы данные хранились в оперативной памяти и не подгружались из озера данных? Есть простой, но не очень красивый способ создать таблицу, в которую загрузить содержимое таблицы озера данных (рис. 26).

image
Рисунок 26

СКРИПТ:
CREATE COLUMN TABLE MEMBERS AS (SELECT * FROM TSTUSER.RDL_MEMBERS)
Но есть еще один способ это применение репликации данных в оперативную память SAP HANA. Это может обеспечить лучшую производительность запросов SQL, чем доступ к данным, размещенным в озере данных с помощью виртуальной таблицы. Вы можете переключаться между виртуальными и таблицами репликации. Для этого необходимо к виртуальной таблице добавить таблицу реплики. Это можно сделать с помощью инструкции ALTER VIRTUAL TABLE. После чего, запрос, использующий виртуальную таблицу, автоматически обращается к таблице реплик, которая размещается в оперативной памяти SAP HANA. Давайте посмотрим, как это сделать, проведем эксперимент. Выполним такой запрос (рис. 27).

image
Рисунок 27

СКРИПТ:
SELECT R.KEY, R.SOURCE, R.TYPE
FROM TSTUSER.RDL_RELATIONSHIPS R inner join TSTUSER.MEMBERS M on R.SOURCE=M.NAME

И посмотрим, сколько надо было времени, чтобы выполнить этот запрос (рис. 28).

image
Мы видим, потребовалось 92 миллисекунды. Давайте включим механизм репликации. Для этого надо сделать ALTER VIRTUAL TABLE виртуальной таблицы, после чего данные Озера будут реплицированы в оперативную память SAP HANA.

image
Рисунок 28

СКРИПТ:
ALTER VIRTUAL TABLE RDL_RELATIONSHIPS ADD SHARED SNAPSHOT REPLICA COLUMN LOADABLE;
Проверим время выполнения, как на рисунке 29.

image
Рисунок 29

Получили 7 миллисекунд. Это отличный результат! Минимальными усилиями мы переместили данные в оперативную память. Причем, если вы закончили анализ и вас устроит производительность, можно снова отключить репликацию (рис. 30).

image
Рисунок 30

СКРИПТ:
ALTER VIRTUAL TABLE RDL_RELATIONSHIPS DROP REPLICA;
Теперь данные опять подгружаются из Озера только по запросу, а оперативная память SAP HANA свободна для новых задач. Сегодня мы выполнили, на мой взгляд, интересную работу и протестировали SAP HANA Cloud на быстроту, легкую организацию единой точки доступа к данным. Продукт будет развиваться, и мы ожидаем, что в ближайшее время может появится прямое соединение с озером данных. Новая возможность обеспечит более высокую скорость загрузки больших объемов информации, отказ от ненужных служебных данных и повышение производительности операций, специфичных для озера данных. Мы будем создавать и выполнять хранимые процедуры непосредственно в облаке данных на технологии SAP IQ, то есть сможем применять обработку и бизнес-логику там, где находятся сами данные.
Александр Тарасов, старший бизнес-архитектор SAP CIS
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    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