Как мы снижали time to market в MLE-команде
Привет Хабр! Рано или поздно в разработке появляется одна из важных метрик — time to market (TTM), которая напрямую влияет на все процессы в компании. Хочу поделиться нашим опытом снижения TTM и рассказать, почему это было непросто, но интересно.
Для начала разберёмся, что же такое TTM.
TTM — отрезок времени от появления идеи до запуска продукта.
Чем меньше TTM, тем быстрее бизнес реагирует на рынок и конкурентов, а также проверяет больше гипотез.
Наша команда разрабатывает сервисы ML-моделей для оценки стоимости недвижимости. Эта оценка применяется в различных внутренних процессах. Если их сгруппировать, то можно выделить два основных типа: ипотечные и UI-процессы.
Новая команда
Так получилось, что я пришел в новую команду и начал погружаться в общее состояние дел. Приходя в новую компанию или команду, я всегда начинаю с анализа текущей ситуации в продукте и команде. Этот этап можно назвать «Первые три месяца».
Диапазон | Что делаем | Контекст |
1 неделя | Знакомство с внутренним и внешним клиентом | Знакомимся ближе с продуктом и клиентами в роли клиента |
2—4 недели | Анализ технической команды и процессов в роли слушателя | Изучаем проблемы бизнеса, команды и клиентов, а также процессы, связанные с командой |
5—8 недель | Глубокое погружение в критические системы | Изучаем техническое состояние всех систем |
9—12 недель | Первые изменения процессов | Планируем и внедряем изменения (при необходимости) |
Этот фреймворк, конечно, не панацея, но он позволяет ориентироваться в новой неопределённости и обозначать конкретные направления для своих действий.
Самое главное — не забывать о здравой логике и не спешить менять буквально всё с первого дня.
Что было в наследстве
У нас микросервисная архитектура. Только в нашей команде более 30 микросервисов, задействованных в оценке недвижимости. Наш стек включает Python, PSQL, Redis (частично) и RabbitMQ.
Богатые сервисы
Самое интересное — это наши сервисы. В чём заключалась сложность?
Разные фреймворки серверов. Мы использовали три фреймворка: aiohttp, Sanic и FastAPI.
Неоднородная архитектура сервисов. Поскольку вся логика была написана на Python и без строгих стандартов, мы столкнулись с разнообразием структур и архитектур в различных сервисах. К тому же каждый сервис являлся примером уникального творчества. Были действительно интересные решения, но тем не менее мешало команде:
Эффективно решать бизнес-задачи
Легко поддерживать и развивать систему
Быстро адаптировать новых членов команды и погружать опытных сотрудников
Отсутствие тестов. Это классическая проблема legacy-кода: тесты либо отсутствуют, либо их очень мало. Нам немного повезло, кое-где тесты всё же были.
Мониторинг. В инфраструктуре и в соседних командах уже существовал качественный мониторинг сервисов. Благодаря этому команды не узнавали «случайно» и посреди ночи о том, что, например, в очереди накопилось 25 тыс. сообщений или что сервису плохо и требуется больше ресурсов. В нашем же случае где‑то мониторинг существовал, а где‑то метрики в сервисе просто отсутствовали. Дашборды тоже различались, поэтому приходилось при необходимости искать нужные графики.
Хаос в данных
Наши модели построены преимущественно на табличных данных. Мы обрабатываем значительные объемы информации, предъявляя к ней определённые требования: нам нужны исторические данные, которые надо уметь хранить, считать и складывать.
В процессе работы мы столкнулись с тремя основными сложностями: широкие витрины, дублирование данных и нецелевые технологии для расчётов. Коротко опишу каждую из них.
Широкие витрины: витрины были очень объемными и не имели чёткой декомпозиции на более мелкие участки. По сути, модель данных отсутствовала — была одна большая витрина с множеством столбцов.
Дублирование данных: данные могли дублироваться между разными типами недвижимости. Например, одна и та же информация могла храниться в разных базах данных, но извлекалась только та, которая была необходима для конкретного направления.
Нецелевые технологии: для расчётов мы использовали Airflow и Python, а хранение осуществлялось в PostgreSQL. Возможно, вы спросите: «Почему это нецелевое использование?» Дело в том, что буквально все расчёты проходили через оперативную память с помощью Python. В нашем случае существует более простое и понятное решение, о котором я расскажу далее.
Весь процесс разработки
Мы провели экспертный анализ процесса разработки с опорой на данные из Jira, и пришли к тому, что среднее время, затрачиваемое на сервис и подготовку данных для конкретной модели, составляет в лучшем случае до десяти недель, в худшем — до 13 недель.
Важно отметить, что некоторые этапы выполняются параллельно и не блокируют работу разработчика. Так, например, создание баз данных осуществляет команда DevOPS, а пентесты проводит команда кибербезопасности.
Что внедрили
Проблемы разнообразны и многочисленны, что делает задачу интересной. Я предпочитаю принимать решения, которые дают системный результат: один раз разработанное решение затем используется многократно.
ТЗ для передачи задачи от DS-команды
Мы тесно сотрудничаем со специалистами в Data Science (DS). Если взглянуть на процесс разработки, то этап «Анализ и создание ТЗ» мог занимать до трёх недель. Да, вы не ослышались: разработчик самостоятельно создавал себе ТЗ, выясняя детали по ходу дела.
Одним из первых предложений и решений стало внедрение во всей команде формализованных документов «Технического задания» — от бизнеса к DS и от DS к MLE.
Пример ТЗ
Этап | Что | Пример |
Декомпозиция | Ссылки на документацию | Ссылки на бизнес ТЗ |
Модель | Репозиторий + заполненный README | Ссылки на репозитории |
Задача предсказания | 1. Сущности, на основе которых делается предсказание 2. Возможный исход предсказаний 3. Алгоритм предсказания | 1. Данные о квартире 2. Смогли/не смогли предсказать 3. Последовательность действий, в которых нужно сделать предсказание |
Развёртывание модели | 1. Варианты использования модели 2. Методы оценки перфоманса модели | 1. Применение модели: UI или ипотечные процессы 2. Скорость работы предсказания |
Описание контракта | 1. Данные на вход 2. Данные на выход | 1. Данные, которые нужно отправить в модель для предсказания 2. Данные, которые модель отдает |
Источники данных | Источник данных для сбора сущностей | Источники, схемы, таблицы |
Фичи | Описание фичей | Описание важных фичей для модели: что означают столбцы в таблицах и что нужно |
Конвейер подготовки данных | Пошагово описать алгоритм сбора данных и трансформацию данных для каждой фичи, используемой в модели | Описание алгоритма подготовки данных |
Наличие такого документа для каждой модели позволяет нам в любой момент проследить историю её работы и изучить как логику, так и сами данные. Кроме того, это формализует требования к разработке, предоставляя разработчику чёткое понимание:
Контракта DS-библиотеки
Необходимых данных
Сторонних сервисов для интеграции
Внедрение только этого документа сократило наш time to market до трёх недель.
Архитектура сервисов и шаблон
Переходим к самому интересному - это архитектура сервисов. На мой взгляд, архитектура сервиса — ключевой аспект в разработке микросервисов, которому следует уделить особое внимание, чтобы значительно упростить дальнейшую поддержку системы всей команде. Мы остановились на Onion Architecture — подходе, при котором систему делят на несколько слоёв, каждый из которых выполняет свою функцию.
Наша типовая архитектура сервиса стала выглядеть следующим образом:
service_name
|__ adapters
| |__ repositories
| |__ http_clients
|__ domain
|__ entrypoints
|__ routes
|__ http_server.py
|__ services
| |__ predict_service.py
|__ config.py
|__ di.py
settings
tests
pyrpoject.toml
adapters
: системные компоненты (репозитории, HTTP-клиенты, паблишеры RMQ и Kafka и т.д.), не отвечающие за бизнес-логикуdomain
: тематические классы данных (вход для конкретных конечных точек и их выход, известные как контракты)entrypoints
: роуты сервера, инициализация сервераservices
: реализация объектов с бизнес-логикойdi.py
: паттерн Dependency Injection, чтобы все инициализации объектов и их зависимостей находились в одном месте и не были разбросаны по всему сервису
Для обеспечения повторного использования и дальнейшего развития мы разработали шаблонный репозиторий, Каждый новый типовой сервис разворачивается на его основе. Для генерации кода мы используем cookiecutter — простое в настройке и использовании решение.
В результате мы получили следующие преимущества:
Архитектура всегда готова
Архитектура становится единой для всех сервисов
Разработчик не тратит время на создание новой архитектуры
Эти действия позволили нам сократить time to market до 1—1,5 недель.
Фреймворк для сервисов
Для поддержания единообразия в шаблонах и архитектуре сервисов нам понадобился общий фреймворк. Эта тема заслуживает отдельной статьи с примерами, но здесь мы рассмотрим ключевые аспекты: что это, зачем нужно и какие стандарты мы применяем.
Что такое фреймворк для сервисов в нашем понимании? Это готовая библиотека, позволяющая быстро и в едином виде создавать API-серверы в соответствии с командными стандартами разработки. Мы выбрали FastAPI, добавили к нему наши стандарты и упаковали всё в собственную библиотеку.
Зачем это нужно? При увеличении количества сервисов важно, чтобы их поддерживала вся команда, а не отдельный специалист, отвечающий за определённый набор сервисов. Это позволяет не распределять нагрузку: например, по пять сервисов на одного человека, а каждый разработчик может поддерживать практически любой сервис.
Библиотека помогает нам:
Ускорить длительное погружение разработчика в контекстную область
Делиться опытом
Спокойно уходить в отпуск всем сотрудникам
Мы применяем следующие стандарты:
Метрики сервера: RPS, latency роутов, коды статуса маршрутов
Базовый HTTP-клиент с метриками: latency запросов к смежным сервисам, коды статуса
Единый стандарт логирования
Базовые Exception Handlers
Базовые Middlewares
Добавление sentry
Пример сборки стандартного сервера
def create_app(di: DI) -> FastAPIServer:
config = di.resolve(Config)
error_response = {'model': ErrorResponse}
factory = FastAPIFactory(app_name=config.name, app_version=version)
factory.with_logconfig(logconfig=config.logging)
factory.with_sentry(sentry_dsn=config.sentry_dsn)
factory.with_routes(srv_routers)
server = FastAPIServer(
lifespan=lifespan,
title=config.name,
version=config.version,
responses={
HTTPStatus.INTERNAL_SERVER_ERROR: error_response,
HTTPStatus.UNPROCESSABLE_ENTITY: error_response,
},
)
return factory.build_default(app=server)
Однажды разработав такой фреймворк в виде так называемого коробочного решения, мы можем использовать его постоянно вместе с выбранным шаблоном и архитектурой.
Этот фреймворк для сервисов позволил нам снизить time to market на 1—1,5 недели.
Feature Store
Завершив разработку и внедрение ключевых компонентов сервисов, можно поговорить про Feature Store. Это система с набором различных инструментов для подготовки в офлайн-режиме данных к использованию в наших сервисах. Эта обширная тема подробно рассмотрена в отдельной статье.
Основные преимущества использования Feature Store заключаются в решении следующих задач:
Расчёт данных для моделей
Упрощение поддержки расчётов с помощью SQL
Повторное использование расчётов
Поставка данных для сервисов в эксплуатацию
Создание Online Feature Store.
Инструменты, которые мы используем:
GreenPlum для холодного хранения
PostgreSQL для онлайн-хранения
AirFlow
Inhouse DBT
Feature Store оптимизирует подготовку данных для конкретных моделей, сократив time to market примерно на 30% (в абсолютном выражении это три недели).
Что стало с time to market
Совокупность всех внедрённых улучшений позволила получить такой процесс разработки:
Весь процесс разработки сократился до пяти недель. Этот показатель является наихудшим сценарием, поскольку длительность зависит от сложности логики и количества необходимых интеграций. При неопределённости в логике мы закладываем именно пять недель на разработку.
На практике же случалось, что мы заканчивали разработку за 2,5 недели.
Этап | Сокращение time to matket |
Шаблон ТЗ от DS к MLE | До трёх недель |
Архитектура и шаблон | До 1—1,5 недель |
Framework сервиса | До 1—1,5 недель |
Feature Store | До трёх недель |
В заключение хочу поблагодарить команду за достигнутый превосходный результат. Отслеживайте свой time to market — это напрямую влияет на ваш бизнес и позволяет добиваться новых прорывов. Для нас же это является демонстрацией высокого профессионализма.