Поддерживаемый ETL: советы по упрощению поддержки и расширения конвейеров

задняя часть Программа перевода самородков

modularized code example

В основе любого проекта по науке о данных лежат... о, о, о... данные! Подготовка данных надежным и воспроизводимым образом является неотъемлемой частью процесса. Если вы обучаете модель, выполняете вычислительную аналитику или просто объединяете данные из нескольких источников в другую систему, вам потребуется построить обработку данных или ETL.1трубопровод.

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

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

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

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

  1. Составьте серию простых заданий.
  2. Используйте инструменты управления рабочим процессом.
  3. Используйте SQL везде, где это возможно.
  4. Внедрение проверок качества данных.

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

Составьте серию простых заданий

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

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

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

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

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

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

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

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

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

WITH added_threshold as (
  SELECT
    items.item_price,
    thresh.high_price_threshold
  FROM shipped_items as items
  LEFT JOIN thresholds as thresh
    ON items.client_segment = thresh.client_segment
      AND items.item_category = thresh.item_category
), flagged_hp_items as (
  SELECT
    CASE
      WHEN item_price >= high_price_threshold THEN 1
      ELSE 0
    END as high_price_flag
  FROM added_threshold
) SELECT
    SUM(high_price_flag) as total_high_price_items,
    AVG(high_price_flag) as proportion_high_priced
  FROM flagged_hp_items

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

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

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

Например, предположим, что компания добавляет новую категорию товаров, например, головные уборы. Мы начали продавать головные уборы, но забыли обновить пороговую таблицу. В этом случае наш совокупный индикатор пропускает высокие цены. Поскольку мы использовали ЛЕВОЕ СОЕДИНЕНИЕ, поскольку соединение не удаляет строки, аhigh_price_thresholdбудет NULL. На следующем этапе удаляются все строчки, связанные со шляпой, чьиhigh_price_flagбудет равно нулю, и это значение будет принято в наш окончательный расчетtotal_high_price_itemsиproportion_high_priced.

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

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

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

Мы пошли на некоторые компромиссы, создав поэтапный конвейер. Одним из самых больших является производительность во время выполнения, особенно когда мы имеем дело с большими наборами данных. Чтение и запись данных с диска влечет за собой значительные накладные расходы, и на каждом этапе обработки мы считываем вывод предыдущего этапа и записываем результат. Большое преимущество Spark по сравнению со старой парадигмой MapReduce заключается в том, что временные результаты можно кэшировать в памяти рабочих узлов (исполнителей). Механизм Spark Catalyst также оптимизирует планы выполнения для запросов SQL и преобразований DataFrame, но не оптимизирует границы чтения/записи. Второе серьезное ограничение этих поэтапных конвейеров заключается в том, что они усложняют создание автоматических интеграционных тестов, которые включают проверку результатов нескольких этапов вычислений.

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

Используйте инструменты управления рабочим процессом

Благодаря надежному управлению рабочим процессом и механизму планирования можно добиться значительного повышения производительности. Некоторые общие примеры включаютAirflow,Oozie,LuigiиPinball. Эта рекомендация требует времени и опыта для создания; это не то, за что может нести ответственность отдельный специалист по данным. В Stitch Fix мы разрабатываем собственные проприетарные инструменты, поддерживаемые нашей командой платформы, которые специалисты по данным используют для создания, запуска и мониторинга наших собственных рабочих процессов.

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

Используйте SQL везде, где это возможно

Это, наверное, самое спорное предложение, которое я когда-либо делал. Даже в Stitch Fix есть много специалистов по данным, которые выступают против SQL в пользу языков программирования общего назначения. Не так давно я был частью этого лагеря. С практической стороны SQL трудно тестировать, особенно с помощью автоматических тестов. Если у вас есть опыт работы в области разработки программного обеспечения, трудности тестирования могут заставить вас чувствовать себя достаточной причиной, чтобы избегать SQL. В прошлом я попадал в эмоциональную ловушку по поводу SQL: «SQL менее техничен и менее профессионален;настоящийспециалистов по данным должны кодировать. "

Основное преимущество SQL заключается в том, что его могут понять все специалисты по данным: специалисты по данным, инженеры данных, инженеры-аналитики, аналитики данных, администраторы баз данных и многие бизнес-аналитики. Это огромная база пользователей, которые помогают создавать, проверять, отлаживать и поддерживать конвейеры данных SQL. Несмотря на то чтоStitch Fix не имеет многих из этих ролей данных, но SQL — это общий язык наших разнообразных специалистов по данным. В результате использование SQL снижает потребность в специализированных ролях в командах с сильным опытом работы с CS, создавая конвейеры для всей команды и не имея возможности справедливо разделить обязанности по поддержке.

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

Различные варианты языка SQL имеют много общего, и у нас есть определенная степень переносимости с одного механизма SQL на другой. В Stitch Fix мы используем Presto для специальных запросов и Spark для рабочих конвейеров. Когда я создаю новый ETL, я обычно использую Presto, чтобы понять структуру данных и выполнить часть преобразования. Когда детали на месте, я почти всегда использую Spark.4Запустите тот же оператор запроса без каких-либо изменений. Если бы я переключился на API Spark DataFrame, мне пришлось бы полностью переписать свой запрос. Обратное также может отражать это преимущество переносимости. Если есть проблема с производственным заданием, я могу повторно запустить тот же запрос и добавить фильтры и ограничения, чтобы получить подмножество данных для визуальной проверки.

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

Внедрение проверок качества данных

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

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

Сбои восходящего потока также могут привести к неверным данным в исходных данных: неправильно вычисляются поля, чаще происходят изменения схемы и/или отсутствующие значения. В динамической и подключенной среде нередко экспериментируют с источниками данных, созданными другой командой, и эти источники часто неожиданно меняются; в основном это среда, в которой мы работаем в Stitch Fix. Модульные тесты обычно не отмечают эти сбои, но они могут быть обнаружены с помощью проверки во время выполнения (иногда называемой проверкой качества данных). Мы можем написать отдельные ETL-задачи, которые будут автоматически выполнять проверки и выдавать ошибки, если наши данные не соответствуют ожидаемым нами стандартам. Выше был приведен простой пример, где отсутствовал порог высокой цены шляпы. Мы можем запросить таблицу «Комбинированные отправленные товары и пороги высоких цен» и найти строки с отсутствующими порогами. Если мы найдем какие-либо строки, мы можем предупредить сопровождающего. Эту идею можно обобщить на более сложные проверки: вычисление нулевых оценок, среднего значения, стандартного отклонения, максимума или минимума.

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

Работа в процессе

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

Горький плод, который я попробовал на прошлой работе, научил меня тому, что если трубопроводтрудно использовать, это затруднит поддержку и расширение проекта. Я работал в инновационной лаборатории, которая впервые применила инструменты больших данных для решения проблем в организациях. Моим первым проектом было создание конвейера для выявления продавцов с украденными номерами кредитных карт. Я создал решение с помощью Spark, и получившаяся система очень успешно выявляла новые мошеннические действия. Однако, как только я передал его в отдел кредитных карт для поддержки и расширения, начались проблемы. Я нарушил все лучшие практики, которые я перечислил при написании конвейера: он содержит задание, которое выполняет много сложных задач, он был написан в Spark, который был новым для компании в то время, он полагался на cron для планирования и не ' Отправляйте оповещения в случае сбоя, в нем нет проверок качества данных, чтобы убедиться, что исходные данные актуальны и правильны. Из-за этих недостатков трубопровод не работал в течение длительного периода времени. Хотя существует обширная дорожная карта для добавления улучшений, эти улучшения редко реализуются, потому что код сложно понять и расширить. В конце концов весь пайплайн переписывается в более удобном для сопровождения виде.

Как и ваш текущий проект по обработке данных ETL, ваш конвейер никогда не бывает полностью завершенным и должен рассматриваться как постоянно меняющийся. Каждое изменение — это возможность для небольших улучшений: улучшения читабельности, удаления неиспользуемых источников данных и логики, упрощения или разбивки сложных задач. Эти рекомендации не являются крупным прорывом, но они требуют самодисциплины, если вы хотите последовательно им следовать. Как и при приручении льва, когда трубы маленькие, ими относительно легко управлять. Однако чем крупнее они становятся, тем труднее их контролировать и тем больше вероятность того, что они проявят внезапные и неожиданные расстройства. В этот момент вы должны начать все сначала и выбрать лучший подход, иначе вы рискуете потерпеть неудачу [5][#f5].


Примечания

[1]↩Аббревиатура для извлечения, преобразования и загрузки.

[2]↩Самый простой способ — использовать простую замену строк или интерполяцию строк, но вы можете добиться большей гибкости с библиотекой обработки шаблонов, такой как jinja2.

[3]↩Для Python, как в стандартной библиотекеClick,Fire,четноеargparseТакая библиотека может легко определить эти флаги командной строки.

[4]↩Такие операции, как манипулирование датами и извлечение полей из JSON, требуют изменения запроса, но эти изменения незначительны.

[5]↩Ни один льв или специалист по данным не пострадал при ведении блога.

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


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.