MAATRIX / Блог / Миф: микросервисы — это правильно, а монолит устарел

Миф: микросервисы — это правильно, а монолит устарел

MAATRIX

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

Откуда взялся миф про «устаревший монолит»

Микросервисную архитектуру популяризировали компании вроде Netflix, Amazon и Uber — с тысячами разработчиков, сотнями команд и нагрузкой, которая физически не помещается на один класс серверов. У них действительно была проблема: единый монолит с сотнями коммитов в день от полусотни команд превращался в узкое горлышко — любой деплой требовал координации, любой баг в одном модуле мог уронить всё приложение, а масштабировать нужно было не всё целиком, а конкретные горячие точки (например, ленту рекомендаций, а не биллинг).

Микросервисы решили именно эту проблему — и решение было настолько наглядно успешным, что превратилось в мем «так делают все серьёзные компании». Дальше сработал классический карго-культ: если Netflix делает микросервисы и Netflix успешен, значит, микросервисы = успех, а монолит = что-то для тех, кто не дорос. Проблема в том, что у Netflix была конкретная боль конкретного масштаба, а у команды из трёх разработчиков с проектом на 500 запросов в минуту — нет. Архитектурное решение, которое сняло чужую проблему, перекочевало в чужой контекст без всякой проверки, актуальна ли там сама проблема.

К этому добавился рынок труда: «опыт с микросервисами» и «Kubernetes» звучат в резюме солиднее, чем «умею писать поддерживаемый Django-монолит». Инженерам выгодно продавливать модную архитектуру — это ценный опыт для резюме, даже если для конкретного продукта она избыточна.

Что микросервисы решают на самом деле — и решают хорошо

Важно не скатываться в противоположную крайность и не объявлять микросервисы вредными сами по себе. У архитектуры есть три реальных, проверяемых на практике преимущества — но все три раскрываются только при определённом масштабе.

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

Независимые команды разработки. Когда над одним продуктом работают 15 команд по 6-8 человек, монолитный репозиторий с общей кодовой базой превращается в постоянный источник конфликтов при слиянии, взаимных блокировок релизов и необходимости координировать деплой у всех разом. Разделение на сервисы с чёткими границами API позволяет команде А деплоить свой сервис хоть по десять раз в день, не спрашивая разрешения у команды Б.

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

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

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Цена, которую вы платите за микросервисы

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

Сетевые вызовы вместо вызовов функций. В монолите вызов order_service.create_order(user, items) — это вызов функции в памяти: наносекунды, гарантированная доставка, понятный стектрейс при ошибке. В микросервисах тот же вызов — это HTTP- или gRPC-запрос по сети: миллисекунды задержки в лучшем случае, возможность таймаута, обрыва соединения, частичного ответа. Сеть ненадёжна по своей природе — пакеты теряются, DNS иногда не резолвится, балансировщик иногда шлёт запрос на под, который уже завершает работу. Каждый такой вызов теперь требует retry-логики, таймаутов, circuit breaker'ов — кода, которого в монолите просто не существовало как класса задач.

Распределённые транзакции. Это, пожалуй, самая недооценённая цена. В монолите с одной базой данных операция «списать деньги со счёта и создать заказ» — это одна ACID-транзакция: либо всё прошло, либо всё откатилось. В микросервисах, где счета и заказы живут в разных базах разных сервисов, такой гарантии физически нет. Приходится городить Saga-паттерн (последовательность локальных транзакций с компенсирующими действиями при откате), event sourcing или паттерн outbox — и это без преувеличения одна из самых сложных тем в распределённых системах, где легко получить забытые компенсирующие транзакции и рассинхронизацию данных, которую разработчики находят только постфактум, разбирая логи после инцидента.

Инфраструктура оркестрации. Десяток сервисов нужно где-то разворачивать, обновлять независимо, следить за их здоровьем, балансировать нагрузку между инстансами. На практике это означает Kubernetes (или как минимум Docker Swarm) с его собственной кривой обучения — манифесты, Helm-чарты, service mesh, network policies, secrets-менеджмент. Кто-то в команде должен уметь администрировать сам кластер — это отдельная профессиональная компетенция, а не побочный навык бэкенд-разработчика.

Распределённая трассировка и мониторинг. Когда пользователь жалуется на медленный ответ, в монолите вы смотрите один лог и один профилировщик. В микросервисах запрос может пройти через восемь сервисов, и чтобы понять, где именно он завис на 3 секунды, нужна система трассировки уровня Jaeger или Tempo с correlation ID, прокинутым через все хопы, плюс агрегация логов из десятков контейнеров в одном месте (условный Loki или ELK-стек). Без этого отладка распределённой системы превращается в археологию.

Отдельно упомянем ещё один эффект, который на бумаге выглядит как преимущество, а на практике часто оборачивается против команды: изоляция сбоев работает только если вы отдельно спроектировали graceful degradation для каждой зависимости. Если этого не сделать, падение одного сервиса без правильных таймаутов и circuit breaker'ов утягивает за собой цепочку вызывающих его сервисов — мы разбирали именно такой случай в статье отвалился один микросервис и утянул пять. Микросервисы дают инструмент для изоляции сбоев, но не дают её автоматически — это ещё один пласт инженерной работы поверх и без того возросшей сложности.

Когда сложность оправдана, а когда — нет

Ключевой вопрос звучит не «микросервисы или монолит», а «какая у меня реальная нагрузка и реальный размер команды». Вот примерные ориентиры (именно ориентиры — у вас может быть иначе в зависимости от специфики продукта):

ПараметрМонолит обычно достаточенСтоит рассматривать микросервисы
Размер команды разработки1-10 человек30+ человек, несколько независимых команд
Частота релизовНесколько раз в неделю силами всей командыРазные части системы деплоятся независимо, десятки раз в день
Профиль нагрузкиРавномерный по всем модулямРезко неравномерный — один модуль в 50 раз горячее других
Требования к изоляции сбоевДостаточно грамотной обработки исключений внутри процессаЮридически или коммерчески критично, чтобы сбой одной подсистемы не касался других
Штат DevOps/SREОдин человек совмещает ролиОтдельная команда платформенной инженерии

Если у вас команда из двух-трёх разработчиков и типичный SaaS с несколькими тысячами пользователей, обработка распределённых транзакций и содержание Kubernetes-кластера — это не «модный правильный подход», это чистые издержки без соразмерной выгоды. Вы тратите время не на фичи для пользователей, а на то, чтобы два ваших же сервиса научились между собой договариваться о консистентности данных.

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

Modular monolith: практичная середина

Показательно, что решение «монолит vs микросервисы» не бинарное. Существует архитектурный паттерн modular monolith (модульный монолит) — единое развёртываемое приложение, но с чёткими внутренними границами между модулями: отдельные пакеты или домены с собственными интерфейсами, минимумом связей между собой и (в продвинутом варианте) отдельными схемами в одной базе данных.

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

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

Практические признаки, что вам стоит начать с монолита

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

  • Команда разработки — 1-5 человек, все и так знают всю кодовую базу.
  • Продукт ещё ищет product-market fit — архитектура, скорее всего, будет меняться вместе с продуктом, и дробить на сервисы то, что через полгода перепишется целиком, — потерянное время.
  • Нет отдельного человека или команды, которая готова администрировать Kubernetes, следить за service mesh и разбираться с сетевыми политиками.
  • Нагрузка укладывается в один-два сервера среднего размера без экзотической балансировки.
  • Команда никогда раньше не работала с распределёнными транзакциями и не горит желанием отлаживать eventual consistency в проде.

Практический пример инфраструктуры для монолита такого масштаба — один выделенный сервер или пара VPS (одна под приложение, вторая под базу и бэкапы) с грамотно настроенным Docker Compose. Это на порядок проще поддерживать силами одного-двух человек, чем кластер из десятка контейнеризированных сервисов с собственной сетью между ними. Если задача — рассчитать, какой конфигурации сервера хватит под ожидаемую нагрузку монолита, это отдельный практический вопрос, который мы разбирали в статье как рассчитать конфигурацию сервера под нагрузку.

Обратные сигналы — в пользу вынесения хотя бы части функциональности в отдельные сервисы:

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

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

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Значит ли это, что микросервисы — плохая архитектура?

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

С какого размера команды имеет смысл думать про микросервисы?

Универсального числа нет, но ориентир — когда у вас несколько независимых команд (обычно от 20-30 разработчиков суммарно), которые регулярно мешают друг другу в общем релизном цикле, а не пара человек, работающих над одной кодовой базой.

Можно ли начать с микросервисов, если я уверен, что проект дорастёт до большого масштаба?

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

Что делать, если монолит уже разросся и стал неповоротливым?

Сначала попробовать рефакторинг в модульный монолит — навести порядок в границах модулей внутри одного приложения. Это часто снимает боль без цены распределённой системы. Выносить в отдельные сервисы стоит только те модули, для которых есть конкретная измеримая причина (нагрузка, независимый релизный цикл, требование изоляции).

Усложняют ли микросервисы разработку локально?

Да, заметно. Вместо того чтобы поднять один процесс и одну базу, разработчику нужно поднять несколько сервисов, их сети, очереди сообщений — это либо Docker Compose с десятком контейнеров, либо неполный локальный стенд с моками недостающих сервисов. Это ещё одна операционная цена, которую редко считают на старте.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →