Поучительная история о конвейере данных

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

Заданию GetTheData необходимо извлечь все необходимые данные из всех различных баз данных приложений. Затем необходимо запустить задание CrunchTheNumbers, чтобы агрегировать все данные из предыдущего шага. Он просто выгружает свои выходные данные в S3, поэтому есть еще одно задание, называемое LoadTheData, которое включает сбор данных и их размещение в реляционной базе данных, которую использует приложение приборной панели. Затем есть еще одно задание под названием TotalUpTheBill, которое собирает эти данные из S3 и определяет, каким клиентам нужно выставить счет и в каком размере. Задание TotalUpTheBill сохраняет свои результаты во временном плоском файле на узле, на котором выполняется задание. Другое задание под названием SendTheBills берет этот файл и отправляет кучу электронных писем, чтобы выставить счет всем клиентам, используя некоторые данные из другой реляционной базы данных, в которой хранится вся информация об учетных записях клиентов.

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

Джей Крепс и другие в сообществе Kafka сравнили системы с этим потоком данных со спагетти.

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

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

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

Предупреждение о спойлере: они не будут нажимать на объявление. ¯ \ _ (ツ) _ / ¯

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

Исследования показали, что 20% рабочего времени специалиста по данным тратится на создание систем данных, которые почти построить невозможно. *

* На самом деле исследования могут не показать ничего подобного. У кого есть время читать исследования?

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

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

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

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

Все это одна система машинного обучения.

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

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

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

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

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

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

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

Когда Луиджи говорит, что ConsumerJob зависит от ProducerJob для производства данных, необходимых для запуска ConsumerJob, это зависимость. Но Луиджи, как и все другие инструменты оркестровки заданий, на самом деле не знает, действительно ли ConsumerJob может принимать данные, созданные ProducerJob, до того, как эти задания будут выполнены. Как это могло быть? Ему ничего не известно о фактических объектах, создаваемых тем или иным заданием, помимо того, что существует в коде, который управляет взаимодействием заданий. Поскольку этот код представляет собой динамически типизированный код Python, вам нужно будет фактически запустить часть этого кода, чтобы проверить, совместимы ли эти два задания (возможно, в тесте).

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

Вы можете просто скомпоновать конвейер в коде приложения.

Это звучит так просто; На самом деле это не может быть решением проблемы больших данных / машинного обучения, не так ли? Должен быть хотя бы какой-то тренд в блоге на Hacker News, который оправдывает этот подход, используя греческую букву или две, верно? Что ж, вот Ξ, чтобы вы начали. Мне придется полагаться на вас в голосовании за. Перейдем к инженерии.

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

Приведенный выше код никогда не будет компилироваться, если эти функции действительно не компилируются. Вместо того, чтобы просто надеяться, что выходные данные производственной функции будут использованы функцией потребления, компилятор сообщит вам об этом. Сейчас. На вашем ноутбуке. Прежде чем тратить ни копейки на EC2, стоит. ƪ (˘⌣˘) ʃ

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

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

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

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

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

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

Если вы работаете над Hadoop, я рекомендую попробовать Каскадирование и Скалирование, в зависимости от того, работаете ли вы на Java или Scala соответственно. У обоих есть хорошо продуманные абстракции для построения конвейеров.

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

  • Для безопасных операций с наборами данных вы можете просто использовать RDD, поскольку они сохраняют типы объектов в RDD.
  • Если вам нужна более элегантная идиома конвейера и вы готовы пожертвовать некоторой безопасностью типов, вы можете использовать конвейеры Spark. Они используют DataFrames вместо RDD, поэтому они предлагают более слабую гарантию, чем композиция с использованием RDD.
  • Чтобы получить лучшее из обоих миров, взгляните на KeystoneML. Некоторые из тех же людей, которые работали над функциональностью конвейера в spark.ml, доводят эти усилия до их логического завершения, сочетая состав конвейера с строгими гарантиями типизации.

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

Обратите внимание, что я не предлагал вам объединить кучу пакетных заданий без общих знаний и просто надеяться на лучшее. Мне было бы так грустно. (ಥ_ಥ)

Свое место у оркестровки. Даже в самом крохотном стартапе у вас будет несколько заданий, которые будут иметь какое-то отношение друг к другу. Когда вы окажетесь в такой ситуации, тогда, конечно, вам следует использовать инструмент для оркестровки работы, такой как Луиджи. У Эрика Бернхардссона, создателя Луиджи, есть хорошее руководство по альтернативам Луиджи, чтобы вы могли начать (даже если он упустил разницу между Марио и Луиджи). В этом сообщении блога гораздо больше, чем просто те. Pinterest только что открыл еще один. Я уверен, что все в порядке. Обычно это так.

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

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

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

Не оркестрируйте, когда умеете сочинять.