От legacy до промышленной платформы: инженерная эволюция OSA в «Магнит». data engineer.. data engineer. data science.. data engineer. data science. datalake.. data engineer. data science. datalake. mlops.. data engineer. data science. datalake. mlops. osa.. data engineer. data science. datalake. mlops. osa. pyspark.. data engineer. data science. datalake. mlops. osa. pyspark. архитектура системы.. data engineer. data science. datalake. mlops. osa. pyspark. архитектура системы. Облачные вычисления.
От legacy до промышленной платформы: инженерная эволюция OSA в «Магнит» - 1

Как мы провели проект через четыре «эпохи» – от ручных запусков на Windows‑планировщике до Spark + k8s на масштабе сети

Меня зовут Имиль Валиуллин, я тимлид команды разработки платформы OSA. В предыдущих статьях цикла On-Shelf Availability (OSA) уже разбирали с разных сторон: что такое OSA как продукт, как устроен алгоритм детекции аномалий и весь конвейер генерации сигналов – эвристики, ML‑модели, фильтры, обратная связь, A/B и оценка эффекта (ссылки на предыдущие статьи: 1, 2, 3). В этой статье мы раскрываем следующий слой – инженерный. Потому что всё перечисленное было бы невозможно без большой работы под капотом: данных, транспорта, оркестрации, SLA, мониторинга, качества данных, обратной связи, API и доставки сигналов в торговые точки. Многие забывают, что даже самая крутая ML‑модель – это только верхушка айсберга. Результат появляется только тогда, когда под ней есть надёжный фундамент: чистые данные, стабильный транспорт и бесперебойная доставка. Как говорится, garbage in – garbage out, и наоборот: качественный фундамент позволяет получить качественный результат.

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

Показать это мы хотим через эволюцию продукта – от legacy и ручных запусков до промышленной платформы, работающей на масштабе сети. С точки зрения пользователя это всё тот же продукт – сигналы на торговых точках (ТТ), помощь сотрудникам магазина, рост доступности товара. Но под капотом OSA прошёл несколько серьёзных инженерных перерождений, которые мы для удобства назвали «эпохами»: каменный век, бронзовый, железный и индустриальная эпоха. 

Глоссарий терминов 

On‑Shelf Availability (OSA) – доступность товара на полке.

Teradata – аналитическая СУБД.

Informatica – ETL‑инструмент для интеграции данных.

КХД – корпоративное хранилище данных.

ТСД – терминал сбора данных.

Антифрод –  защита от мошенничества.

Сигнал – запрос на проверку ценника или наличия товара на полке, выглядит как связка торговая точка – товарная позиция (ТТ‑ТП).

Копилки – таблицы, содержащие сигналы.

Алгоритм – бизнес‑ или ML‑модель, которая генерирует сигналы.

PUMP – фреймворк для транспорта данных, написанный внутренней командой проекта.

Кратко об OSA

OSA – On‑Shelf Availability, или доступность товара на полке, один из ключевых показателей в ритейле. Приходя в магазин, клиент ожидает увидеть нужный товар и в нужном количестве, а наша команда помогает сотрудникам торговых точек (ТТ) вовремя замечать пустые полки. 

Немного о команде

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

  • Data Engineer – 3 человека; 

  • Backend Developer – 1 человек; 

  • MLOps‑engineer – 1 человек. 

Также напрямую с нами работают ML‑инженеры (около трех человек) и аналитики данных (около четырех человек).

Каменный век: legacy и первый управляемый рефакторинг

В январе 2024 года к нам попал замороженный два года назад legacy MVP‑проект: без документации, с разрозненным стеком, разными способами запуска и Excel‑отчётностью. Команда, которая над ним работала, уже занималась другими вещами – но код стабильно работал «по инерции», что очень помогло при рефакторинге. Проект приносил пользу, поэтому задача была не «идеально переписать всё», а быстро привести его в управляемое состояние: собрать код, перенести расчёты, закрыть хаос в запусках и наладить отправку сигналов в торговые точки. Сигнал – это задание для магазина на терминале сбора данных (ТСД): проверить ценник или подтвердить наличие товара на полке. 

Технический стек выглядел так: 

  • Логика написана на SQL для Teradata, в ней же данные хранились и обрабатывались. Потоки данных и конвейеры расчётов были реализованы на Informatica. В основе проекта лежит MPP‑архитектура. 

  • ML‑модели работали на Python. 

  • Оркестрация – полная анархия. Задачи запускались через Cron на виртуалках, с помощью Informatica и планировщика задач Windows на личном ПК разработчика. Подход нас, мягко говоря, удивил. 

  • Отправка сигналов работала в двух направлениях: Java API предоставлял доступ к сигналам внешним партнёрам, а Python‑скрипт рассылал Excel‑файлы сотрудникам в торговых точках. 

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

Первый подход

Что мы имели на момент начала работ: 

  • один технический специалист; 

  • несогласованный бюджет на инфраструктуру; 

  • монструозная задача. 

От legacy до промышленной платформы: инженерная эволюция OSA в «Магнит» - 2

Было принято решение – переписать «как есть» на PySpark, так как в компании уже были примеры успешных проектов на данном стеке. Но быстро стали очевидны проблемы разработки и поддержки такой архитектуры, помимо описанных ранее: 

  • Неструктурированный код. Большая часть кода была написана на SQL, одни процедуры могли вызывать другие, и так далее. Такая вложенность могла достигать пяти уровней. Процедуры третьего порядка могли использовать данные, сохранённые на первом этапе. 

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

  • Каждая подсистема, если на тот момент их можно было выделить, была разработана разными командами. Каждый сделал что умел – потоки загрузки данных, расчёты, API. 

  • Устаревший и неоднородный стек. Teradata деградирует много лет, Informatica – не самый удобный инструмент. В целом компания идёт по курсу избавления от устаревшего стека. 

Итак, фронт работ был понятен, логика и источники – на руках. Дело оставалось «за малым»: переставить процессы с MPP‑рельс на Big Data‑конвейеры, полностью переписать SQL‑код на PySpark и перенести потоки данных из Teradata в Hadoop. Роль оркестратора на том этапе временно отдали Airflow, который администрировала смежная команда (владельцы Hadoop). 

На тот момент мы ощущали себя «проектом‑паразитом»: у нас не было собственной инфраструктуры, и мы полностью зависели от чужих мощностей и графиков релизов. 

Вдобавок к техническим проблемам, аналитика тоже требовала реанимации. В наследство нам досталось несколько «BI‑дашбордов»… в виде Excel‑таблиц, которые просто рассылались по почте топ‑менеджерам и заказчикам. 

Полное переписывание legacy‑кода, логики и ETL‑потоков на новый стек заняло у нас около 6 месяцев. 

Что получилось на выходе

Из позитивных моментов: 

  • Собрали код в один репозиторий с единой точкой входа, внедрили простенький CI/CD и полностью изолировали логику от транспортного слоя. 

  • Перенесли расчёты на Hadoop, переписали конвейер обработки на PySpark. Сознательно оставили часть логики на SQL API – это ускорило миграцию и упростило работу аналитикам данных. Для сохранения транзакционности перешли от MPP‑архитектуры Teradata к Big Data с форматом Delta Lake (если вам интересно узнать больше о формате Delta Lake, можно ознакомиться со статьёй), который обеспечивает базовые ACID‑свойства.

  • Создали собственное хранилище на HDFS и частично переиспользовали данные смежного проекта. 

  • Закрыли хаос в запусках с помощью Airflow, покрыли процессы интерактивными блок‑схемами и документацией. 

  • Разработали совершенно новый модуль для работы с терминалом сбора данных (ТСД). Большое спасибо разработчикам ТСД – они много сделали на своей стороне. 

  • У нас появилась система оповещения в закрытый канал «общественного» мессенджера с бумажным самолётиком на логотипе – теперь можно мониторить все процессы 24/7. 

  • В расчёте задействовано более 30 таблиц, ежедневный инкремент – около 400 Гб.

Из негативных моментов: 

  • Логика не претерпела практически никаких изменений, конвейер обработки считался более 3 часов. 

  • Конвейер расчёта был слабо структурирован. 

  • Работали на ограниченной географии – около 1000 торговых точек. 

  • Все данные (исходные и рассчитанные) лежали в одном слое. 

  • Только начали переписывать Excel‑отчётность. 

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

От legacy до промышленной платформы: инженерная эволюция OSA в «Магнит» - 3

Бронзовый век: первый управляемый продукт

Продукт начал расти, и ограничения первого этапа стали критичными. Нужно было масштабироваться на 20 000+ магазинов с жёстким SLA, уйти от зависимости от чужой инфраструктуры, ускорить транспорт данных и научиться считать не «всё для всех сразу», а с учётом географии, часовых поясов и окон доставки сигналов. 

Параллельно мы начали миграцию на собственную инфраструктуру в Yandex Cloud (YC) – пора было слезать с чужих ресурсов. 

Три критические проблемы

По дороге мы упёрлись в три критические проблемы:

  • Наш основной источник данных – корпоративное хранилище на базе Teradata (КХД). Выкачка данных через PySpark JDBC‑драйвер в S3-бакет YC происходила слишком медленно. Скорость транспорта полностью ломала любой потенциальный SLA. 

  • Свежие данные появлялись на стороне КХД в лучшем случае около 6 утра. Это критически сокращало окно для расчётов и отправки сигналов в магазины к началу рабочего дня. 

Что построили

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

Для загрузки данных из КХД в облако был разработан самописный фреймворк транспорта данных (PUMP) с использованием механизмов самой Teradata, что позволило значительно сократить время загрузки. 

Весь конвейер расчёта разделили на логические группы: расчёты вспомогательных таблиц, алгоритмов (ML‑модели, которые генерируют сигналы) и копилок (таблиц, содержащих сигналы). 

Чтобы уложиться в жёсткий SLA для 20 000+ торговых точек, мы отказались от идеи «считать всё и для всех одновременно». Мы привязали генерацию копилок к географии торговых точек – теперь расчёты запускаются по часовым поясам. 

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

Фундаментально изменили подход к запуску спарк‑приложений и хранению данных: 

  • Полностью разделены настройки Spark‑сессий и расчётов через конфиг‑файлы и управляющие таблицы. 

  • Общие параметры расчётов были вынесены в отдельные конфигурационные файлы. 

  • Хранение данных разделили на слои: исходные данные источников отделили от рассчитанных. Однако при переходе с HDFS на S3 расчёты стали дольше – скорость чтения из объектного хранилища оказалась ниже. Потребовалась дополнительная настройка: оптимизация конфигурации S3 и размера файлов.

Результаты бронзового века

  • Доказанный эффект – около 1000 рублей спасённых продаж в день на торговую точку. 

  • Конвейер охватывает все торговые точки компании, в среднем каждый день уходят сигналы на 21 500 магазинов. 

  • Суммарный эффект – около 21,5 млн рублей спасённых продаж ежедневно. 

  • Точность сигналов в малых форматах – на уровне 30%, в больших форматах – около 55%. Для сравнения: по данным McKinsey, 15-30% случаев отсутствия товара напрямую влияют на выручку – покупатели уходят к конкурентам. При этом 20–25% магазинов генерируют 65-70% всех проблем с OSA, поэтому фокус на проблемных точках даёт максимальный эффект. 

  • В расчёте задействовано 80+ таблиц: источников, рассчитываемых, управляющих – в том числе 20+ алгоритмов. 

  • С технической точки зрения проекту была добавлена гибкость и удобство масштабирования – что впоследствии позволило успешно тиражировать продукт на сеть магазинов Дикси (но это уже другая история). 

  • Вся инфраструктура, используемая для наших вычислений, соответствует подходу IaC (Infrastructure as Code). 

  • Внедрили SOPS для базовой защиты учётных данных и секретов.

Из негативных моментов: 

  • По‑прежнему основным источником данных является КХД. 

  • Управляющих таблиц становилось всё больше. 

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

От legacy до промышленной платформы: инженерная эволюция OSA в «Магнит» - 4

Железный век: масштаб вскрывает новые ограничения

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

Разберём по порядку, что начало болеть. 

Конфиги множатся быстрее, чем мы их пишем

При разработке мы придерживались подхода DRY (Don’t Repeat Yourself) и старались максимально параметризировать все запуски. Для настройки технической части расчётов команда разработки использует конфиг‑файлы, а логическую часть конфигурируют дата‑аналитики и ML‑инженеры через управляющие таблицы. Со временем таких таблиц стало всё больше. 

Транспорт работает, но не настолько хорошо, как хотелось бы

Несмотря на появление PUMP, при получении данных из КХД сохранились определённые ограничения: долгая подготовка данных, нестабильность сессий, потеря пакетов, нехватка места на стейджинг‑машине и другое. Нужны были дополнительные проверки источников до и после транспорта данных. 

Бизнес хочет сигналы раньше, чем источник готов их отдать

У бизнеса появилась потребность отправлять сигналы в магазины не позже 3 часов ночи. При этом данные на КХД всё ещё появляются около 6 утра – потребовались near real‑time данные (NRT). 

DE и ML работают параллельно, но не вместе

В тот момент сложилась ситуация, когда команды DE и ML имели разный подход к разработке кода и архитектуре. 

Облако даёт гибкость, но требует дисциплины и контроля расходов

На предыдущем этапе проект уже переезжал из on‑prem Hadoop в Yandex Cloud. Опыт был не самым приятным. Нужно было сделать проект менее зависимым от инфраструктуры, чтобы дальнейшие миграции проходили безболезненно (спойлер: миграции ещё будут). 

Также возникла проблема перерасхода средств на инфраструктуру. В on‑prem Hadoop это было менее заметно – мы не одни работали на кластере. После переезда в Yandex Cloud нагрузку генерируем только мы, плюс существует время простоя. Да, у облачных кластеров есть автоскейлинг, но он работает не всегда идеально. Можно включать и выключать кластеры по необходимости, но они могут не запуститься в нужный момент. Например, в зоне доступности закончились необходимые инстансы – кластер переходит в ожидание, и либо не запустится вовсе, либо запустится на усечённых мощностях. В обоих случаях про SLA можно забыть. 

Алертинг превращается в шум

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

Итог железного века. Продукт уже работает на масштабе сети, но накапливается набор системных проблем, которые нельзя закрыть «ещё одной таблицей конфига». Нужно идти в зрелую инженерную платформу. 

От legacy до промышленной платформы: инженерная эволюция OSA в «Магнит» - 5

Индустриальная эпоха: зрелая data/ML‑платформа

Это уже стадия зрелой платформы, когда недостаточно просто «посчитать и отправить сигналы». Нужно управлять продуктом как промышленной системой: со свежими данными, NRT, Data Quality, контролем стоимости облака, переносимостью между инфраструктурами, единым процессом разработки и нормальной эксплуатацией. 

На данный момент это актуальная версия проекта. 

Мастер‑таблица – диджейский пульт пайплайна

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

Валидация данных до и после транспорта

Для повышения качества данных разработали механизм валидации: проверка на источнике до транспорта и поствалидация на приёмнике. По факту это SQL‑запрос, успешная отработка которого сигнализирует о готовности данных. Механизм гибко настраивается: количество проверок, интервал между ними и дедлайн (время, после которого проверки теряют смысл – если не укладываемся в SLA, данные забираются «как есть»). 

Kafka вместо КХД для NRT‑сигналов

Для обеспечения новых SLA перешли от КХД к получению данных напрямую из корпоративной шины данных на базе Kafka. Такой подход позволяет получать данные в NRT‑режиме, но потребовал дополнительного слоя подготовки данных на нашей стороне. 

Единая инженерная культура и релизный процесс 

Мы привели в порядок кодовую базу. Весь код проекта консолидирован в одном репозитории и приведён к единой архитектуре. Внедрено UAT‑тестирование. Типичный путь новой фичи теперь выглядит так: DEV → UAT → PROD. Также внедрена релизная система – два релиза в неделю, автотесты для основного расчёта копилок и автоматическое код‑ревью с использованием Copilot – для избавления от рутины и повышения качества новых разработок. 

Spark + k8s вместо managed‑решений

Для повышения кроссплатформенности мы отказались от различных managed‑решений и перевели конвейер на расчёт в Spark + k8s. Ключевое – мы разделили compute и storage: теперь не привязаны к конкретной инфраструктуре и можем легко масштабировать вычисления. А когда придёт время переезжать на новую инфраструктуру, переезд займёт не месяцы, а дни или недели.

Сервисы, оставшиеся в тени

Помимо основного пайплайна мы развернули инфраструктурные сервисы, которые работают тихо, но без них никуда. Централизованные системы мониторинга и логирования позволяют следить за состоянием кластера и процессов в режиме 24/7. HashiCorp Vault и SOPS обеспечивают надёжное хранение секретов и учётных данных. Harbor выступает хранилищем Docker‑образов, а жизненный цикл контейнеров управляется через GitLab CI/CD. 

Алертинг 2.0: подписки и таргетирование 

На завершающей стадии – новая версия алертинга с полным переходом в корпоративный мессенджер Tag (на базе Mattermost). Реализована подписочная система для получения таргетированных сообщений через бота.

Итог индустриальной эпохи. OSA перестал быть «переписанным legacy с фишками» и стал зрелой инженерной платформой с управляемой стоимостью, NRT‑данными, валидацией качества, единой кодовой базой и нормальной эксплуатацией. 

Что дальше?

Одна из самых амбициозных целей – сделать конвейер обработки более «чувствительным» к товарам‑драйверам. Он должен научиться генерировать сигналы несколько раз в день для самых востребованных товарных групп, чтобы быстрее реагировать на изменения ситуации в торговых точках. 

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

Множество тем были затронуты вскользь, о них можно рассказать более подробно, например: 

  1. Реализация транспорта данных. На практике это нетривиальная задача, когда нужно «поженить» КХД на базе Teradata и S3 в Yandex Cloud. Написан свой фреймворк PUMP, так как не нашлось подходящего решения из существующих. 

  2. Всё, что связано с Data Quality, дельтами данных, валидацией. 

  3. Устройство оркестрации пайплайнов, система расчёта по часовым поясам. 

  4. У нас есть своё API – надёжное, экономное. Кто‑то может сказать, что простое, но мы считаем, что главное соответствовать всем требованиям, а не гнаться за ненужным усложнением. 

  5. Расчёты в spark + k8s и всё, что с ними связано. 

  6. Антифрод‑система на ТТ: как мы работаем с некорректной обратной связью и формальными закрытиями заданий. 

  7. Устройство нашего подхода по работе с кодовой базой. 

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

Итог: что такое OSA сегодня

Сегодня OSA – это уже не MVP и не просто переписанный legacy, а промышленная платформа, которая: 

  • ежедневно работает на масштабе сети; 

  • объединяет ML‑разработчиков, дата‑инженеров, MLOps, backend‑разработчиков, аналитиков и смежные команды, а также включает собственный API; 

  • помогает развивать инженерные практики внутри ML&AI Hub; 

  • имеет индекс стабильности 94% по оценке сотрудников магазинов. 

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

Надеюсь, что вам этот опыт будет полезен. Возможно, вы поделитесь своим опытом, укажите на слабые места и посоветуете лучшие решения.

Отдельно хочу сказать спасибо команде OSA – без вас ничего бы не получилось. И тем, кто помогал советами и делился опытом – тоже спасибо.

Автор: Imil_Valiullin

Источник