Архитектура хранилища функций и использование

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

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

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

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

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

Может быть, это не на 100% точно, но это более-менее наша повседневность.

Хорошо, характеристики важны. Я убежден. Теперь мы должны правильно обработать их, используя хранилище функций. Посмотрим, как.

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

Уровень данных

У нас было два источника данных для создания наших функций:

  • Озеро Дельта: это был централизованный источник данных компании, которым управляла команда инженеров данных на базе AWS S3.
  • Потоковые данные (Kafka): данные о посещениях с корпоративного портала.

Реестр функций

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

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

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

Особенности вычислений

Мы использовали PySpark для вычисления функций (и Spark Streaming для потоковой передачи данных).

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

Хранение функций

Мы использовали Cassandra в качестве нашего онлайн-уровня, а Delta Lake — в качестве автономного уровня. Просто как тот.

После того, как мы вычислили новые значения функций, было сделано следующее:

  1. Обновите онлайн-слой, заменив старое значение или добавив новое для нового ключа в группе объектов. Благодаря функции Cassandra Upsert это было легко сделать.
  2. Вставьте новые значения в автономный слой с соответствующей отметкой времени.

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

Для поддержки всех этих операций мы разработали библиотеку Python, которая отвечала (среди прочего):

  • Регистрация функций: для обновления реестра функций.
  • Загрузка рассчитанных объектов: для запуска обновления онлайн- и офлайн-слоев, описанного выше.
  • Получение функций: как для обучения, так и для вывода

Особенности организации и потребления

Мы организовали функции в нашем хранилище функций в иерархической структуре по доменам и группам. Например, поскольку мы были в контексте данных о недвижимости, у нас был домен Product, в котором была группа Value с такими функциями, как condominium_fee, rent_price и sale_price, поэтому, чтобы получить эти функции позже (для обучения или вывода), мы мог запросить у них полное имя: product.value.sale_price. Вот так просто!

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

Чтобы получить эти функции, в нашей библиотеке есть два специальных метода:

  • get_features: получить исторические данные с возможностью фильтрации по периоду
  • get_online_features: для получения данных из онлайн-слоя. Используется, например, для вывода.

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

Как он интегрируется в нашу экосистему

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

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

Это довольно просто. Что ты об этом думаешь? Я надеюсь, что это было полезно для вас в некотором роде.

Want to Connect?

Let's talk! You can find me on LinkedIn. 

You're welcome to drop me a message!