Data Engineering — это не Software Engineering
Это мой вольный перевод статьи «Data Engineering is Not Software Engineering», с рядом моих правок, дополнений, а так же сокращений (так как автор склонен повторять одно и то же, но иными словами или излишне «разжевывать» очевидные вещи). Мне кажется, автор действительно поднял очень важную тему, которую я «чувствовал» по своей практике, но не мог сформулировать так точно, как это сделал он.

Мало кто задумывается, что дата-инженерия и разработка ПО имеют значительные различия. Поэтому распространено мнение, что некое отставание дата-инженерии в части внедрения современных методов разработки, таких как Agile, Test Driving Development и т.д. обусловлено лишь отставанием в освоении этих передовых практик.
На самом деле этот взгляд ошибочен. Хотя дата-инженерия и разработка ПО действительно имеют много общего, между ними существуют значительные различия. Игнорирование этих различий и управление командой дата-инженеров по тем же принципам, что и командой разработчиков ПО, является ошибкой. Особенно этим грешат относительно молодые менеджеры, или те, кто никогда не работал с «датой». Собственно, этим зачастую и вызваны ошибки в пименении «в лоб» соврмененых методой разработки. Дата-инженерия — как томат: технически это фрукт, но это не значит, что его стоит добавлять в фруктовый салат.
Data Pipelines — это не приложения
Разработка ПО, как правило, сосредоточена обычно на создании приложений. В контексте этой статьи под приложением понимается широкий спектр решений: веб-сайт, десктопное/мобильное приложение, API, монолитная мэйнфрейм-система, игра, микросервис, библиотека и многое другое. Общие характеристики этих решений заключаются в следующем:
-
Они предоставляют пользователям ценность, создавая новый способ взаимодействия. Игру можно играть, веб-сайт можно просматривать, API можно использовать в других программах.
-
Содержат множество в основном независимых функций. Веб-сайт может расширяться за счет новых страниц, игра — за счет новых уровней и персонажей, API — за счет дополнительных эндпоинтов.
-
Работают с относительно небольшим объемом данных, которое они же и создают. Данные предназначены для поддержки работы приложения, и их хранение часто передается внешним системам. Цель заключается в том, чтобы значительная часть программного обеспечения оставалась статичной. Например, веб-приложение можно в любой момент завершить и перезапустить, поскольку его данные храняться в базе данных, работающей в отдельном процессе.
-
Слабо связаны с другими программами и сервисами. Хорошее программное обеспечение должно работать независимо в любой среде, что объясняет популярность микросервисов и контейнеров.
Дата-инженеры же занимаются созданием конвейеров данных (data pipelines). Они берут данные из одного места, трансформируют их и перемещают в другое место, где они и потребляются. Как и приложения, конвейер данных представляет собой программное решение. Однако, в отличие от приложений он:
-
Не имеет прямой ценности для пользователя. У самого конвейера данных нет конечных пользователей — важен только итоговый датасет, который он создает. Если бы данные можно было передавать иным способом (хоть посадить 20 индусов, которые вручную бы копировали и редактировали бы файлики), стейкхолдер был бы одинаково удовлетворен.
-
Имеет только одну значимую функцию для заказчика — производство запрашиваемого датасета. В отличие от приложений, конвейер данных можно считать «завершенным», когда он выполняет свою задачу. Однако он требует постоянного обслуживания из-за изменений во входных данных и новых требований пользователей.
-
Неизбежно жестко связан с источником данных. Вся суть конвейера данных в том, чтобы получать данные из определенного источника. Поэтому его стабильность и надежность напрямую зависят от стабильности исходных данных.
Эти фундаментальные различия создают уникальные вызовы в сфере дата-инженерии, которые часто недооцениваются бизнесом, ИТ-менеджерами и даже разработчиками ПО.
Data pipeline — либо завершен, либо бесполезен
В отличие от приложений, которые могут разрабатываться итеративно и выпускаться с частичным функционалом, конвейер данных имеет смысл только в полностью завершенном виде. Если он не доставляет нужный датасет в требуемый «пункт назначения», он бесполезен.
Представьте себе процесс, который извлекает информацию из источника, но не загружает ее в хранилище — он не приносит ценности. Аналогично, если данные трансформируются «не полностью» или содержат ошибки, пользователи не смогут на них полагаться, что делает их непригодными к использованию.
Это принципиально отличает дата-инженерию от разработки ПО, где можно выпустить MVP, который работает частично, но уже приносит пользу пользователям. В случае с данными «частично работающий» конвейер — это нерабочий конвейер.
Большинство организаций управляют командами разработки по принципам Agile. Основная идея Agile — максимизировать скорость доставки ценности пользователю, выпуская ПО небольшими итерациями. Это позволяет быстрее создать MVP и получать обратную связь, чтобы команда работала только над самыми приоритетными задачами.
Однако эти принципы плохо применимы к дата-инженерии.
-
Конвейер данных нельзя разработать поэтапно, увеличивая его ценность на каждом шаге. У него нет эквивалента MVP. Он либо создает нужный набор данных, либо нет. Можно, конечно, попытаться поизвращаться с «неполным» датасетом (как по колонкам, так и по строкам) — иногда такие кейсы даже возможны, но в целом, это скорее экзотика и «натягивание совы на глобус»
-
Разработка загрузки плохо вписывается в Agile-методологии. Сложный конвейер как правило соответствует одной User Story, но его реализация может занять несколько спринтов. Менеджмент часто игнорирует этот нюанс и пытается встроить дата-инженеров в Scrum-команды. В результате User Stories по факту превращаются в этапы реализации в виде мелких задач, типа:
-
«Создать таблицы»
-
«Написать коннектор для API»
-
«Реализовать логику загрузки в stage-layer»
-
«Настроить трансформацию данных stage -> bronze/silver/gold-layer»
-
и т.п.
-
Это приводит к микроменеджменту и снижению эффективности работы. Когда руководство не понимает сути дата-инженерии, оно принимает нерациональные решения.
Например, один из менеджеров, недовольный «медленным» прогрессом, потребовал от дата-инженера создавать набор данных итеративно — колонка за колонкой, чтобы клиент «хотя бы получил хоть какие-то данные».
Опытные дата-инженеры и дата-саентисты сразу поймут, насколько бессмысленно это требование. А для тех, кто не сталкивался с подобными проблемами, вот причины, почему такой подход не работает:
-
Частичный датасет не имеет пропорциональной полезности. Если в наборе данных есть 9 из 10 колонок, означает ли это, что он на 90% полезен? Все зависит от того, какая именно колонка отсутствует (целевая переменная, столбец с второстепенными метаданными, какой-то важный/не важный признак — в случае, если датасет используется для ML).
-
Поэтому Data Scientist’ы хотят получить как можно более полный набор для начала работы. Частичные данные означают, что обучение моделей придется проводить заново, как только появятся новые поля, а это излишняя трата времени и ресурсов.
-
Время разработки конвейра не зависит от размера набора данных. (Ох, как часто я сталкивался с подобным заблуждением!) Даже если клиент доволен половиной датасета, создание этой половины не займет половину времени от разработки загузки полного датасета. Конвейер данных не состоит из независимых задач, каждая из которых отвечает за отдельный столбец.
-
Если несколько колонок происходят из одного источника, включение одной или всех в итоговый набор не увеличивает сложность.
-
Но объединение колонок из разных источников может варьироваться: от простого JOIN до сложных оконных функций и перекрестных преобразований.
-
Какие-то колонки могут быть перекладыванием «1 в 1», а какие-то — результатами сложных вычислений.
-
Перед тем как вообще появятся какие-либо данные, нужно разработать базовую инфраструктуру:
-
Клиент для API
-
Какой-нибудь парсер для неструктурированных данных
-
Оркестрирование
-
Логирование
-
и т.п.
-
-
-
При этом затраты на создание конвейера по загрузке датасета все равно растут с его размером. Чем он больше (как по количеству строк, так и по колонкам), тем дольше/сложнее его разрабатывать (и отлаживать), а также затем поддерживать.
Механическое перенесение принципов Agile в дата-инженерию не учитывает специфику данных. Инкрементальные изменения и частые релизы, которые хорошо работают в разработке ПО, здесь неуместны из-за «инерции» данных.
Дата-инженеры стремятся сделать все правильно с первого раза, минимизируя число деплойментов. Если конвейер данных развертывается слишком часто, это говорит либо о том, что:
-
Клиент сам не понимает, чего хочет и постоянно меняет требования.
-
Источник данных нестабилен, и требуются постоянные исправления.
В отличие от бесстатусных приложений, которые можно обновить, просто перезапустив контейнеры, обновление датасетов — это не просто переустановка кода. Здесь нужно еще «пересчитать» уже имеющиеся данные (например, добавить новые колонки, пересчитать какое-то поле в соотвествии с новой логикой или добавить исторические данные из нового источника), попутно «ничего не сломав». А учитывая, что «данные — новая нефть», то цена ошибки тут кратно выше.
Обратная связь в разработке data pipeline — очень медленная
Чтобы быстро создавать новые функции или исправлять ошибки в ПО в рамках спринтов, разработчикам необходима быстрая обратная связь.
В разработке ПО это обычно достигается с помощью юнит-тестов. Но вот в мире дата-инженерии юнит-тестирование производится крайне редко. Обычно конвейеры данных тестируют путем развертывания в среде разработки. Это требует шагов сборки и деплоя, после чего инженер должен некоторое время следить, чтобы проверить, работает ли он как задумано. Если конвейер не идемпотентен, то переразвертывание может потребовать ручного вмешательства для сброса состояния, оставшегося после предыдущего деплоя. Такой цикл обратной связи очень медленный по сравнению с запуском юнит-тестов.
Почему же тогда не просто писать юнит-тесты?
-
Конвейеры данных обычно «падают» из-за ошибок, которые невозможно протестировать юнит-тестами. Самостоятельно работающая логика, которую можно протестировать юнит-тестами, обычно сильно ограничена. Большинство ошибок происходят на неправильных интерфейсах между системами или из-за непредсказуемых данных. Особенно последнее.
-
Внешние системы редко стабильны. Использование мок не покажет, если реальная система изменилась так, что это сломает конвейре
-
Проблемы с качеством исходных данных. Я бы сказал, что 80% всех проблем связаны именно с этим спектом:
-
Источники часто не обеспечивают постоянное качество данных (как с точки зрения технических аспектов, так и логичекских/изнесовых). С этим можно только смириться. (Помню, как на одном из проектов после дискусси на тему, что «проблема должна быть исправлена на уровне источника, ибо это — явная ошибка в бизнес-данных», заказчик, исчерпав все свои аргументы, признался, что в том «далеком филиале в Мухосранске» в принципе невозможно найти квалифицированные кадры, которые бы не косячили при работе с «системой-источником», так что им проще заплатить нам, подрядчикам, чтобы мы правили эту «кривизну» на уровне загрузки данных)
-
Неожиданное содержание или структура данных может сломать процесс или привести к неправильному результату.
-
Обычно стратегия защиты от таких данных — это проверка поступающих данных. Однако это не защищает от ошибок в содержимом и тонких «данных-ошибок». Например:
-
Как обработать переход на летнее/зимнее время?
-
Содержит ли столбец строки, не соответствующие ожидаемому формату? (забавный пример — месяц назад у нас «упала» загрузка из-за даты «31 июня» во входных данных)
-
Имеют ли числовые значения физический смысл (например, температура не может быть 2000°C, отрицательный остаток на банковском счете)?
-
-
Эти проблемы не связаны с логикой конвейера данных, которую можно протестировать юнит-тестами. К тому же поле потенциальных ошибок в данных — необъятно.
Другой аспект, что юнит-тесты, необходимые для тестирования ограниченной самодостаточной логики преобразования, часто сложнее самого кода, поскольку требуют от разработчика создания представительных тестовых данных и ожидаемых выходных данных. Это много работы, которая не сильно увеличивает уверенность в конечном результате. К тому же эти тестовые данные должны охватывать значительное подмножество комбинаций входных параметров. Однако в функциях, которые преобразуют датасет, аргументы представляет собой почти бесконечное пространство всевозможных комбинаций входных данных.
В моей практике лет 15 назад была попытка с подачи менеджмента внедрить тестирование на синтетических данных — по сути это было то, что чуть позже назовут Test Driving Development. То есть аналитики «генерил» тестовые входные данные со всеми основным (по его мнению) комбинациями значимых атрибутов и потом на основе этих данных сам рассчитывал выходной результат. Эта даже пытались обернуть в зарождавшийся тогда CI/CD (таких модных слов мы тогда не знали и говорили просто «автотесты при сборке»).
Потратив на это несколько месяцев мы убедились в бесперспективности данного занятия. Затраты на генерацию входных и выходных тестовых данных были значительными и сопоставимы с затратами на саму разработку. При этом в ходе такого вот тестирования чаще находили ошибки в тестовых данных, а не в коде. И что самое печальное, это незначительно влияло на количество ошибок, найденных уже после деплоя на ПРОД.
В последствии, я еще пару раз встречался с попытками нескольких менеджеров предложить подобный подход. Но благо, мы достаточно легко могли их убедить, что это — просто выброшеные на ветер деньги. А компания была коммерческая, и деньги считать умела :)
Поэтом самый надежный способ получить обратную связь о конвейере данных — это развернуть и запустить его. Это всегда будет медленнее, чем запуск юнит-тестов локально, что значит, что обратная связь поступит гораздо позже. Результат — разработка, особенно на этапе отладки, становится очень медленной.
Кроме того, обратная связь необходима не только «техническая», но и от конечного стейкхолдера (помним, что заказчика интересует конечный датасет и его корректность, а не то, что «пули вылетели и долетели»). А он сталкивается с той же проблемой, что и Дата-Инженер — данных много, возможный комбинаций различных кейсов — тоже не мало. Ему так же требуется немало времени, чтобы дать обратную связь.
Самый идеальный случай — это когда у вас есть какой-нить «эталон» для сравнения. Хотя бы для части датасета. Это может быть какой-либо отчет из «системы-источника» (например, в банковской сфере это может быть какой-то встроеный в АБС стандарный отчет типа «Баланс кредитной организации» или «Кредитный портфель»), или какая-нибудь регуляторная отчетность. А может это какая-то выгрузка из «старой системы», которую вы стемитесь заменить, внедряя новое DWH/Data Platform. Тут главное — четко понимать, насколько этот «эталон» — действительно является эталоном, чтобы процесс сверки не «перевернулся» и не превратился в поиск ошибок в «эталоне».
Information security
Еще один немаловажный аспект, который хотелось бы затронуть, это подход к information security и построению инфраструктуры для нужд дата-инженеринга. Особенно актуально это в современном мире, где cloud фактически становится стандартом, количество утечек данных растет, а их последствия несут для компаний все больше финансовых и репутационных рисков. Я не буду в рамках данной статьи повторять прописные истины про важность защиты данных в организации — это нестолько очевидно, что повторять это просто нелепо. Я хочу взглянуть на это наоборот, с другой стороны, со стороны повседневной работы команды дата инженеров/аналитиков/саентистов.
И здесь мне очень часто просто хочется ВЫТЬ! Те, кто придумал все эти политики, правила и ограничения для разворачивания cloud-ресурсов согласно всем требованиям типа безопастности — желаю вам гореть в аду! Я понимаю ваше стремление защитить данные огранизации от утечки, но не обязательно же при этом делать невыносимыми повседневные будни дата-команды? Практически невозможный доступ к любым storage account/базам данных/etc., несколько private gateway через, естественно, корпоративный VPN (c двухфакторной аутентификацией конечно же) с дополнительной активацией Contibutor access каждые 4 часа и все это через Remote Desktop, работающий исключительно в Web-браузере с отключенным буфером обмена. Это не фантазия, это то, как мы сейчас работаем. Подключение к базе через толстый клиент? А чем вас не устраивает «потрясающий», хоть и находящийся в Preview Web-UI для Azure SQL? Запросы писать же можно… Хочешь работать в привычном Visual Studio Code / IntelliJ IDEA? Так Databricks же имеет уже «великолепный» Web-UI c ноутбуками…
В итоге данные типа защищены. В том числе от тех, кто с ними должен работать по долгу своих обязанностей. Процессы information security и политики развертывания ресурсов просто не учитывают специфику работы с данными. Если для какой-нибудь бэк-энд разработки разработчику действительно не нужен прямой доступ к БД — достаточно, чтобы контейнер, в котором будет «крутиться» приложение имел доступ к ней, то для дата-команд такой доступ просто необходим. Но DevOps и information security этого зачастую не понимают.
Из той же серии маниакальное стремление ограничить копирование данных с ПРОДа на ДЕВ, ровно как и закрыть вообще любой доступ разработчику на ПРОД. Этот подход опять же пришел из других сфер разработки. Ограничения на доступ в ПРОДу, ровно как и запрет на синхрнизацию данных ПРОД-ДЕВ — это в дата-инженерии сродни запрету доступа к исходному коду, который работает на продакшене. Отделение кода конвейеров данных от самих данных — тупиковый подход. Я бы рассматривал код конвейра данных и сами данные — как две неделимые половины. Код загрузки и трансформации данных зависит от самих данных и наоборот. Одно без другого не имеет смысла.
Самое опастное в таком «все запретительском» подоходе те, что дата-команда, чувствуя, что иного выхода нет, начинает втихую искать, и находит, различные «лазейки» — работать то надо. И это в свою очередь несет не меньшие, а то и бОльшие риски. Из своей практики я могу на вскидку вспомнить как минимум два случая, когда мы использовали такие «лазейки» втихую. И мы свято хранили «наш секрет», хотя и понимали, что это очень большая «дыра» в безопастности. Хорошо, что мы в были профессионалами, отдавали себе отчет в том, что делаем и поэтому соблюдали со своей стороны все меры предосторожности… Но куда лучшим и более безопасным бы было совместно с DevOps и information security выработать устраивающее всех решение, покрывающее потребности Data Team и при этом не слишком усложненное и достаточно безопасное.
Заключение
Конвейеры данных — это программное обеспечение, но не программные продукты. Они — фабрика, которая служит для создания того «автомобиля», который заказал клиент. Это «средство производства». Независимо от того, что утверждают «влиятельные лица» в области данных, независимо от того, сколько уровней абстракции накладывается, данные принципиально отличаются от ПО. Игнорирование этих различий и применение процессов Agile к дата-командам, потому что это сработало в команде по разработке ПО, в конечном итоге часто приводит к неудаче.
Как сделать команду по данным успешной и продуктивной?
-
Признать, что легкая форма Waterfall (обычно ассоциирующаяся с плохим в программной инженерии) неизбежно будет происходить в проектах в сфере Дата-Инженерии. Прежде чем начать разработку, необходимо провести множество обсуждений с клиентами для уточнения требований к желаемому датасету, а также с поставщиками данных для обмена знаниями о не задокументированных API и подключения к исходной системе. Признайте, что изменения будут стоить дорого после того, как конвейер будет вынесен на продакшн.
-
Выделите дата-инженерам время на профилирование данных и эксперименты с источниками. Признайте, что несмотря на документацию и «дата-контракты» проблемы с качеством данных все равно будут.
-
Не разделяйте разработку одного конвейер данных между несколькими разработчиками. Лучше выделите больше времени на код-ревью, ибо это скорее поможет выявить проблемы на ранней стадии.
Автор: Ninil