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

Сервис начинает отдавать 502, хотя CPU занят на 40%, а приложение «вроде работает». В легаси это типичный сценарий: узкое место скрывается не в самом медленном обработчике, а в очереди перед ним, пуле соединений к базе, блокировке или внешнем API. В первые недели под нагрузкой задача инженера не переписать систему, а восстановить причинную цепочку: какой ресурс исчерпался первым, как это повлияло на соседние компоненты и какое изменение уменьшит риск, не добавив новый.
Сначала зафиксируйте систему, а не чините её
Эта тактика рассчитана на типичный веб-стек: Linux, Nginx 1.24 или новее, приложение за reverse proxy и PostgreSQL 16 или совместимую реляционную базу. Если в проекте стоят другие версии, проверяйте поведение по документации именно вашей сборки: в легаси важны не только названия компонентов, но и локальные патчи, настройки запуска и способ деплоя.
В первые два-три дня нужен не список архитектурных претензий, а карта потока запроса. Запишите:
- где завершается TLS и какой компонент принимает внешний запрос;
- через сколько процессов или потоков он попадает в приложение;
- какой пул соединений используется для базы данных и внешних сервисов;
- где стоят очереди, фоновые задачи, кэш и балансировщик;
- какие тайм-ауты заданы на каждом участке цепочки;
- как откатить релиз и кто имеет право менять конфигурацию в проде.
Снимите версии, лимиты и текущие значения, но не меняйте их сразу. Иначе после инцидента будет непонятно, что именно повлияло на результат.
Что ломается первым
У нагрузки есть неприятное свойство: она превращает небольшую задержку в очередь. Один медленный запрос занимает рабочий слот, следующий ждёт, затем растёт время ответа, а пользователи начинают повторять запросы. Повторы добавляют нагрузку и ускоряют отказ.
1. Очередь перед приложением
Первым сигналом может стать не высокая загрузка процессора, а рост времени ожидания до приложения. Nginx продолжает принимать соединения, но upstream не успевает обслужить их. В результате вы видите 502 или 504, хотя процесс приложения ещё не завершился.
Смотрите на распределение задержки, а не только на среднее значение. Средняя задержка может оставаться приемлемой, пока длинный хвост запросов уже блокирует пользователей и рабочие процессы.
2. Пул соединений к базе данных
Если каждый запрос получает соединение с базой и удерживает его во время обращения к внешнему API, пул быстро становится очередью. Увеличить его размер вслепую опасно: PostgreSQL и сама операционная система получат больше одновременно работающих запросов, а конкуренция за CPU, память и блокировки усилится.
Проверяйте четыре значения в одной временной шкале: число активных соединений, время ожидания свободного соединения, длительность SQL-запросов и количество транзакций. Если приложение ждёт пул, а база простаивает, проблема на границе компонентов. Если база занята блокировками или тяжёлыми запросами, расширение пула только отложит отказ.
3. Блокировки и длинные транзакции
Под нагрузкой особенно болезненны операции, которые в обычный день выполняются редко: массовое обновление, пересчёт остатков, очистка старых записей, изменение индекса. Длинная транзакция удерживает блокировки, а обычные запросы начинают ждать. Снаружи это выглядит как «иногда медленная база».
Ищите не самый большой запрос по объёму данных, а запросы, у которых растёт время ожидания. Отдельно отмечайте транзакции, живущие дольше обычного цикла запроса. Причину блокировки нужно подтверждать в базе, а не угадывать по логам приложения.
4. Внешняя зависимость
Платёжный шлюз, сервис доставки, авторизация или DNS могут отвечать медленно, а легаси-приложение продолжит держать рабочий поток и соединение с базой. Один внешний вызов без тайм-аута способен занять ресурс на всё время ожидания. Несколько таких вызовов в цепочке дают задержку, которую пользователь воспринимает как падение сайта.
У каждого сетевого вызова должны быть измеримые: время установления соединения, время ожидания ответа, число повторов и итоговый тайм-аут. Если в коде этого нет, не подставляйте общий тайм-аут на уровне прокси как единственное лечение: он оборвёт запрос снаружи, но внутреннюю работу приложение может продолжить.
5. Логи, диски и фоновые задачи
Когда начинается инцидент, приложение пишет больше ошибок. Если логи синхронно сбрасываются на диск, а место заканчивается, проблема наблюдаемости превращается в новую аварию. Параллельно очередь фоновых задач может расти быстрее, чем воркеры успевают её разбирать.
Измеряйте размер очереди, возраст самого старого задания и скорость обработки. Для логов нужны ротация, ограничение объёма и проверка свободного места. Удалять логи во время инцидента без копии нельзя: вы потеряете единственные следы первопричины.
Под нагрузкой ломается не «самый плохой» компонент. Ломается первый ограниченный ресурс, который не умеет сообщить соседям, что он уже заполнен.
План первых недель
Работа по порядку важнее масштаба изменений. Вносите одну проверяемую правку за раз и заранее определяйте показатель, который должен измениться.
- Соберите базовую линию. Зафиксируйте частоту запросов, p50, p95 и p99 задержки, долю ошибок, загрузку CPU и памяти, состояние диска, активные соединения и глубину фоновых очередей. Снимите данные минимум в обычный период и в известный пик.
- Разделите симптомы по слоям. Отметьте момент начала задержки в Nginx, приложении, базе и внешней зависимости. Проверка только HTTP-статуса не покажет, где запрос провёл большую часть времени.
- Найдите первый исчерпанный ресурс. Это может быть лимит воркеров, соединения, блокировка, файловые дескрипторы, память или пропускная способность внешнего сервиса. Не увеличивайте лимит, пока не понятно, что произойдёт с зависимым компонентом.
- Снизьте давление. Временно отключите необязательную тяжёлую операцию, ограничьте повторные попытки, вынесите пересчёт в фон или включите кэш только для безопасных чтений. Любая мера должна иметь условие отката.
- Проведите контролируемое измерение. Повторите сценарий на стенде, близком к продакшену, или выберите небольшой процент трафика. Сравните одинаковые показатели до и после, а не впечатления команды.
- Закрепите защиту. Добавьте тайм-ауты, лимиты, алерты и runbook. Если знание живёт только у человека, который был на ночном дежурстве, система всё ещё уязвима.
Как читать замеры, а не спорить по графикам
| Наблюдение | Вероятный механизм | Что проверить первым | Опасное действие |
|---|---|---|---|
| Растут p95 и p99, CPU не загружен | Ожидание пула, блокировка или внешняя зависимость | Время ожидания каждого ресурса и активные транзакции | Без причины увеличить число воркеров |
| 502/504 появляются волнами | Исчерпание очереди или тайм-аут upstream | Логи Nginx, время ответа приложения, длину очереди | Поднять тайм-ауты во всех слоях сразу |
| База загружена, запросы стали длиннее | Конкуренция, план запроса, блокировки или нехватка I/O | План и фактическую длительность проблемных запросов | Сделать пул соединений ещё больше |
| Ошибки растут после повторов | Retry storm: повторные вызовы усиливают исходный сбой | Число попыток, задержку между ними и общий бюджет времени | Добавить бесконечные или синхронные повторы |
| Очередь фоновых задач не уменьшается | Производительность потребителей ниже скорости поступления | Возраст старого задания, ошибки и длительность обработки | Запустить ещё воркеры без проверки базы и внешних API |
Где подход ломается
Наблюдение за метриками не заменяет трассировку, если один пользовательский запрос проходит через несколько сервисов, а идентификатор запроса теряется на границе. В таком случае хотя бы прокидывайте единый correlation ID через прокси, приложение и фоновые задания. Если легаси не позволяет быстро добавить полноценную трассировку, начните с временных логов длительности вокруг конкретных операций и удалите их после измерения.
Один и тот же симптом может иметь разные причины. Рост памяти бывает следствием утечки, большого кэша, очереди сообщений или резкого увеличения размера ответа. 504 может означать медленную базу, зависший внешний вызов или слишком короткий тайм-аут. В отчёте об инциденте разделяйте факт, гипотезу и подтверждённую причину.
Отдельная граница — кэш. Он снижает нагрузку на чтение, но добавляет вопрос актуальности данных и риск одновременного прогрева. Инвалидация и защита от cache stampede — отдельная тема; здесь важно только измерить, сколько запросов действительно обслуживается из кэша.
Что ревьюить в изменениях
- Не появился ли сетевой вызов внутри транзакции или цикла по объектам.
- Есть ли верхняя граница у повторов и фоновой очереди.
- Освобождаются ли соединения и файловые дескрипторы при ошибке.
- Не увеличивает ли новая метрика или лог объём записи на диск в разы.
- Есть ли план отката, а не только инструкция по включению.
- Изменяется ли один ограниченный ресурс без проверки его потребителей.
Формулировка ревью должна быть проверяемой: «Этот вызов удерживает соединение с базой до ответа внешнего API; измерьте время ожидания и вынесите операцию за границы транзакции». Замечание «код может тормозить» не помогает принять решение.
Сноска. p95 — 95-й перцентиль задержки: 95% измеренных запросов быстрее этого значения, а 5% медленнее. Для пользовательского опыта он обычно полезнее среднего времени, потому что показывает длинный хвост задержек.
Что делать дальше
После первых измерений выберите один узкий сценарий с высокой ценой отказа: оформление заказа, вход, поиск или запись данных. Для него заведите дашборд с задержкой по слоям, ошибками, ожиданием пулов, блокировками и очередями. Затем проведите повторяемый тест с зафиксированными версией приложения, объёмом данных, числом параллельных запросов и конфигурацией.
На проде следите не только за доступностью. Поставьте алерты на p95/p99, долю 5xx, возраст очереди, свободное место, активные соединения и длительные транзакции. Следующий полезный шаг — проверить сценарий отказа внешней зависимости: сервис должен завершать запрос предсказуемо, а не держать воркеры до общего тайм-аута.
Разрушитель авто-разводов: Фото заказ-наряда СТО: завышения, дубли работ, лишние позиции.
Спросить за 15 ₽Источники: Google SRE Book: Monitoring, NGINX documentation: HTTP upstream module
Частые вопросы
С чего начать, если в системе почти нет мониторинга?
Нужно ли первым делом увеличивать число воркеров приложения?
Что делать, если растут 502 и 504, а ошибок в приложении нет?
Как отличить проблему базы от проблемы внешнего API?
Можно ли увеличить тайм-аут, чтобы пользователи перестали видеть 504?
Что делать, если нагрузочный тест нельзя запускать на проде?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.