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

В статье речь пойдёт об этапах развития IT-продукта — от бизнес-идеи до промышленного Cloud-Native-решения, о проблемах и возможных способах их решения. Статья написана на основе интервью с участниками команды проекта MTC Live, а именно Александром Богдановым, руководителем проекта, CTO, и Сергеем Праводеловым, руководителем группы разработки, архитектором решения.

Предыстория

MVP Web-витрины для продажи билетов был запущен в 2020 году. Продукт был задуман как билетный агрегатор и витрина для продажи билетов с cashback. Он представлял собой отдельную витрину, где бизнес развивал идеи и стратегии по продаже билетов на развлекательные мероприятия в рамках развития Цифровой экосистемы компании МТС.

Несколько позже, в конце 2020 года, был запущен MVP мобильного приложения МТС Live (APP), которое развивалось как отдельный проект. Оно повторяло функции Web-витрины, но ещё и привносило дополнительные персональные возможности, специфичные для мобильных платформ, такие как: AR-сцены (дополненная реальность), аудиогиды, дополнительное содержание, сопровождение пользователя на площадках событий, услуги заказа еды и напитков на площадках событий.

Если говорить о статусе продукта, то в период с конца 2020 года по конец 2022 года это был рабочий прототип, собранный в стиле гибкой (agile) разработки — простой проверки гипотезы, что это вообще нужно бизнесу, с минимальными затратами на производство. Становление продукта происходило в период пандемии COVID-19.

Запуск Прототипа. Авария

Команда разработки Прототипа выбрала вариант создания относительно дешёвого монолитного решения для MVP (Minimal Viable Product). Это обеспечило быстрый выход на рынок для проверки гипотез, однако и имело последствия в виде сложности развития бизнес-идей по скорости добавления новых функций и экономичного масштабирования системы под высокие нагрузки.

Монолит Прототипа представлял из себя минимальные конфигурации кластеров PostgreSQL, Redis, RabbitMQ, Kubernetes, набора контейнеров служб BFF API (прямые запросы к РСУБД), размещённых в кластере Kubernetes с горизонтальным автоматическим масштабированием, контейнер Web-приложения (React, монолитный Front-End (SSR)), размещённый в кластере Kubernetes с горизонтальным автоматическим масштабированием. Прототип был работоспособен на низких (порядка 25 RPS) нагрузках, и бизнесу показалось приемлемым начать активности по привлечению аудитории, без учёта возможного всплеска нагрузки. Команда маркетинга и продвижения готовила новые акции, занималась рекламными стратегиями. Наблюдался медленный рост аудитории, с которой Прототип справлялся. На относительно низкую пользовательскую активность влияли ограничения, связанные с пандемией COVID-19.

Настоящее стресс-испытание Прототип получил при размещении прямой ссылки на портал на странице регистрации доступа к Wi-Fi в московском метро в апреле 2021 года. Эта акция вызвала быстрый сильный всплеск пользовательской активности на Web-портале. Системы мониторинга фиксировали до 8 000 RPS в пике на сетевых интерфейсах, из которых обрабатывалось только 200-250 RPS с сильной задержкой, остальные запросы просто не обрабатывались из-за нехватки ресурсов (с ошибкой типа Out-of-Memory на процессе сервиса API) или по тайм-ауту (прямые тяжёлые запросы к РСУБД с Web API). Техническая инфраструктура продукта (по конфигурации) оказалась не готова к таким скачкам нагрузки и не смогла оперативно масштабировать используемые ресурсы для параллельной обработки запросов. Автоматическое масштабирование контейнеров API просто привело к увеличению количества запросов на РСУБД и его деградации.

Полный отказ обслуживания был получен на 1 800 RPS из-за прекращения обработки запросов на кластере РСУБД PostgreSQL (т. к. 3-НФ без материализованных представлений может привести к тому, что в какой-то момент закончится место на дисках для временного хранения материализаций JOIN-запросов). Свою роль сыграла и неудачная физическая модель данных, которая к тому же была результатом стихийной интеграции изменений без общего проекта архитектуры данных.

Agile, что тут сказать. Agile — это когда инженер команды эксплуатации проявляет всю гибкость, чтобы одной рукой писать со скоростью звука команды в консоли умирающего сервера, пытаясь не уронить разрывающийся от звонков менеджеров продукта телефон. Не то чтобы автор был против методик гибкой разработки, но да, автор не любит возиться с собранными на коленке agile-прототипами, с заявленными как на production-ready, и нулевой сопровождающей документацией. 

Да, был кеш-по-запросу (Redis), но без использования паттерна распределённой блокировки, и ситуацию он не спасал — подавляющее количество запросов пользователей всё так же проходило до РСУБД. Параллельная проверка наличия кеш-данных приводила к одному и тому же запросу в РСУБД и множественной перезаписи ключа кеш одинаковыми данными.

Бизнес встал на несколько часов, акция провалилась, tNPS начал проседать, получены финансовые и репутационные потери.

Осмысление и реорганизация

После аварии стало понятно, что продукт не готов к тому использованию, которого требует бизнес. Оперативно были собраны представители от бизнеса и технической группы для разработки стратегии выхода из кризиса. Усугубляло ситуацию то, что к весне 2021-го года полностью разошлась техническая команда разработки Прототипа. Что на входе:

  • Прототип-монолит, который не справился с первой же нагрузкой

  • утрачена экспертиза разработки ввиду потери команды Прототипа

  • Прототип, в «лучших» традициях agile, адекватной документации не имеет

  • бизнес заблокирован в развитии своих программ

Силами технических специалистов соседней рабочей группы удалось стабилизировать Прототип под нагрузкой ~1 800 RPS, что бизнес счёл удовлетворительным. Далее начался период компромиссов между бизнесом и техническими специалистами. Так, бизнес всё ещё не мог уверенно проводить масштабные акции, опасаясь повторения инцидента с отказом обслуживания, но и прекращать действия по продвижению было нельзя. Основная сложность была в том, что систему уже запустили и она была включена в некоторую бизнес-стратегию. Удалось прийти к договорённостям о координации бизнеса с технической командой при проведении рекламных кампаний и зафиксировать порядок разработки бизнес-задач и задач по приведению Прототипа к продукту в режиме штатной промышленной эксплуатации (тех. долг) на основе принятого SLA.

Постоянная оперативная связь с представителями бизнеса давала результаты. Так, начали вырисовываться SLA/SLO, отмечены наиболее критичные для бизнеса функции, также бизнес начал учитывать реальные технические возможности в своих стратегиях, опираясь на консультации технических специалистов. Проекту нужна была новая модель управления, которая должна была вывести продукт из производственного хаоса. Продукт получил нового Технического лидера, руководителя, который оперативно начал сбор новой команды и переработку (точнее сказать, разработку с нуля) процессов разработки IT-решения, взаимодействия с бизнес-заказчиком. Сосредоточившись на вопросах проектного управления, CTO делегировал решение технических проблем опытным разработчикам и команде эксплуатации. Такое решение себя впоследствии оправдало.

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

Сильно сказывалось отсутствие автоматических тестов, так как при разработке Прототипа разработчики не тратили на них время, руководствуясь желанием бизнеса как можно быстрее вывести продукт на рынок. В какой-то момент время вывода бизнес-функций в промышленную эксплуатацию (Time-to-Market) выросло до нескольких месяцев, что уже не устраивало. Продукту потребовался новый опытный Владелец (CPO — Chief Product Owner), обладающий компетенциями как в области технической стороны разработки IT-решений, так и в области продуктового управления. В 2022-м году Продукт его получил. Соответственно, выстроился мост между технической командой и бизнес-командой, так как CPO, прекрасно понимая потребности бизнеса, мог доносить бизнес-ценности до команды разработки на «родном» для них языке и делать адекватные выводы о текущем техническом состоянии продукта без переводчиков-посредников.

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

Решения

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

Организация команды и организация процессов разработки

Приступая к работе, мы уже понимали, что и как нужно сделать, во всяком случае в отношении главной части задачи. Был разработан плейбук для команд, при помощи которого работу удалось стандартизировать и организовать быстрый онбординг новых разработчиков. Сейчас, к примеру, новый член команды всего за неделю способен погрузиться в работу, причём погрузиться вполне осмысленно — осознавая масштаб задач всей команды и свои собственные задачи в частности.

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

Наши команды состоят из крепких мидлов. Если использовать стандартную схему, согласно которой все программисты делятся на 9 уровней — от Junior_1 до Senior_3 — то медианный уровень специалистов в компании будет Middle_2. Джуны тоже могут стать частью команды, но в том случае, если мы уверены в возможности «прокачки» такого специалиста до Middle_1, причём в сжатые сроки.

Работаем мы по схеме квартального планирования — новой системе, которую обкатываем в МТС. К слову, работа над сервисом билетов интересна и специфична, потому что рынок постоянно претерпевает изменения и нужно быстро адаптироваться. Также есть нетривиальные задачи за счёт того, что существует множество различных разновидностей мероприятий, и у каждого своя специфика. В целом работа в этой сфере может быть интересна с точки зрения профессионального роста как джунам, так и состоявшимся специалистам.

Техническое решение

Как описывалось ранее, мы оказались в ситуации, когда у нас имелось два продукта:

  • МТС Live Web — решение для Web (Web-портал)

  • МТС Live APP — решение для мобильных платформ (Android и iOS)

Каждое решение имело свой Frontend и Backend (которые повторяли функции друг друга в большей части) и собственные команды разработки.

Если решение МТС Live для мобильных платформ изначально разрабатывалось с нуля, причём с чётким представлением у владельца продукта о том, чего он хочет от мобильного приложения, в результате чего серверная часть решения для мобильных платформ изначально была выполнена достаточно близко к принципам 12-факторных приложений. То о Web-решении такого сказать было нельзя. Было предложено объединение решений с моделью: МТС Live Платформа, МТС Live Web, МТС Live APP. Причём многие успешные наработки по серверной части были взяты именно у МТС Live APP.

Привести решение вида:

к виду:

МТС Live Платформа

МТС Live Платформа взяла на себя решение задач администрации данных (ETL, консолидация данных, построение витрин), интеграции с партнёрскими системами и Цифровой экосистемой МТС, предоставления системного API для клиентских приложений.

Разрабатывается на основе принципов 12-факторных приложений. Здесь установлены достаточно высокие требования к опыту и уровню подготовки разработчика (не ниже старшего):

  • требуется уверенное владение инструментарием из стека

  • навыки работы с IaC, архитектурными паттернами

  • отладка нетривиальных ошибок (в том числе на стороннем ПО)

  • соблюдение стиля кода

Требования такого уровня выставляются потому, что решается практически весь спектр задач построения распределённых систем.

Если говорить о технологическом стеке, то выбран достаточно стандартный для подобных решений инструментарий: PostgreSQL (CDC Debezium), Kafka, Redis, Cassandra, Elasticsearch, Consul/Vault, Kubernetes/Docker, .NET 7+.

*Брокер RabbitMQ был заменён на Kafka, так как последний больше подходит для работы с потоками системных событий (соблюдение порядка) и имеет большую производительность по скорости записи событий в журнал (с кратковременным хранением), что для решения критически важно.
*Брокер RabbitMQ был заменён на Kafka, так как последний больше подходит для работы с потоками системных событий (соблюдение порядка) и имеет большую производительность по скорости записи событий в журнал (с кратковременным хранением), что для решения критически важно.

МТС Live Web

Мы оставили Web-приложение отдельным решением в рамках общей предметной области, а функции администрации данных перешли в зону ответственности Платформы (мастер-данные теперь предоставляет Платформа). Разработчики Web-решения постепенно уходят от SOA-решения, декомпозируя legacy-службы, которые не удовлетворяют критериям 12-факторных приложений, и заменяя «тяжёлые» сервисы на более простые микрослужбы. Разработчики могут дополнительно использовать любые (одобренные техническим комитетом) доступные решения DBaaS для формирования собственных моделей представления данных с тем ограничением, что должна сохраняться связь с корневой сущностью, данные по которой были получены через API данных МТС Live Платформы. Добавление новых бизнес-функций, специфичных для МТС Live Web, происходит за счёт модификации компонентов UI/Frontend и создания новых BFF-микросервисов, использующих готовый API Платформы.  

МТС Live (APP)

Разбор продукта, как и Web-решения, — достаточно объёмный материал, который не поместится в общую обзорную статью. Поэтому тут тоже будем кратки: IT-решение изначально было разработано на принципах Cloud Native, а внутренняя архитектура этого IT-решения остаётся за рамками этой статьи. Она неинтересна без раскрытия информации, защищённой NDA. Серверная сторона решения разработана по тем же архитектурным принципам, что и МТС Live Платформа, за тем исключением, что по мастер-данным опирается строго на API платформы. Решению позволяется создавать и хранить представления данных, полученных от API платформы в виде подходящего для бизнес-задачи агрегата.

Стек частично повторяет платформу: PostgreSQL (данные сервисов, специфичных только для мобильных приложений), Redis, Kubernetes (для размещения контейнеров служб BFF), NET 7+, GraphQL.

Возможности архитектуры

Вертикальное масштабирование: доступно размещение всех компонентов в нескольких ЦОД, разделённых географически. Увеличивается отказоустойчивость и доступность сервиса, а за счёт stateless-процессов выполнение может быть распределено по нескольким центрам обработки данных, где развёрнуто решение.

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

Высокие нагрузки: операции чтения (которых в системе подавляющее большинство) выполняются специально оптимизированными под них СУБД (Redis, Cassandra) и принимают на себя нагрузку по запросам данных. Скорость выполнения одного запроса чтения уже сопоставима с сетевой задержкой. Все операции записи выполняются асинхронно, через брокер системных команд (Kafka), что даёт возможность распределять нагрузку на конкретные микросервисы-процессоры при возросших нагрузках и использовать политики повторов команд (принцип идемпотентности и политика повторных запросов) при системных сбоях.

Независимая разработка бизнес-решений на Платформе: Web-приложение и Мобильные приложения могут развивать свои, специфичные для платформ, B2C-услуги, при этом работая с общими данными домена и общими API Платформами. Так, например, заказ на баре площадки события доступен только в мобильном приложении и создан как отдельный сервис, но при этом все основные данные по площадке, анонсу и событию являются общими с Web-приложениями. Разработчики B2C-решений сосредоточены именно на решении бизнес-задач, а все экосистемные интеграции инкапсулированы в Платформу и доступны через её API, разработанное под DSL (Domain-Specific Language) бизнеса по продаже билетов.

Интеграция в экосистему: платформа интегрируется с экосистемой компании с соблюдением общего стандарта.

Имплементация принципов 12-факторных приложений

  1. Кодовая база под управлением VCS. Используется корпоративный GitLab по принципу — 1 микросервис на 1 репозиторий. Репозитории объединены в один проект GitLab, структурированный по каталогам.

  2. Чёткая декларация и разделение зависимостей. Используются манифесты сборки. Все зависимости проходят экспертизу ИБ (Информационной Безопасности) и хранятся в закрытых корпоративных репозиториях. Исходные тексты сторонних пакетов и библиотек также сохраняются (по возможности) в корпоративной системе управления версиями.

  3. Разделение конфигураций сред исполнения. Конфигурация каждого приложения-микросервиса отделена от его кода. Используются Consul и Vault для хранения конфигураций и защиты ключей. Конфигурация определяется средой развёртывания и выдаётся в момент активации контейнера (consul-connect-injector).

  4. Утилитарные службы — это подключаемые ресурсы. Поддерживается принцип DBaaS (БД как сервис) и SaaS (ПО как сервис). Подключаемые ресурсы типа DBaaS размещаются в корпоративном облаке с разделением доступа на чтение и запись (CQRS).

  5. Разделение этапов сборки, выпуска и выполнения. Как было описано в п. 1, используется инфраструктура GitLab для описания и выполнения конвейера сборки и поставки. Присвоение меток версий регламентировано корпоративным стандартом. Сборка и размещение образов для развёртывания управляются PaaS-решением в корпоративном облаке.

  6. Приложения-микросервисы — отдельные stateless-процессы. Приложения (микросервисы API или микросервисы асинхронного процессора сообщений) исполняются как отдельные процессы без сохранения внутреннего состояния между запросами.

  7. Привязка сервиса к сетевому порту. Каждый системный сервис публикуется на точке доступа (end-point) с чётким определением порта, который ассоциирован с определённым протоколом. Так, например, некоторые наши публичные API поддерживают несколько протоколов взаимодействия для сохранения обратной совместимости для клиентов, за каждым системным протоколом закреплён свой порт.

  8. Параллельная обработка запросов. Поддерживается параллельная обработка запросов несколькими экземплярами микросервиса. В некоторых случаях при необходимости используется паттерн распределённой блокировки. Соблюдается принцип идемпотентности запросов на изменение состояния системы (запись данных).

  9. Утилизация. Асинхронные процессоры (микросервисы, обрабатывающие команды на изменение данных) разрабатываются таким образом, что при остановке контейнера гарантированно штатно завершаются текущая транзакция и процесс сервиса. При критическом сбое обработки запроса система получает сигнал о необходимости выполнить компенсирующие транзакции для запроса изменения данных (используется correlation ID запроса). В этом случае отмеченная бизнес-транзакция переводится в статус ошибки. Попыток восстановить состояние сбойного процесса система не предпринимает. Такой подход выбран осознанно.

  10. Промышленная среда и актуальность промежуточных сред развёртывания. Наши окружения разделены на Промышленную (Production), Приёмочную (UAT), Промежуточную (Staging) среды. Окружение UAT — это копия промышленной среды (с искажением данных). Здесь специалистами анализа качества и демонстрации новых функций бизнес-заказчику перед выпуском проводится приёмочное тестирование. Также проходят полные автоматические тесты перед выпуском (gated-builds) на промышленную среду. Развёртывание на промышленную среду проводит один из старших инженеров вручную, согласно плану релиза и при поддержке инженеров из команды эксплуатации. Мы работаем над автоматической пакетной поставкой на промышленную среду, но пока есть достаточно рисков и нюансов, поэтому без присмотра инженера не обойтись. Окружение Staging — допускается сильное отличие от Production и UAT. Здесь проходят проверки интеграций и предварительные тесты экспериментальных функций. Служит разработчикам для проверки своих приложений на тестовых контурах корпоративных служб. Версии, прошедшие автоматическую проверку и тесты разработчика, могут быть перенесены на UAT для последующего выпуска. Окружения Development как такового нет в общем доступе. Каждый инженер разворачивает локально мини-версию необходимых подключаемых ресурсов и проводит smoke-тесты перед интеграцией на окружение Staging. Возможно, в будущем мы сделаем быстрое резервирование виртуальных машин для каждого разработчика в корпоративном облаке.

  11. Журнал — поток системных событий. Изменения данных в хранилищах системы возможны только через системные команды (паттерн CQRS). Изменением данных занимаются асинхронные процессоры, которые получают команды из журнала брокера сообщений Kafka в гарантированном хронологическом порядке. Таким образом, имеется кратковременное хранилище вектора состояния системы. Кроме этого, каждый сервис подключён к корпоративной системе сбора данных для мониторинга, куда публикуется журнал событий процесса каждого микросервиса, что обеспечивает долгосрочное хранение журналов системных событий для анализа и расследования инцидентов.

  12. Изменения инфраструктуры и миграции. Используется подход «инфраструктура как код (IaC)». Каждый сервис системы имеет декларацию зависимостей собственной инфраструктуры и поддерживается вместе с кодом приложения, находясь с ним в одном репозитории исходного кода. В зависимости также входят пакеты скриптов миграций данных и схем данных (версия схемы хранилища). Здесь используем SaaS-решение на основе Liquibase.

Оценка готовности и целесообразность перехода к Cloud Native

Если говорить о разработке с нуля и поддержке Cloud-Native-решений, то нужно отметить факт — разработка таких решений достаточно дорогая. Если имеется надёжный облачный поставщик с услугами администрации облака, то стоимость поддержки облачного решения будет определяться стоимостью услуг поставщика. Что же касается стоимости разработки, то тут всё сложнее.

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

  • Есть облачная инфраструктура. Крупные компании могут позволить себе разрабатывать собственные PaaS-решения для контроля собственного облака, но если своего решения нет, то должен быть доступен надёжный поставщик облачной инфраструктуры с хорошим показателем SLA, использование услуг которого не нарушает законодательства страны, где будут использоваться ваши услуги. Смена поставщика облачной инфраструктуры, как правило, — очень затратная задача, связанная с финансовыми потерями и рисками временной остановки сервиса. Поэтому выбирать поставщика нужно очень тщательно, продумывая бюджет и уровень поддержки.

  • Есть опытная команда эксплуатации. У вас должна быть опытная команда инженеров по эксплуатации (или вы имеете бюджет на оплату подобного сервиса на стороне поставщика облачной инфраструктуры).

  • Есть опытная команда разработки. У вас должна быть инженерная команда, состоящая из опытного архитектора решения с практическим опытом проектирования распределённых систем по принципу Cloud Native, а также опытных разработчиков, имеющих практический опыт создания распределённых систем с параллельной и асинхронной обработкой данных. Начинать разработку в стиле Fast-n-Dirty, имея в распоряжении десяток Junior из расчёта, что потом удастся пригласить ведущего разработчика, который совершит чудо и всё исправит, — это выкидывание денег. Никакой «играющий тренер», даже если он согласится с вами поработать, ничего не исправит. Так или иначе вы заплатите за полную разработку плюсом к тому, что уже было потрачено до этого. Поэтому минимальная команда из опытных инженеров и архитектора должна быть собрана сначала.

Что в итоге?

Если подводить итог трёх лет разработки продукта МТС Live, то можно сказать, что нам удалось:

  • справиться с производственным хаосом

  • пересобрать команду разработки

  • реорганизовать монолитное MVP-решение в систему Cloud-Native-приложений и сервисов на новой архитектуре

  • обеспечить высокую доступность сервиса в процессе реорганизации legacy-системы

  • выйти на высокий показатель скорости вывода бизнес-функций в эксплуатацию

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

Комментарии (1)


  1. akmelnikov
    14.08.2023 05:43

    Годная статья получилась. Спасибо! Плюс в карму)