Disclimer: все написанное относится исключительно к связанным с Data Engineering'ом вопросам.
Сейчас развертывание дата платформ и решений для аналитки в облаке - явление повсеместное. Кажется, что так было (и будет?) всегда. При этом существует постоянное (но не всегда очевидное) противостояние между подходами Cloud Native и Cloud Agnostic. Cloud Native поддерживает использование специфических сервисов конкретного облачного провайдера, в то время как Cloud Agnostic нацелен на создание приложений, которые могут работать на различных облачных платформах без изменений.
Важно осознавать различия между этими подходами и принимать обоснованные решения при выборе одного из них для своего проекта. Этот выбор может существенно повлиять на архитектуру, масштабируемость вашей дата платформы и стоимость разработки и поддержки.
Однако важно помнить, что не следует впадать в крайности и быть абсолютно приверженным только одному из подходов (вспомним акисому Эскобара). Иногда оптимальным решением может быть комбинация обоих подходов, чтобы достичь оптимального баланса между гибкостью и эффективностью.
В данной статье я попытаюсь описать подход, который в итоге я внедрил в своей команде для построения дата-инфраструктуры.
Немного истории и контекста
Когда я присоединился к компании, прошел все онбординг-мероприятия и погрузился в тогдашнюю дата-инфраструктуру и процессы, то понял, что работы предстоит непочатый край. Объединив свои наблюдения и сопоставив их с отрывочными рассказами коллег, я быстро восстановил "историю" (которая, думаю, характерна для многих быстрорастущих компаний):
Текущая дата-платформа начала создаваться примерно лет 5-6 назад вместе с выделением компании в отдельное ЮЛ и решением "идти в облака". Занимались этим люди, которые были далеки от "даты".
Как это часто бывает, сначала сделали PoC, "чтоб быстро и работало". Когда "оно" заработало, бизнес сказал "круто!" и тут же начал "это" использовать в своих процессах, начав запрашивать все новые и новые фичи.
В итоге PoC по сути начал использоваться в продакшене, обрастая попутно все новым и новым функционалом.
Люди, занимающиеся всем этим, менялись, ни у кого не было ни времени, ни инициативы, ни экспертизы "наводить" порядок и, боже упаси, создавать хоть какую-то документацию.
-
В итоге на момент моего прихода, после очередной череды смен кадров и недавних уходов сотрудников компания встретила меня двумя Дата Инженерами:
О. - бывший еще 2 года назад просто джавистом
А. - молодой и зеленый (но при этом толковый), нанятый на месяц раньше меня
плюс еще троих (помимо меня) предстояло нанять :-)
Бонусом прилагался И. (отличный чел кстати, и как специалист, и как человек), который работал в компании уже 7 лет и периодически "касался" всего этого (а где-то даже и "хорошенько наследил"), но сейчас занимал мелко-руководящую должность.
Думаю, при таком раскладе очевидно, что с архитектурной точки зрения "все было плохо". Возвращаясь плавно к теме статьи, одной из проблем, которую сразу увидел я, и которую не замечали - отсутствие какой-то последовательности и подхода в использовании облачных сервисов, полная мешанина всего и вся, что было невозможно определить "политику" касательно использования Cloud Native- vs Cloud Agnostic-подходов (что, впрочем, неудивительно, учитывая "историю становления" текущего решения). Так как мне была предоставлена большАя (я бы даже сказал очень большая) свобода действий, то первым шагом к "наведению" порядка и стало обдумывание целевого подхода к использованию cloud-сервисов и внедрение стандартов разработки с учетом этого подхода.
Размышления
Любой облачный провайдер пытается “подсадить” своих клиентов на решения, специфичные только для его облака. За примерами далеко ходить на надо: AWS Glue, AWS Kinesis, Azure Data Factory, Google Data Fusion, Databricks Unity Catalog (условно вычеркиваем, так как пока статья болталась в черновиках, Databricks выложила его в open source) etc. У таких cloud-native решений есть бесспорно свои плюсы (самый главный - ускорение получения работающего решения "в точке", а остальные я не буду перечислять - уважаемый читатель без труда найдет их в соответствующих маркетинговых материалах), но и значительные минусы, о которых вам не напишут в рекламном буклете. Выделю только самые, на мой взгляд главные (и очевидные):
Отсутствие гибкости и ограниченный функционал. На красивых демо-примерах на конференциях и обучающих семинарах от вендоров и YouTube'еров все "красиво". Также на начальном этапе использования этого очень часто не замечают, но по мере развития проекта эти ограничения становятся все более ощутимыми. Попробуйте, например, стандартными средствами Azure Data Factory внутри пайплайна сгенерировать на основании
dateFrom
иdateTo
массив дат, входящих в этот интервал (сложность - easy), а еще лучше, массив календарных номеров недель сложность - hard), в которые эти даты попадают - "удовольствие" гарантировано :-)Привязка к вендору и его решению. Если вы используется очень активно и много фич данного конкретного облачного сервиса, то становится очень трудоемко мигрировать не только от одного провайдера к другому, но и просто с одного сервиса на другой внутри одного облака. (А может у вас вообще кейс, как у клиента, на проекте которого я когда-то трудился: очень большая компания решила мигрировать из on-premise в облако, и оказалось, что ни один облачный провайдер из доступных ему не готов предоставить требуемые мощности с требуемым SLA. Компания была вынуждена попутно строить muli-cloud решение)
Очень часто может возникать проблема с организацией нужного вам релиз-менеджмента. Разные инструменты имеют разные, и порою совсем не очевидные ограничения, но особенно это актуально для инструментов, которые предполагают активное “программирование мышкой через UI” (привет, Azure Data Factory! которую без танцев с бубнами можно деплоить только целиком, а не отдельными data pipeline'ами, да и вообще настройка CI/CD для нее, если у вас "хотелки" чуть выше, чем уровень примеров из документации, можно сразу вешаться)
ИМХО, хороший Дата Архитектор должен все это понимать и поэтому стремиться найти разумный компромисс, руководствуясь в первую очередь здравым смыслом, зрелостью технологий, их возможностями и ограничениями, и конечно же целями и стратегией компании (как с точки зрения развития бизнеса, так и с точки зрения технологического развития). "Политические" в широком смысле этого слова аспекты, начиная от коррупционной составляющей и стремлением "помочь друзьям" до "геополитических" и импортозамещения, мы рассматривать не будем. Здесь важно не преуменьшать значение целей и стратегии компании, чем часто "грешат" "технические" люди. Как всегда это бывает, финальный выбор будет обусловлен компромисом между выигрышем в краткосрочной и долгосрочных перспективах - по большому счету частный случай известной в психологии дилеммы между мгновенным удовлетворением (instant gratification) и отсроченной наградой (delayed gratification). Мы, технари, по своей природе всегда будем тяготеть скорее к "долго, дорго, ох..енно"(с) Артемий Лебедев. Однако бизнес, чьи задачи мы в первую очередь обслуживаем, будет всегда ожидать от нас "быстро, дешево и чтоб работало". И здесь опыт и квалификация архитектора, принимающего решение, играет критическую роль. Если, конечно, такая роль вообще выделена :-)
Основываясь на своем многолетнем опыте и десятках проектов за плечами, для себя я вывел следующие ключевые моменты, на которых, как мне кажется, может базироваться разумный компромисс:
отказ от вендор/cloud-лок решений там, где этого можно избежать незначительными трудозатратами или незначительным изменением процессов.
избегание по максимуму реализации какой-либо логики сложности выше "easy" в low-code/no-code инструментах (да простят меня их поклонники и вендоры :-) ), а также в сервисах вида “программирование мышкой через UI”. В первую очередь из-за сложности с переносимостью реализованной таким образом логики куда-либо, более сложного релиз-менеджмента, дебагинга и трекинга изменений.
реализации бизнес-логики с помощью инструментов/технологий/фреймворков, которые не зависят от вендора и/или облачного провайдера. Ну или по крайней мере данный инструмент должен быть доступен у многих провайдеров.
легкость поддержки решения (как всего решения в целом, так и конкретного программного кода в частности) приоритетнее некритичного выигрыша в производительности.
* здесь приведен взгляд Дата Архитектора компании, а не внешнего вендора/консультанта. Бизнес-цели внешнего подрядчика часто могут отличаться от целей клиента и поэтому Дата Архитектор подрядчика, в соответствии с замечанием выше, будет руководствоваться иными соображениями, например, "подсадить" клиента на какой-то свой продукт - как раз пример учета целей и стратегии компании.
Реальность
Все приведенное выше - "сферические размышления в вакууме" вида "за все хорошее против всего плохого". А что же на практике? На практике же необходимо было определить комбинацию различных инструментов и архитектурные паттерны, которые планировалось использовать для постепенного "наведения порядка". При этом уже было известно, что материнской компанией планируется создание общей дата-платформы всей группы на горизонте 1,5-2 лет по средне-оптимистичным оценкам, и неизвестно, что еще "они там придумают". Поэтому вопрос будущей миграции был вовсе не гипотетическим, и его нельзя было игнорировать.
Чтобы есть слона по частям, давайте выделим сперва главные блоки любой дата-платформы:
Хранение данных
Обработка данных
Оркестрация
* Здесь по-хорошему надо упомянуть еще и инструменты бизнес-анализа и визуализации, а также вопросы DevOps. Но так как в нашей компании это не зона моего непосредственного влияния, то я решил не писать про это. К тому же статья и так получилась достаточно объемной. Возможно, в будущем посвящу этим блокам отдельную статью.
Хранение данных
Этот вопрос является, вероятно, самым простым. Уже много лет стандартным подходом является хранение данных в таких универсальных форматах, как parquet / delta / iceberg / json / csv в объектных хранилищах типа S3, Azure Data Lake Storage и т.д. Уже сам этот подход является почти cloud agnostic.
В нашем случае мы храним данные в Azure Data Lake Storage, которое сынтегрировано "по умолчанию" со многим сервисам в Azure и которое уже ранее было примонтировано к файловой системе Databricks-кластера. Это очень удобно - не надо каждый раз писать лишние строки когда и возиться с ключами. Почему-то данный подход, кстати, редко встречается в туториалах и видео от Youtube'ров. Сейчас, с прицелом использования в новой групповой дата платформе, рассматривается возможность использования Unity Catalog. В случае, если будет принято такое решение, можно будет достаточно легко переключится на Unity Catalog Volumes.
Таким образом мы можем по минимум привязываться в Databricks-specific функциональности, и в случае, если кто-либо когда-либо по разным причинам захочет использовать вместо Databricks, например, HDInsight или Synapse, не возникнет никаких проблем с доступом к данным. И даже в случае маловероятной миграции в другое облако мы тоже очень просто сможем перенести наши данные.
Обработка данных
Этот вопрос разбивается на несколько аспектов.
Фреймворк для обработки данных. Тут все очевидно - Spark. Без комментариев.
Compute. В нашем случае это Databricks-кластер. Может и не самое cost-effective решение, но зато очень стабильное и избасляющее от головной боли касательно конфигурирования и оптимизации настроек кластера. Думаю, отчасти именно поэтому он был выбран еще 5-6 лет назад теми, кто был далек от "даты". Еще плюс Databricks - его доступность на всех трех самых популярных облачных платформах, где его можно использовать с небольшими различиями в стоимости, функциональности и расположении дата-центров.
Язык программирования. Существует три основных универсальных языка для работы со Spark: Python, Scala и Java. Сюда можно еще "со звездочкой" добавить подход, когда эти языки являются лишь "оберткой" для запуска обычного SQL, но мы такой подход для разработки продуктовых data pipeline даже не рассматриваем по очевидным причинам. Я очень рад, что мы в нашей компании уже использовали Scala. Хотя PySpark и развивается сильно, он все же еще уступает и в возможностях, и в производительности коду на Scala. Но, вынужден признать, что грани все больше и больше стираются, особенно учитывая выход Spark Connect (мы пока не смотрим в его сторону, но в будущем ситуация может измениться), да и Databricks стремиться сделать Python основным языком для работы со Spark.
К плюсам использования Scala (как и Java) я бы отнес то, что это - компилируемый язык, что упрощает разработку environment-agnostic Spark-приложений. Последнее для меня означает, что один и тот же код должен как минимум:
запускаться как на локальной машине, так и на кластере / в любом облаке (как тут не вспомнить недавний "worldwide IT-destroy" by Microsoft & CrowdStrike - с точки зрения процесса разработки я его даже не заметил, так как хотя у нас и легли многие облачные сервисы, но текущей разработке и отладке приложения на локальной машине это никак не мешало; так же я могу продолжать работать и там, где нет интернета - в поезде, в самолете и т.п., что важно в условиях "удаленки")
запускаться через любой шедулер (Oozie, Airflow, AWS Glue, Azure Data Factory и т.д.)
работать (как читать, так и записывать) с данными, хранящимися как любом cloud storage, HDFS или на локальной машине
и все это желательно без использования Docker.
Небольшое отступление про ноутбуки в контексте этих требований
* Из этого кстати ковенно следует, что необходимо избегать использования ноутбуков в продакшене в том числе и вследствии сложности удовлетворения всем этим требованиям.
Про ноутбуки хочется заметить дополнительно, что этот простой тезис еще 5-10 лет назад был всем очевиден, но сейчас вендоры, в попытке еще больше "подсадить" клиентов на свои решения, продвигают ноутбуки чуть ли не как best practice разработки data pipeline'ом. Результат не заставляет себя долго ждать - часть молодого поколения дата инженеров (видать после курсов "войти в ИТ" и просмотра роликов на Youtube), к сожалению, не всегда понимает это (не говоря уже о руководящем звене). Для меня же удивительно, почему люди ведутся на маркетинговые приемы и готовы игнорировать человеко-века, инвестированные в IDE, и выстроенные (и выстраданные) за десятилетия подходы к процессами разработки, совершая даунгрейд в ноутбуки и создавая себе потенциальные проблемы на ровном месте?
Все это мы достаточно просто обеспечили следующими, в принципе несложными, подходами:
Грамотная параметризация и продуманная возможностью конфигурирования Spark-приложений. В контексте темы статьи - в первую очередь при работе с "секретами", путями к данным, передачей аргументов командной строки. Например, все наши Spark-приложения принимают на вход как аргументы командной строки минимум интервал дат и путь к "корню" Data Lake, который может быть как путем к точке монтирования Azure Data Lake Storage к Databricks File System или к "песочнице" для дебагинга, так и локальный путь до папки с тестовыми данными.
-
"Внутри" мы пишем код так, чтобы он мог изначально работать как на локальной машине, так и на кластере. Для этого стараемся не использовать каких-либо специфичных для вендора/cloud-провайдера библиотек. Некоторые примеры:
для работы с файловой системой используем
org.apache.hadoop.fs.FileSystem
, а неjava.io
илиdbutils.fs
для работы с секретами используем специальные функции-Wrapper'ы, которые только в случае запуска на кластере, берут секреты из Key Vault, а при запуске на локальной машине - из локального файла с "дефолтными" значениями для дебагинга
еще не совсем приятный кейс: когда какой-то сервис существует только в единственном экземпляре в облаке и только на ПРОДе, вынужденно делаем "заглушки" во Wrapper'ах.
Для деплоя используем только fat jar. При этом версии всех базовых библиотек, что по умолчанию установлены на кластере, фиксированы и являются
provided
вbuild.sbt
(как правило, это версия Scala, Spark, Delta Lake и т.д. - см. документацию, в нашем случае к Databricks Runtime), а остальные включаются в fat jar. Это дает возможность без усложнения с использованием Docker'а быть уверенным, что конфликтов из-за версий библиотек 99% не возникнет (практика пока это поддтверждает). Также благодаря этому разные приложения, запускаемые на одном и том же кластере, могут использовать разные версии одной и той же библиотеки. Например, сейчас это нам упрощает миграцию на новую версию Databricks Runtime.
Оркестрация
Тут "по умолчанию" предпочтение следует отдавать open source решениям, желательно тем, которые можно развернуть в облаке как сервис. Бесспортный лидер тут - Apache Airflow. Но оркестрация - это как раз тот случай, когда использование cloud native решений может быть оправдано. В первую очередь потому, что оркестрация подразумевает взаимодействие с большим количеством других сервисов, а значит большой объем задач по их интеграции и решения вопросов прав доступа. Что не говори, а провайдеры, как правило, уже решили все эти вопросы и имеет смысл не изобретать велосипед.
В нашем конкретном случае мы используем Azure Data Factory(ADF), который сочетает в себе как функцию оркестратора, так и ETL/ELT-инструмента и, к несчастью, "программируется мышкой". Наш подход заключается в том, что:
вся бизнес-логика инкапсулирована в Spark-приложениях, ADF только запускает их на Databricks-кластере. Таким образом самая интеллектуально емкая и трудозатратная часть всех наших data pipline'ов остается cloud agnostic.
используем ADF-activity в наших пайпалйнах только для самых простых действий, не связанных напрямую с преобразованием данных, например "переложить файлы с sFTP в Cloud Storage", "послать сообщение в Слак о падении/завершении процесса", "проверить наличие файла на FTP", "выбрать в зависимости от параметра запускать JAR-1 или JAR-2" и т.п.
используем функционал оркестирования без каких-либо ограничений (у нас в основном все pipeline'ы запускаются по расписанию, запуска по событию пока нет)
Такой подход позволяет с одной стороны не "изобретать велосипед" и быстро настраивать рутинные процессы транспортировки данных (без преобразований) между FTP - Cloud Storage - SQL DB - etc., устанавливать расписание запуска, настраивать уведомления об ошибках и т.п., минимизируя затраты инженера на рутинные и неинтересные части data pipeline'ов при этом высвобождая время на разработку более "интеллектуальноемких" преобразований, которые реализованы в Spark-приложения
Заключение
В итоге спустя менее чем год, мне удалось внедрить все, описанное в данной статье. И не только внедрить для нового функционала, но и постепенно перевести всё существующие под эти стандарты. Как результат, я теперь спокоен, что миграция в будущем на маячащие на горизонте групповую дата платформу (чтобы там не придумала головная компания) в нашей команде потребует явно меньше усилий и пройдет куда спокойнее и быстрее. Приятным бонусом стало так же повышение стабильности наших data pipeline'ов: вместо 5-6 инцидентов в месяц (как падений, так и просто необходимости ручного перезапуска), мы теперь имеем всего 1-2 в квартал.
Как краткое summary вышенаписанного, можно резюмировать, что при выборе между Cloud Native и Cloud Agnostic в Data Engineering'е важно учитывать специфику проекта и потребности бизнеса. Рекомендуется стремиться к гибридному подходу, который сочетает в себе преимущества обеих стратегий, обеспечивая гибкость и масштабируемость инфраструктуры. На мой взгляд здоровым компромиссом является реализация бизнес-логики в environment-agnostic стиле, а для всего "вспомогательного" и не-интеллектуально емкого можно использовать cloud native инструменты.