Деградация системы под нагрузкой: что ломается первым
Сервис редко падает в один момент: сначала растёт p95, затем забиваются пулы соединений, появляются таймауты и повторные запросы. Разбираем, как найти первый ограниченный ресурс и измерить цену деградации.

Сервис может отвечать на 100 запросов в секунду за 80 мс, а после перехода к 300 запросам — держать среднее время в пределах нормы и одновременно отдавать часть ответов через 5 секунд. Это типичная деградация под нагрузкой: система ещё не упала, но очереди уже растут, таймауты запускают повторы, а один медленный компонент начинает перегружать остальные.
Деградация начинается не с ошибки 500
Для разбора возьмём типичный HTTP-сервис за Nginx 1.27 с приложением и PostgreSQL 16. Версии важны для конкретных директив и представлений мониторинга; сама методика применима шире. Числа ниже — не результаты универсального бенчмарка, а пример того, как читать поведение системы.
Пользователь видит проблему раньше, чем мониторинг фиксирует падение:
- растёт p95 и p99, хотя среднее время ответа почти не меняется;
- увеличивается доля ответов 499, 502, 504 или ошибок приложения;
- очереди запросов и соединений становятся длиннее;
- растёт число повторных запросов от клиентов и прокси;
- пропускная способность перестаёт увеличиваться вместе с нагрузкой.
Последний признак особенно важен. Если вы подаёте в систему вдвое больше запросов, а завершённых запросов становится лишь на несколько процентов больше, упёрся не пользовательский сценарий, а ограниченный ресурс: CPU, память, диск, база, пул соединений или внешний сервис.
Под нагрузкой нужно искать не «самый загруженный сервер», а первый ресурс, который начал увеличивать очередь и время ожидания.
Насыщение — состояние, в котором новый запрос не получает ресурс сразу и ждёт в очереди: свободный процесс, соединение с базой, слот диска или ответ внешнего API.
Как система деградирует по цепочке
1. Растёт очередь перед ограниченным ресурсом
У каждого слоя есть конечная ёмкость. У приложения ограничено число рабочих процессов или потоков, у PostgreSQL — доступные подключения и вычислительные ресурсы, у диска — пропускная способность и время обработки операций. Пока запас есть, задержка почти не меняется. После насыщения новый запрос сначала ждёт.
Эта задержка не всегда видна в профиле самого приложения. Например, обработчик может провести 40 мс в коде, но ещё 600 мс ждать свободное соединение с PostgreSQL. Поэтому одного времени выполнения функции недостаточно: нужно отделять работу от ожидания.
2. Таймаут превращает очередь в повторную нагрузку
Клиент, балансировщик или SDK прекращает ждать через заданный интервал. Если клиент повторяет запрос, исходная работа не обязательно исчезает: сервер может продолжать выполнять её до отмены или завершения. Так 300 исходных запросов превращаются в большее число попыток.
Повторы особенно опасны для записи, оплаты и постановки задач. Без идентификатора операции один пользовательский запрос может создать несколько одинаковых действий. Идемпотентность — отдельная тема; здесь важно не смешивать защиту от дублей с диагностикой производительности.
3. Локальная проблема становится каскадной
Медленный запрос к базе удерживает рабочий поток приложения. Забитый пул соединений увеличивает время ожидания. Веб-сервер держит больше открытых соединений, растёт расход памяти, а сборщик мусора или ядро начинают тратить больше времени на обслуживание. Ошибка распространяется вверх по цепочке.
Поэтому график CPU приложения может выглядеть умеренным, когда реальная причина находится в базе или внешнем сервисе. Низкая загрузка CPU не означает, что система свободна: потоки могут ждать сеть, блокировку или соединение.
Что измерять до и во время теста
Нагрузочный тест без базовой линии даёт только красивый график. Зафиксируйте версию приложения, конфигурацию Nginx 1.27, PostgreSQL 16, тип хранилища, объём данных, число виртуальных пользователей, профиль запросов и длительность прогрева. Иначе сравнение «до и после» будет спорным.
| Уровень | Что смотреть | Как трактовать ухудшение |
|---|---|---|
| Клиент | RPS, p50, p95, p99, ошибки, таймауты | Растущий p99 при стабильном p50 указывает на хвост очереди или отдельные медленные операции |
| Nginx | 5xx, 499, активные соединения, время до ответа upstream | 504 и рост времени upstream показывают, что прокси ждёт приложение или следующий слой |
| Приложение | очередь, занятые workers, время ожидания БД и внешних API | Занятые workers при низком CPU часто означают блокирующее ожидание |
| PostgreSQL | активные запросы, ожидания, блокировки, чтения, записи, подключения | Рост ожидания блокировки или диска важнее одного значения загрузки CPU |
| Хост | CPU, RAM, swap, диск, сеть, file descriptors | Swap и нехватка дескрипторов могут замедлить весь процесс до появления явных ошибок |
Собирайте показатели с одинаковым интервалом времени и совмещайте их на одной временной шкале. Если p99 вырос в 10:03, а очередь подключений к базе — в 10:02, это полезнее, чем отдельные снимки конфигурации после инцидента.
Как найти первый ограниченный ресурс
- Подайте ступенчатую нагрузку. Начните с уровня, на котором сервис стабилен, затем увеличивайте интенсивность небольшими шагами. На каждом уровне ждите, пока установится режим, и фиксируйте p95, p99, RPS и ошибки.
- Разделите прогрев и рабочую фазу. Первые запросы могут заполнять кеши, открывать соединения и компилировать код. Не сравнивайте их с установившейся нагрузкой.
- Сопоставьте вход и выход. Сравните отправленные, принятые и завершённые запросы. Если вход растёт, а завершённые запросы перестают расти, ищите очередь, а не «медленный интернет».
- Проверьте ожидание. Разложите время ответа на обработку приложения, ожидание базы, сеть и внешние вызовы. Если такой разрез не собирается, добавьте трассировку или хотя бы метрики времени каждого вызова.
- Повторите тест после одного изменения. Увеличение пула, добавление кеша и изменение таймаута одновременно уничтожают причинно-следственную связь. Меняйте один фактор и повторяйте тот же профиль нагрузки.
Проверка каждого шага проста: на графике должна появиться точка, после которой меняется наклон задержки или пропускной способности. Это и есть кандидат на первое насыщение.
Где подход ломается
Среднее время скрывает хвост
Среднее значение может остаться приемлемым, если 99 запросов быстрые, а один ждёт несколько секунд. Для пользовательского интерфейса и API смотрите p95 и p99, но не заменяйте ими всё остальное: высокий p99 при маленьком объёме может быть случайным выбросом.
Пул соединений не лечит медленную базу
Увеличение пула иногда повышает пропускную способность, если база недоиспользована. Но если PostgreSQL уже упирается в диск, CPU или блокировки, дополнительные соединения лишь создают больше конкурирующих запросов. Цена решения — память приложения, подключения к базе и более длинная очередь.
Кеш маскирует проблему данных
Кеш может резко улучшить повторяемый тест и не изменить поведение на промахах. Проверяйте отдельно попадания и промахи, холодный и прогретый режим, а также момент истечения ключей. Массовое истечение TTL способно создать короткий всплеск запросов к базе.
Тестовый профиль слишком удобный
Один одинаковый URL не похож на реальную нагрузку. В рабочем профиле смешайте чтение, запись, авторизацию, разные размеры ответов и внешние вызовы. Но не запускайте такой тест на продакшене без согласованного окна и защиты от изменения данных.
Как сравнить варианты исправления
| Подход | Что улучшает | Цена и риск |
|---|---|---|
| Оптимизация запроса | Снижает время и ресурсы одной операции | Нужны план выполнения и проверка на реальных объёмах данных |
| Ограничение параллелизма | Не даёт очереди разрастись бесконтрольно | Часть запросов получит отказ или будет ждать раньше |
| Кеширование чтения | Убирает повторную работу приложения и базы | Нужно управлять устаревшими данными и промахами кеша |
| Масштабирование | Добавляет вычислительный или сетевой ресурс | Не помогает, если узкое место — одна база, блокировка или внешний API |
| Асинхронная обработка | Убирает долгую работу из пользовательского запроса | Появляются очереди, статусы задач, повторы и контроль доставки |
Выбирайте исправление по месту насыщения. Если растёт ожидание подключения к базе, начинать с увеличения Nginx workers бессмысленно. Если медленный внешний API удерживает потоки, полезнее ограничить параллелизм и перенести допустимую работу в очередь, чем бесконечно увеличивать таймаут.
Что делать дальше
Соберите для сервиса простой нагрузочный профиль и сохраните базовую линию: максимальный устойчивый RPS, p95, p99, долю ошибок и показатель первого насыщенного ресурса. Затем добавьте алерты не только на 5xx, но и на рост хвоста задержки, очередей и времени ожидания базы.
После каждого изменения прогоняйте тот же сценарий на том же объёме данных. В продакшене отдельно отслеживайте повторные запросы, блокировки PostgreSQL 16, таймауты Nginx 1.27 и расхождение между входящим и завершённым трафиком. Следующий полезный шаг — построить трассу одного медленного запроса от клиента до базы и проверить, где именно он провёл большую часть времени.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: NGINX documentation: HTTP upstream module, PostgreSQL 16 documentation: Monitoring Database Activity
Частые вопросы
Почему p95 растёт, а среднее время ответа почти не меняется?
Как понять, что первым перегружается база данных?
Поможет ли увеличение числа workers приложения?
Что будет, если нагрузочный тест превысит расчётный предел?
Нужно ли ориентироваться на 100% загрузку CPU?
Как отличить медленный запрос от перегруженной системы?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.