Всем привет, меня зовут Сергей Прощаев, и в этой статье расскажу про то, как системы, которые честно выполняют все заявленные функции, разваливаются под первой реальной нагрузкой — и почему корень проблемы чаще стоит искать не в коде, а в требованиях, которые никто не удосужился записать цифрами.
Я Tech Lead и руководитель направления Java | Kotlin разработки в FinTech & E‑commerce и преподаю на курсах разработки и архитектуры в OTUS.
За последние годы я разбирал десятки инцидентов, и у большинства из них один и тот же корень: архитектуру проектировали вокруг бизнес‑функций, а производительность, отказоустойчивость, безопасность и стоимость оставили «на потом». Потом наступает. Обычно ночью.

Знакомая история?
Система прошла приёмку. Все user story закрыты, демо прошло гладко, заказчик доволен. Через три недели после запуска маркетинг запускает рассылку — и p99 времени ответа уезжает с 200 мс до 8 секунд. Ещё через месяц выясняется, что счёт за облако вдвое больше запланированного.
А когда заказчик приходит с вопросом «а мы точно переживём распродажу в ноябре?», оказывается, что ответ «да» стоит переписывания половины сервисов: синхронные вызовы, общая база, превратившаяся в узкое место масштабирования, и ни одного числа, с которым можно сверяться.
Знаете, что самое обидное? Ни одна из этих проблем не была неожиданной. Все они были предсказуемы ещё на этапе проектирования — если бы кто‑то задал пять неудобных вопросов про нефункциональные требования (NFR).
Разберём 5 ошибок работы с НФТ, которые я встречаю чаще всего. Для каждой — симптом, причина, цена и исправление. В конце — сводная таблица, чек‑лист и практики, которые сильные команды используют по состоянию на середину 2026 года.
Ошибка 1. НФТ собираются «потом» — когда архитектура уже выбрана
Симптом. На дизайн‑сессии обсуждают сущности, API и экраны. Слово «нагрузка» звучит один раз, в формате «ну, потом посмотрим». Через полгода заказчик уточняет: «нам нужно 99,95% доступности и пик 2000 RPS в чёрную пятницу» — и команда понимает, что монолит с единственной общей БД этого не выдержит.
Почему так делают. Мотивация выглядит разумной: «сначала докажем ценность продукта, оптимизировать будем по мере роста». Проблема в том, что НФТ — это не оптимизация. Это входные данные для выбора архитектуры. Выбор между монолитом и микросервисами, синхронным REST и очередями, одной базой и шардированием — всё это следствия НФТ, а не вкусовщина. Причём между собой НФТ всегда в конфликте: производительность тянет вверх стоимость, доступность добавляет сложности, безопасность съедает задержку — и проектирование по сути является выбором компромисса, который невозможно сделать осознанно, пока требования не названы цифрами.
Цена. Здесь работает жестокая экономика: то, что на дизайн‑сессии стоило часа спора у доски, в проде превращается в миграцию на месяцы. Прежде чем идти дальше, посмотрите на рис. 2 — как растёт цена одного и того же требования в зависимости от того, на каком этапе оно всплыло.

Главная мысль этой схемы: НФТ никуда не исчезают, если их не обсудили. Они просто переезжают вправо по таймлайну — туда, где каждое изменение стоит на порядок дороже и затрагивает уже принятые архитектурные решения.
Как исправить. Ввести правило: дизайн‑сессия не считается завершённой, пока не заполнен короткий NFR‑опросник — нагрузка в пике, допустимая задержка, доступность, RTO/RPO, бюджет на инфраструктуру, требования по данным (персональные? финансовые?). Мой вариант, который я обычно использую: одна страница, 10 вопросов, заполняется вместе с заказчиком. Не знает ответа — пишем гипотезу и дату, когда уточним. Пустых клеток не оставляем.
Ошибка 2. НФТ без цифр: «система должна быть быстрой и надёжной»
Симптом. В документации есть раздел «Нефункциональные требования», и он даже заполнен. Читаем: «система должна работать быстро», «обеспечить высокую доступность», «выдерживать высокие нагрузки». Всё. Ни одного числа.
Почему так делают. Потому что цифры страшно фиксировать: за них потом спросят. А «быстро» — безопасное слово, под которое можно подогнать что угодно. Помню, как однажды на разборе инцидента у нас случился диалог: «Система же должна быть быстрой!» — «Она отвечает за 3 секунды, это быстро?» — «Нет!» — «А где написано, сколько — быстро?» Тишина.
Цена. Требование без числа невозможно ни спроектировать, ни протестировать, ни нарушить. Каждый участник понимает его по‑своему: разработчик считает, что 500 мс — нормально, заказчик ждёт 100 мс, а тестировщик вообще не знает, что проверять. Конфликт гарантирован, просто отложен до продакшена.
Как исправить. Каждое НФТ переписывается в измеримый вид: метрика, условие, порог. Сравните:
Плохо: Система должна быть быстрой. Лучше: p99 времени ответа POST /api/orders ≤ 300 мс при нагрузке 500 RPS на профиле «чёрная пятница». Плохо: Высокая доступность. Лучше: Доступность checkout-флоу ≥ 99,9% в месяц (бюджет ошибок ~43 мин), RTO ≤ 15 мин, RPO ≤ 5 мин. Плохо: Система должна масштабироваться. Лучше: Горизонтальное масштабирование order-service до 10 инстансов без даунтайма; деградация не более +20% к p99 при отказе одной AZ.
Обратите внимание: в формулировках «лучше» всегда есть профиль нагрузки. Число без контекста — это половина требования: 300 мс на 10 RPS и 300 мс на 500 RPS — две разные архитектуры. И ещё: RTO и RPO из второго примера — не просто строчки в документе, дальше они становятся критериями учений по восстановлению. Если восстановление из бэкапа ни разу не прогоняли, ваш RPO — не 5 минут, а «неизвестно сколько».
Ошибка 3. Проектирование от happy path: отказы и деградация «не предусмотрены»
Симптом. Все сценарии в документации начинаются со слов «пользователь успешно...». Что происходит, когда платёжный шлюз отвечает 30 секунд, а не 200 мс, — не описано нигде. В коде это выглядит так: синхронный вызов внешнего сервиса без таймаута прямо в транзакции.
Классика жанра (Java):
// Анти-паттерн: внешний вызов внутри транзакции, без таймаута и fallback @Transactional public Order createOrder(OrderRequest request) { Order order = orderRepository.save(mapToOrder(request)); // Платёжный шлюз "обычно" отвечает за 200 мс. Обычно. PaymentResult result = paymentClient.charge(order); // таймаут по умолчанию: 30 сек order.setStatus(result.isSuccess() ? PAID : FAILED); notificationService.sendEmail(order); // и email туда же, синхронно return order; }
Когда шлюз начинает тормозить, потоки веб‑сервера один за другим повисают на 30 секунд, а с ними — и соединения к базе: save() уже выполнил SQL, поэтому JDBC‑соединение захвачено из пула и не вернётся туда до завершения метода, даже если Hibernate настроен откладывать захват до первого запроса. Пул исчерпывается, и ложится уже не оплата — ложится всё, включая просмотр каталога. Каскадный отказ на ровном месте.
Оговорюсь: сам по себе внешний вызов внутри транзакции — не всегда ошибка. Опасность возникает, когда транзакция удерживает соединение с БД на время длительной сетевой операции — особенно без жёстких таймаутов и ограничения общего времени выполнения. В примере выше сошлись все факторы разом: долгоживущая транзакция, дефолтный таймаут в 30 секунд и ни одного предохранителя.
Почему так делают. На демо и в тестовом окружении внешние сервисы отвечают быстро и всегда. Отказоустойчивость — это требование, которое буквально невидимо, пока всё работает.
Цена. Один медленный внешний сервис укладывает всю систему. Причём в самый нагруженный момент — потому что именно под нагрузкой зависимости начинают деградировать.
Как исправить. Отказ каждой внешней зависимости — это сценарий, который проектируется явно: таймауты (агрессивные, не дефолтные), retry с экспоненциальной задержкой и джиттером — но только для идемпотентных операций и временных ошибок, иначе retry не лечит перегрузку, а усиливает её, — circuit breaker, идемпотентность операций, асинхронность там, где пользователю не нужен мгновенный результат. Отправка email в нашем сценарии не влияет на результат операции пользователя, поэтому её обычно уводят в очередь и фоновый воркер. В Java/Kotlin‑мире это Resilience4j; но подчеркну: библиотека — следствие, сначала в НФТ должен появиться ответ на вопрос «что видит пользователь, когда платёжный шлюз лежит».
Ошибка 4. НФТ проверили один раз (или ни разу) — и дальше на честном слове
Симптом. Нагрузочное тестирование провели перед запуском, полтора года назад. С тех пор — 400 релизов. Кто‑нибудь знает, какой сейчас p99 под пиковым профилем? Никто. Узнаем в ноябре.
Почему так делают. Нагрузочные тесты воспринимаются как разовый проект «на сдачу», а не как часть pipeline. Плюс их неудобно запускать: отдельный стенд, отдельные люди, отдельная эпопея.
Цена. Деградация производительности накапливается маленькими шагами: тут лишний запрос в цикле, там N+1, здесь новый синхронный вызов. Каждый релиз по отдельности невинен. Сумма — инцидент.
Как исправить. Здесь индустрия за последние годы пришла к внятному ответу, и к середине 2026-го он стал распространённой практикой зрелых инженерных команд: НФТ превращаются в автоматические проверки — fitness functions, которые гоняются в CI так же, как юнит‑тесты. Подход из книги Building Evolutionary Architectures перестал быть экзотикой: архитектурные ограничения проверяет ArchUnit, производительность — k6 или Gatling, как минимум на релиз‑кандидатах или по расписанию, если полный прогон стоит дорого. А в проде за теми же цифрами следят SLO и бюджет ошибок.
Маленький пример fitness function на ArchUnit (Java). Правило нарочно узкое — оно выросло из конкретного инцидента в нашей команде; чаще на практике закрепляют слоистость (application не зависит от infrastructure, контроллеры не ходят в репозитории напрямую), но механика та же — архитектурная договорённость перестаёт жить в головах и начинает ронять сборку:
// Fitness function: сервисы с @Transactional не должны напрямую // зависеть от HTTP-клиентов внешних систем @ArchTest static final ArchRule no_external_calls_in_transactions = noClasses().that().areAnnotatedWith(Transactional.class) .should().dependOnClassesThat() .resideInAPackage("..client.external..") .because("внешние вызовы в транзакции исчерпывают пул соединений " + "при деградации зависимости (см. НФТ-7, инцидент INC-2024-113)");
Из личного опыта: когда мы у себя впервые повесили k6-сценарий на релиз‑кандидат, он покраснел на третьем же релизе — новый фильтр в выдаче заказов добавил 40% к p99. Разработчик искренне удивился: локально всё летало.
Разговор занял десять минут, фикс — час. Без этой проверки мы бы узнали о деградации от клиентов, недели через три, и искали бы причину среди десятка релизов. Как и предполагал, сопротивление команды («ещё одна ступень в CI!») испарилось после первого же пойманного случая.
Как это складывается в единый контур — на рис. 3.

Важная оговорка про границы применимости: k6-сценарий в CI — это smoke‑профиль на 5–10 минут, а не полноценная нагрузочная кампания. Он ловит резкие деградации между релизами, но не заменяет квартальные тесты на полном профиле «чёрной пятницы» с прогревом кэшей и реалистичными данными. Нужны оба уровня — путать их опасно: зелёный smoke создаёт ложное чувство защищённости.
Главная мысль схемы: НФТ — не документ, а цикл. Цифры из требований попадают в ADR, из ADR — в автоматические проверки, из продакшена возвращается обратная связь, которая уточняет цифры. Разрыв в любом месте контура — и через полгода требования снова живут отдельно от системы.
Ошибка 5. Стоимость и эксплуатация — не считаются требованиями вообще
Симптом. Архитектура красивая: Kafka, десяток микросервисов, три реплики всего. Функционально — безупречно. Счёт за облако — в 2,5 раза выше, чем бизнес готов платить, а из наблюдаемости — только логи, по которым инцидент разбирают шесть часов. Kafka здесь, замечу, ни в чём не виновата — виновато отсутствие требований, под которые весь этот зоопарк выбирали.
Почему так делают. Стоимость инфраструктуры и удобство эксплуатации традиционно не воспринимаются как НФТ — «это же не про качество системы». Хотя на деле стоимость обработки одного запроса бьёт по бизнесу не слабее, чем медленный API, — это та же unit‑экономика, только со стороны инфраструктуры.
Цена. Мне как‑то попалась система, которую пришлось «удешевлять» через год после запуска: выяснилось, что 60% облачного счёта генерировал сервис аналитики, которым пользовались раз в неделю, — но он был спроектирован под режим реального времени, потому что «так надёжнее». Никто не спросил заказчика, нужен ли ему realtime. Переделка заняла квартал.
Как исправить. Внести в NFR‑опросник два блока: стоимость (бюджет на инфраструктуру в месяц, допустимая цена одной транзакции) и эксплуатацию (время диагностики инцидента, объём телеметрии, on‑call нагрузка). Формулируются они так же, как любые другие НФТ — цифрами:
Плохо: Инфраструктура должна быть недорогой. Лучше: Бюджет облака ≤ 450 тыс. ₽/мес при 1 млн заказов; стоимость обработки одного заказа ≤ 0,45 ₽. Плохо: Систему должно быть удобно поддерживать. Лучше: Время локализации причины инцидента (MTTI) ≤ 30 мин; каждый межсервисный вызов трассируется end-to-end.
FinOps‑практика «стоимость — это архитектурная метрика» сегодня широко применяется зрелыми командами: прогноз облачного счёта считается на этапе ADR, а не по факту в конце месяца.
Цена вопроса: история healthcare.gov
Чтобы понять масштаб последствий, не обязательно фантазировать — достаточно вспомнить запуск healthcare.gov в США в октябре 2013 года. Функционально портал был готов: регистрация, анкеты, подбор страховых планов.
Причин провала было много — интеграции, координация десятков подрядчиков, процессы, — но одной из ключевых стали проблемы с масштабированием и эксплуатационной готовностью: систему тестировали в расчёте на десятки тысяч одновременных пользователей, а в первый же день пришли сотни тысяч. Сайт лёг практически сразу: в первые дни зарегистрироваться смогли единицы, а полноценно работать портал начал только после месяцев экстренной переделки силами специально собранной команды инженеров.
Показательно, что чинили не «баги в функциях» — переделывали именно то, что относится к НФТ: кэширование, работу с базой, мониторинг (его на старте почти не было), процессы выкатки. Классический случай: все функции на месте, ни одно нефункциональное требование не выдержано — и продукт фактически не существует. Могу себе представить, сколько дизайн‑сессий можно было бы оплатить за цену того спасательного марафона.
Сводная таблица
Ошибка |
Признак |
Что проверить |
|---|---|---|
1. НФТ «на потом» |
На дизайн‑сессиях обсуждают только функции |
Есть ли заполненный NFR‑опросник до выбора архитектуры |
2. НФТ без цифр |
В требованиях «быстро», «надёжно», «масштабируемо» |
У каждого НФТ есть метрика, порог и профиль нагрузки |
3. Только happy path |
Внешние вызовы без таймаутов, retry и fallback |
Описан сценарий отказа каждой внешней зависимости |
4. Проверка «один раз» |
Нагрузочное тестирование старше последних N релизов |
Fitness functions и нагрузка в CI, SLO в проде |
5. Стоимость вне требований |
Счёт за облако — сюрприз в конце месяца |
Бюджет и цена транзакции зафиксированы как НФТ |
Какой навык на самом деле проверяет эта группа ошибок
Все пять ошибок — про одно и то же умение: переводить расплывчатые ожидания заказчика в измеримые ограничения до того, как выбраны ключевые архитектурные решения. Это и есть ядро system design как дисциплины. На собеседованиях по проектированию систем, кстати, проверяют ровно это: сильного кандидата от слабого отличает не знание паттернов, а первый вопрос, который он задаёт. Слабый начинает рисовать квадратики. Сильный спрашивает: «Какая нагрузка? Какая допустимая задержка? Что случится, если мы будем недоступны час?»
Быстрый чек‑лист для самопроверки на вашем текущем проекте:
Могу назвать целевой p99 главного пользовательского сценария, не заглядывая в документы.
Знаю, что произойдёт с системой при отказе самой ненадёжной внешней зависимости.
НФТ проверяются автоматически хотя бы для одного критичного сценария.
Каждое крупное архитектурное решение записано в ADR со ссылкой на НФТ.
Знаю месячный бюджет инфраструктуры и укладываюсь ли в него.
Если по трём и более пунктам ответ «нет» — вы, скорее всего, уже совершили одну из пяти ошибок. Просто она ещё не всплыла.

Когда нефункциональные требования остаются общими словами, архитектурные решения приходится пересматривать уже под реальной нагрузкой.
На открытых уроках разберут, как переводить такие требования в измеримые ограничения и выбирать шаблоны проектирования под конкретные задачи — чтобы система оставалась предсказуемой не только на схеме, но и в рабочей среде.
5 августа в 20:00. «Влияние нефункциональных требований на архитектуру». Записаться
24 августа в 20:00. «Основные шаблоны проектирования в системном дизайне». Записаться
Больше бесплатных уроков смотрите в дайджесте.
ASenchenko
Сергей, может быть я не заметил в статье, но вроде не было.
Поэтому задам таки подлый вопрос. А экономику самой ЧП Вы считали на окупаемость затрат на её поддержку?
Понятно, что есть не только ЧП, но ещё и НГ и 8М. Но это всё-равно не 51 неделя пиковой нагрузки. И вне всплесков инфраструктура работает вхолостую?