Рано или поздно в разработке появляется одна из важных метрик — 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 — это напрямую влияет на ваш бизнес и позволяет добиваться новых прорывов. Для нас же это является демонстрацией высокого профессионализма.
Автор: johnjames


