Александр Чернов

Продакт лид, Много Лосося

9 мая 2023

#Разработка | Development 3

Монолит или микросервисы в стартапе: наш опыт 3 лет разработки того и другого

image

Всем привет! Меня зовут Александр Чернов, я директор по IT и продукту в Много Лосося, и в этой статье я расскажу о взгляде нашей команды на два противоборствующих подхода - микросервисы и монолит.

Статья основана на опыте разработки нескольких ИТ систем в Много Лосося. Я не буду вдаваться в теорию и определения. Что такое микросервисная и монолитная архитектуры уже описано много раз. Я же рассказываю лишь о нашем опыте использования обоих подходов на примере двух систем - клиентского мобильного приложения и CRM системы, - и о достоинствах и недостатках этих подходов для нас как стартапа на разных стадиях развития.

Обе системы имеют сравнимый RPS. По сути они хранят и обрабатывают пересекающиеся данные. 

Небольшой экскурс, кто мы. Много Лосося - сеть из более чем 300 темных кухонь и кафе поинтов в нескольких городах России. Мы готовим и доставляем вкусные суши и роллы, а заказ у нас можно сделать через наше мобильное приложение, агрегаторов и в магазинах Перекресток. С весны 2021 года мы часть группы компаний X5 Retail Group.

Итак,

Мобильное приложение

iOS и Android приложения для заказа еды. Основная задача приложения - помогать пользователю удобно и быстро делать заказ. Этот процесс в себя включает отображение каталога, динамическое скрытие закончившихся блюд, прогноз времени доставки, возможность безопасно привязать карту и платить в одно касание, своевременное обновление статуса заказа, программу лояльности.

Для контекста:

  • MAU февраля 2023 - 127k
  • Средний DAU февраля 2023 - 23k
  • 40% всех заказов мы получаем через наше мобильное приложение. Остальное - с агрегаторов.
image

На бэкенде это монолит на php, а также

  • Postgresql
  • Redis для кэширования каталога, зон доставки и т.д.
  • SQS для заданий планировщика, вебхуков внешних систем.

CRM

  • Получает и хранит все заказы (как из приложения, так и из агрегаторов);
  • Получает от POS системы обновления статуса заказов и изменения остатков блюд;
  • Получает и хранит от приложения и агрегаторов оценки на заказы. У отзыва есть флоу, по которому его двигает наш оператор поддержки в админке;
  • Рассылает уведомления в телеграмм, email и POS систему;
  • Прогнозирует время доставки, которое отображается клиентам в мобильном приложении;
  • Рассчитывает аналитические метрики в реальном времени (выручка, средний чек, время в пути, % заказов с опозданием и т.д.). Более детальная аналитика с обогащением данных из других систем у нас организована в хранилище данных, поверх которого формируются отчеты в PowerBI, но актуализация данных в хранилище данных происходит ночью, то есть аналитика в PowerBI всегда отстает (до суток), в то время как микросервис аналитики служит источником хоть и не такой глубокой, но отчетности в реальном временем (с задержкой от реального события в несколько секунд);
  • Пользователи (сотрудники колл центра, директора ресторанов, упаковщики, сотрудники офиса) взаимодействуют с системой через админку на React.
image

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

Общение между сервисами по большей части происходит с помощью брокера сообщений RabbitMQ. Некоторые из сервисов, получая сообщения из RabbitMQ, сохраняют в своей БД частичную копию данных из других сервисов, если для реализации конкретного функционала внутри сервиса эти данные им нужны. Ведь иначе пришлось бы обращаться за нужными данными в другой сервис по HTTP. Тут приведено 2 примера того, к каким проблемам это может привести.

Например, чтобы показать директору ресторана текущую выручку, сервис аналитики должен узнавать о новых заказах. Сервис заказов инициирует сообщение о новом заказе в RabbitMQ. Все желающие, в том числе сервис аналитики, слушают это сообщение. Обработчик в сервисе аналитики, потребив сообщение о новом заказе из очереди, обновит текущую выручку ресторана, добавив к ней сумму оплаты нового заказа.

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

Архитектура

Если не вдаваться в детали реализации, эти две системы в нашей архитектуре выглядят так:

imageчтобы увеличить изображение, откройте его в новой вкладке

Сравнение

image

Масштабирование серверов

Сервисы нашей CRM управляются kubernetes-ом, который добавляет инстансы и ноды в случае повышения нагрузки.

Наше мобильное приложение не в kubernetes, так что масштабирование у нас ручное. У нас есть несколько выключенных ВМ в Compute Cloud. Все они подключены к Application Load Balancer. В момент нагрузки мы включаем ВМ и балансировщик начинает направлять часть трафика на нее. Очевидно, что отсутствие автоматического масштабирования в данном случае не является следствием монолитной архитектуры.

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

  • в большинстве случаев по ресурсам мы упираемся в БД;
  • на нашем масштабе (единицы ВМ с инстансами приложения) экономии от эффективного масштабирования только части сервисов в сравнении с масштабированием монолитного приложения целиком практически нет;
  • overhead микросервисов (overhead самих приложений, отдельных БД под каждый сервис, наличие sevice discovery) нам сейчас стоит дороже, чем масштабирование серверов под молонит.

Масштабирование команды

Сейчас мы уже замечаем некоторые трудности с разделением задач между разработчиками в монолите.

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

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

В случае с микросервисами поделиться на доменные области разработчикам гораздо проще. Можно не знать ничего о сервисе каталога кроме его API (будь то REST или очереди) и успешно развивать сервис заказов, который взаимодействует с сервисом каталога посредствам этого API. Также и разработчик сервиса каталога должен гарантировать команде только одно - контракт API. А уж о том, кто и зачем взаимодействует с его сервисом, а тем более как реализует это взаимодействие (на какой стэке), ему можно не переживать.

Напомню, что мы говорим о стартапе. К моменту, когда масштабирование приложения целиком станет слишком дорогим с точки зрения стоимости серверов, а разработчиков станет так много, что делиться на доменные команды становится необходимо, вероятно, будут ресурсы, чтобы распилить монолит на микросервисы. Что называется problem nice to have.

Сложность разработки

На мой взгляд минусом в копилку микросервисов является необходимость разработки сложной (в сравнении с монолитом) архитектуры взаимодействия компонентов систем: 

  • Очереди сообщений. Они, конечно, могут использоваться и в монолите, но в микросервисах очереди - это ядро.
  • Поддержание консистентности баз данных разных сервисов. 100 сервисов - 100 БД. Часть из них хранят данных из других. Иногда часть из них недоступна. Точно ли сервис аналитики знает обо всех отмененных заказах из сервиса заказов? Подобные вопросы мы обсуждаем нередко.
  • Распределенные транзакции. При заказе нужно - Создать заказ - Уменьшить остатки на складе

В монолите все это выполняется в рамках одной ACID транзакции. Практически исключены ситуации, что заказ создан, а остаток на складе не уменьшен. Атомарность транзакции - либо транзакция выполнится целиком, либо не будет выполнена ни одна из ее частей.

В микросервисах это превращается в отдельную сложную задачу - распределенную транзакцию. Правда, есть фреймворки, помогающие ее решать. Например, для сервисов на C# мы используем Mass Transit.

  • Service discovery
  • Circuit breakers. Мы их так и не реализовали.

Отказоустойчивость

Повышенная отказоустойчивость микросервисов - это не миф. Мы на своем опыте не раз сталкивались с ситуацией, когда из-за проблемы в одной части монолитного приложения недоступен весь backend и пользователи не могут сделать вообще ничего. В то же время подобные проблемы в микросервисной CRM, то есть когда, например, недоступен один из сервисов, приводят к временному сокращению функционала, но не полному его отсутствию. Конечно, важность с точки зрения функционала у каждого сервиса разная. Падение сервиса заказов и падение сервиса аналитики - несравнимые с точки зрения ущерба для бизнеса проблемы. Однако, падение сервиса заказов это все-таки лучше, чем падение всего.

Как я уже написал выше, многие решения для повышения отказоустойчивости микросервисной CRM мы еще не реализовали (например, circuit breakers), но уже сейчас не наблюдаем более высокую стабильность микросервисной системы в сравнении с монолитом.

Итого

Из нашего опыта разработки двух описанных систем мы сделали следующие выводы:

  • Сотни тысяч заказов – мало (но уже близко) для оправданности микросервисов;
  • Масштабировать наш монолит все еще дешевле, чем overhead микросервисов;
  • 5 разработчиков монолита – не так много для проблем с совместной разработкой;
  • Пока не распиливаем мобильное приложение, но часть нового функционала делаем в отдельных сервисах.

Монолит на старте дает бесценную скорость разработки и, по нашему опыту, eCommerce продукт может жить и развиваться на монолите до довольно внушительных объемов. Важная оговорка, что наш опыт это исключительно ecommerce, а именно доставка еды. Можно ли позволить себе начинать продукта в виде монолита, зависит от сути вашего продукта. Если базовый функционал вашего продукта подразумевает highload (маркетплейсы, коннекторы, облачные CRM\ERP и т.д.), ограничения монолита дадут знать о себе очень скоро.

Ссылки:

Микросервисы vs. Монолит

HTTP based Microservices is a bad idea

Присоединяйся к чату комьюнити

Будь в курсе всех новостей, обращайся за помощью к коллегам и находи единомышленников!

Заходи в чат