27 апр. 2025 г.·7 мин чтения

Простые границы сервисов для лучшей доступности и меньшего расползания

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

Простые границы сервисов для лучшей доступности и меньшего расползания

Почему больше сервисов часто означает больше простоев

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

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

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

Расползание платформы замедляет и реакцию на инциденты. Когда пять команд владеют пятью сервисами, первые 20 минут простоя часто уходят на выяснение, кто вообще должен смотреть на проблему. Эта задержка важна. Небольшие проблемы остаются небольшими, когда один человек может быстро проследить путь и исправить его.

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

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

Как выглядит скучная граница сервиса

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

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

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

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

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

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

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

Такая настройка не эффектна. Её легко запускать, легко отлаживать и гораздо проще ей доверять.

Где обычно начинается расползание платформы

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

Обычный триггер — подражание. Стартап видит, как большая компания разделяет auth, users, billing, notifications, search и reporting на отдельные сервисы, и копирует этот шаблон с первого дня. Диаграмма выглядит аккуратно, но команда не имеет того же трафика, состава или операционных потребностей.

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

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

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

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

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

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

Если команда не может объяснить, кто владеет слоем, зачем он существует и что ломается при его отказе, этот слой часто и есть начало расползания.

Как более простые части повышают доступность

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

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

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

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

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

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

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

Как шаг за шагом упростить загруженный стек

Снизить расходы на облако безопасно
Проще архитектура часто сокращает траты в облаке и облегчает эксплуатацию.

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

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

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

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

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

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

Пример: поток регистрации стартапа с лишними хопами

Типичная ошибка стартапа — заставить регистрацию делать слишком много до того, как пользователь получит аккаунт. Команда хочет сразу подготовить полноценное рабочее пространство, поэтому форма запускает длинную цепочку вызовов в момент, когда кто‑то нажимает «Создать аккаунт».

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

Это красиво на диаграмме. В продакшне — хрупко.

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

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

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

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

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

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

Вот как простые границы сервисов выглядят на практике: один путь для создания аккаунта и отдельные пути для всего остального.

Ошибки, которые усложняют локализацию простоев

Урезать лишние хопы сервисов
Работайте с Oleg, чтобы сократить пути запросов и упростить локализацию простоев.

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

Одна распространённая ошибка — разделять сервисы по орг‑штату, а не по пользовательскому потоку. Команда A отвечает за аккаунты, команда B — за профили, команда C — за уведомления, и простое действие дробится, потому что компания разделена на части. Пользователям всё равно на эти границы. Если для пользователя регистрация — одна задача, держите как можно больше её вместе.

Другая ошибка — переводить простую работу в асинхронные сообщения. Если пользователь нажимает «Создать аккаунт» и ожидает результат сейчас, прямой запрос обычно проще для понимания. Очереди помогают, когда работа может подождать, повториться или идти в фоне. Они усложняют трассировку запрос‑ответ, отладку и легко теряют состояние посреди пути.

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

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

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

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

Скучная настройка часто безопаснее. Меньше движущихся частей делает изоляцию сбоев реальной, а не красивой картинкой на слайде.

Быстрая проверка перед добавлением ещё одного сервиса

Настроить бережливые AI-операции
Переходите к AI-first разработке, не наращивая лишние инструменты и сервисы.

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

Начните с нагрузки. Измерьте, что текущий процесс делает в обычный день, во время пика и после неудачного деплоя. Если у него ещё есть запас, новый сервис может только дать ещё одно место для таймаута, очереди или падения.

Затем спросите про владение. Когда новая часть сломается в 2 часа ночи, кто получит пейдж и сможет ли этот человек быстро исправить ситуацию? Если ответ «команда разберётся», это не владение. Это будущий простой.

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

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

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

Простая проверка перед добавлением обычно сводится к пяти ответам. Текущий процесс близок к реальному пределу, а не воображаемому. Одна команда владеет ошибками и быстро их исправляет. Радиус поражения остаётся узким при падении зависимости. Разработчик может быстро запустить и протестировать сервис. Откат занимает минуты, а не вечер.

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

Что делать дальше

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

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

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

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

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

Если ваш стек кажется перегруженным и никто больше не верит диаграмме, внешний аудит может сэкономить время. Временный CTO может посмотреть архитектуру, указать риски для доступности и сказать, какие части действительно добавляют безопасность, а какие — только хопы. Oleg Sotnikov делает такую работу через oleg.is, с фокусом на более простые системы, снижение расходов в облаке и практические AI‑первичные операции для небольших команд.

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

Часто задаваемые вопросы

Стоит ли разделять сервис только потому, что кодовая база растёт?

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

Сколько синхронных вызовов сервисов — это слишком много для одного пользовательского действия?

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

Что должно оставаться внутри основного запроса при регистрации или оплате?

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

Очереди всегда улучшают надёжность?

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

Как выглядит хорошая граница сервиса?

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

Почему простои длятся дольше в расползающейся системе?

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

Должны ли отдельные сервисы использовать одну базу данных?

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

Какой первый шаг, чтобы упростить загромождённый стек?

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

Как понять, стоит ли вообще добавлять новый сервис?

Сначала отметьте простые вещи. Сможет ли одна команда отвечать за сбои в 2 утра? Может ли разработчик запустить сервис локально быстро? Можно ли откатиться за минуты? Если нет, новый сервис скорее добавит боли, чем пользы.

Может ли более простая архитектура одновременно снизить расходы и улучшить доступность?

Да, часто так и бывает. Меньше сервисов — меньше прокси, секретов, шагов деплоя и разрывов в мониторинге. Вы тратите меньше денег и времени на мелкие сбои, пока не смешиваете несвязанные вещи в одно громоздкое приложение.

Как записать границу отказа для каждого сервиса?

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