Три сигнала, по которым видно проблему до падения
Сервер падает редко «внезапно» — обычно он несколько дней или недель подряд подавал сигналы, которые никто не читал как сигналы. Память медленно ползла вверх, ответы становились чуть медленнее, в логах копились единичные обрывы соединений с базой — и всё это списывалось на шум, пока не наступил инцидент. Разница между командой, которая узнаёт о проблеме в 3 часа ночи по звонку от клиента, и командой, которая чинит её спокойно во вторник днём, — не в лучших серверах, а в том, что вторые смотрят на тренд метрик, а не только на их текущее значение.
Содержание
- Почему падение почти никогда не бывает внезапным
- Сигнал 1: память растёт, а нагрузка — нет
- Сигнал 2: растёт задержка ответа при той же нагрузке
- Сигнал 3: учащаются единичные, ещё не критичные ошибки
- Хороший подход: алерт на скорость изменения, а не только на порог
- Плохой подход: мониторинг только моментальных значений
- Практика: смотреть графики за недели, а не только в момент инцидента
Почему падение почти никогда не бывает внезапным
Любой отказ сервиса — не точка, а конец процесса. Утечка памяти не появляется в момент OOM-killer'а, она растёт часами или днями. Полный отказ базы данных почти всегда предшествуется учащающимися таймаутами и ретраями, которые приложение пока успешно скрывает от пользователя. Деградация под нагрузкой не наступает мгновенно — сначала растёт p95-задержка при том же трафике, и только потом, когда нагрузка чуть подскакивает, сервис не успевает и начинает отдавать 502.
Проблема в том, что мониторинг по умолчанию — Zabbix из коробки, дефолтные дашборды Grafana, встроенные алерты хостинг-панели — почти всегда настроен на абсолютные пороги: память > 90%, CPU > 95%, диск > 85%. Это работает как индикатор «уже плохо», но не как индикатор «скоро будет плохо». Три категории ранних сигналов, которые обычно можно поймать за дни до реального отказа: постепенный рост потребления ресурсов без роста нагрузки, рост задержки при стабильном трафике и учащение единичных, ещё не критичных ошибок соединения. Дальше — как выглядит каждый из них на практике и как настроить наблюдение так, чтобы видеть их раньше, а не постфактум.
Сигнал 1: память растёт, а нагрузка — нет
Классическая утечка памяти выглядит скучно: график RSS процесса медленно и почти монотонно ползёт вверх на протяжении часов или дней, при этом количество запросов в секунду, число активных соединений и объём обрабатываемых данных остаются примерно постоянными. Ключевое здесь — несоответствие: если память растёт вместе с трафиком, это нормально (кэш, буферы, больше воркеров). Если память растёт, а трафик плоский — это утечка, и рано или поздно доступная память закончится, ядро начнёт убивать процессы через OOM-killer или сервис упадёт с ошибкой аллокации.
Проблема в том, что моментальное значение памяти почти ничего не говорит. Процесс, который стабильно держит 6 ГБ из 8, может работать так месяцами — если это не растёт. А процесс, который начал с 2 ГБ и за неделю дорос до 6 ГБ при том же трафике, упадёт с высокой вероятностью, даже если сейчас формально «есть запас».
Как смотреть на тренд, а не на значение, в Prometheus:
# Скорость роста памяти процесса за последний час, в МБ/час
rate(process_resident_memory_bytes{job="myapp"}[1h]) * 3600 / 1024 / 1024
# Прогноз: хватит ли памяти через 6 часов при текущем тренде роста
predict_linear(process_resident_memory_bytes{job="myapp"}[3h], 6*3600) > node_memory_MemTotal_bytes
Правило алерта на predict_linear срабатывает не когда память уже кончилась, а когда *тренд* показывает, что она кончится через N часов при сохранении текущей скорости роста — это и есть разница между «узнать заранее» и «узнать по факту».
Пример хорошего алерта в Alertmanager:
- alert: MemoryLeakTrend
expr: predict_linear(process_resident_memory_bytes{job="myapp"}[2h], 4*3600) > 0.9 * node_memory_MemTotal_bytes
for: 30m
labels:
severity: warning
annotations:
summary: "Память процесса {{ $labels.job }} растёт трендом к исчерпанию за ~4 часа"
Практический нюанс: не у каждого роста памяти есть тренд-опасность. Демоны с внутренним кэшем (Redis с maxmemory, JVM с настроенным heap) растут до потолка и стабилизируются — это нормально. Утечка отличается тем, что рост продолжается без плато — если за неделю наблюдений график ни разу не выровнялся горизонтально, это утечка, а не рабочий кэш.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSСигнал 2: растёт задержка ответа при той же нагрузке
Второй ранний сигнал — сервис формально «справляется»: коды ответа 200, очередь запросов не растёт бесконечно, ошибок нет — но время ответа медленно увеличивается при том же или даже сниженном уровне трафика. Это почти всегда значит, что где-то внутри системы копится деградация: пухнет таблица без нужного индекса, растёт фрагментация на диске, увеличивается время сборки мусора в рантайме, кончается пропускная способность соединения с внешним сервисом. Формально всё «работает», но запас прочности тает — и как только нагрузка вырастет хотя бы немного (пиковый трафик, фоновая задача, ещё один клиент), система не успевает и начинает отказывать по-настоящему.
Отслеживать нужно не среднюю задержку, а перцентили — p95 и p99 — и именно их тренд во времени. Средняя задержка маскирует деградацию хвоста: 95% запросов могут отвечать за 50 мс, а 5% — за 4 секунды, и если растёт именно этот хвост, среднее почти не шевельнётся, а пользователи уже жалуются.
# p95 задержки HTTP-запросов за скользящее окно 5 минут
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
# Тренд: насколько выросла p95 задержка за последний час по сравнению с часом ранее
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
/
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m] offset 1h)) by (le))
Второй запрос — это и есть настройка алерта именно на скорость изменения: если результат стабильно выше 1.3–1.5 (задержка выросла на 30-50% за час) при плоском трафике — это повод разбираться сейчас, а не ждать, пока p95 упрётся в таймаут клиента.
Пример: MySQL или PostgreSQL, у которых со временем растёт время выполнения одного и того же запроса из-за раздувшегося индекса или устаревшей статистики планировщика — типичный случай именно этого сигнала, разобран подробнее в статье про медленные запросы MySQL. Важно смотреть не на «сейчас запрос выполняется 200 мс», а на «неделю назад этот же запрос выполнялся 40 мс, а сейчас 200 — и продолжает расти».
Сигнал 3: учащаются единичные, ещё не критичные ошибки
Третья категория — самая коварная, потому что система активно её маскирует. Одиночный обрыв TCP-соединения с базой, один неудачный DNS-резолв, один упавший health-check, который тут же прошёл со второй попытки — всё это штатно перехватывается retry-логикой, connection pool переподключается, приложение не роняет ни одного запроса пользователя. Именно поэтому такие ошибки легко игнорировать: с точки зрения пользователя всё работает.
Но retry — это не устранение проблемы, это компенсация проблемы. Пока частота единичных сбоев низкая, компенсация справляется. Когда частота растёт — а она растёт, если причина (перегруженный сетевой линк, деградирующий диск, исчерпание connection pool на стороне БД) не устранена — рано или поздно retry перестаёт успевать, и единичные ошибки превращаются в видимый пользователю отказ.
Что стоит считать в динамике:
# Скорость роста количества ретраев подключения к БД за 10 минут
rate(db_connection_retries_total[10m])
# Учащение сетевых ошибок на интерфейсе — не абсолютное число, а изменение за час
increase(node_network_receive_errs_total[1h]) - increase(node_network_receive_errs_total[1h] offset 1h)
Хороший алерт здесь целится не в «ошибки есть» (они почти всегда есть в фоне на любой боевой системе), а в «ошибок стало заметно больше, чем неделю назад при том же трафике»:
- alert: RetryRateGrowing
expr: rate(db_connection_retries_total[30m]) > 3 * rate(db_connection_retries_total[30m] offset 7d)
for: 1h
labels:
severity: warning
annotations:
summary: "Частота ретраев подключения к БД выросла втрое относительно недели назад"
Похожая история с деградирующим диском в RAID-массиве: SMART-показатели растут постепенно, единичные пересчитанные секторы копятся неделями, пока массив либо не начинает сыпаться массово, либо не выходит из строя целиком — это подробно разобрано в статье о том, как месяц молчал мониторинг при деградации RAID: единичные события были, но никто не смотрел на их частоту во времени.
Хороший подход: алерт на скорость изменения, а не только на порог
Правильная настройка мониторинга описанных сигналов строится на трёх принципах.
Первое — хранить историю метрик достаточно долго, чтобы было с чем сравнивать тренд (минимум 15-30 дней для Prometheus, дольше для агрегированных данных через remote write в Thanos/Mimir или через downsampling). Без истории offset 7d и predict_linear на длинном окне попросту не с чем сравнить.
Второе — сравнивать текущую скорость изменения с исторической, а не только текущее значение с фиксированным порогом. Таблица сравнивает оба подхода к одному и тому же сигналу:
| Сигнал | Плохой алерт (порог) | Хороший алерт (тренд) |
|---|---|---|
| Память | memory_used > 90% | predict_linear(...) > memory_total — сработает за часы до исчерпания |
| Задержка | p95_latency > 2s | p95_latency / p95_latency offset 1h > 1.4 при том же трафике |
| Ошибки/ретраи | error_rate > 5% | rate(errors[30m]) > 3 * rate(errors[30m] offset 7d) |
| Место на диске | disk_used > 85% | predict_linear(disk_free[6h], 3*86400) < 0 — кончится через 3 дня |
Третье — не полагаться только на автоматические алерты, а регулярно (не только в момент разбора инцидента) открывать графики этих метрик за длительный период — недели, а не последние несколько часов. Инцидент, разбираемый по логам за последний час, показывает только финальную стадию. Тот же график за три недели почти всегда обнажает медленный восходящий тренд, который был виден заранее, если бы кто-то посмотрел на него не в момент пожара.
Настройка Grafana/Prometheus с нуля с готовыми дашбордами для трендов памяти, CPU и задержки разобрана в статье Grafana и Prometheus на Ubuntu 24.04 — пошаговая установка.
Плохой подход: мониторинг только моментальных значений
Обратная сторона — мониторинг, который проверяет только «сейчас всё в порядке?» и не хранит или не анализирует историю. Типичные признаки такого подхода:
- Алерты настроены исключительно на абсолютные пороги (
> 90%,> 5 секунд), без сравнения с прошлым периодом. - Дашборд по умолчанию открывается на «последние 6 часов», и никто вручную не переключает диапазон на «последние 30 дней».
- Ретраи и единичные ошибки не логируются как метрика с счётчиком — видны только в логах, которые никто не парсит на предмет частоты.
- Алерт срабатывает один раз, когда порог уже пройден, — то есть в момент, когда проблема уже критична, а не когда она начала развиваться.
Это не значит, что абсолютные пороги не нужны — они обязательны как последний рубеж («сейчас всё совсем плохо, будите дежурного»). Проблема в том, чтобы ограничиться только ими. Мониторинг, который умеет сказать «диск заполнен на 87%», но не умеет сказать «диск заполняется на 2% в день и через 6 дней будет полон», формально работает, но systematically узнаёт о проблеме последним. Разбор похожей ловушки — в статье миф «мониторинг — я узнаю первым»: сам факт наличия дашбордов не гарантирует, что кто-то видит тренд раньше, чем алерт по порогу.
Практика: смотреть графики за недели, а не только в момент инцидента
Самая недооценённая привычка в эксплуатации — регулярный (например, раз в неделю, вне зависимости от того, было что-то или нет) обзор ключевых графиков за длинный период: память, p95/p99 задержки, частота ретраев, свободное место на диске — за последние 2-4 недели, а не за последние несколько часов.
Практический чек-лист для такого обзора:
- Открыть дашборд памяти по всем ключевым процессам за 30 дней — есть ли линии, которые растут без плато?
- Открыть p95/p99 задержки основных эндпоинтов за 2 недели при наложенном графике трафика — растёт ли задержка быстрее, чем растёт нагрузка (или растёт вообще при плоском трафике)?
- Посмотреть счётчики ретраев/таймаутов к БД и внешним сервисам за 2 недели — есть ли восходящий тренд, даже если абсолютные числа пока небольшие?
- Проверить свободное место на дисках и трендовую скорость его убывания — хватит ли текущего темпа на ближайший месяц?
- Если такого дашборда с длинным диапазоном по умолчанию нет — создать его отдельно от «оперативного» дашборда на 6 часов, именно с диапазоном 30 дней, и добавить в еженедельный ритуал команды.
Эта привычка стоит дёшево — 15-20 минут раз в неделю — и почти всегда окупается тем, что деградация обнаруживается на стадии «есть время подумать и исправить спокойно», а не на стадии «сервис лежит, клиенты пишут в саппорт». Отдельно стоит завести короткий журнал наблюдений: если неделю назад память процесса была 3.2 ГБ, а сегодня 3.6 ГБ при том же трафике — эта запись через месяц сложится в очевидный тренд, который на глаз за один просмотр не всегда виден.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Как часто нужно проверять графики за длинный период, если алерты и так настроены?
Алерты на тренд ловят не всё — некоторые деградации развиваются медленнее, чем горизонт алерта (например, рост на 1% в неделю). Ручной еженедельный обзор графиков за месяц — дешёвая страховка от того, что осталось незамеченным.
Не приведут ли алерты на скорость изменения к лишнему шуму?
Приведут, если окно расчёта слишком короткое — краткосрочный скачок трафика даст ложный рост "тренда". Решение — считать тренд на достаточно длинном окне (часы, не минуты) и требовать for: 30m-1h устойчивости сигнала перед срабатыванием.
Что делать, если истории метрик меньше 30 дней и сравнивать не с чем?
Начать копить историю с сегодняшнего дня — это не отменяет пользы, просто offset 7d и predict_linear на длинном окне станут доступны через 1-4 недели. Пока используйте более короткие offset (1h, 6h, 1d) — они уже дают представление о направлении тренда.
Одинаково ли применимы эти три сигнала к любому стеку — база данных, веб-сервер, очередь сообщений?
Да, принцип универсален: любой процесс с состоянием подвержен утечкам памяти, любой сервис с зависимостями подвержен росту задержки при деградации зависимости, любая сетевая связь подвержена учащению единичных сбоев при деградирующей инфраструктуре. Конкретные метрики отличаются, логика наблюдения — нет.
Нужен ли для этого именно Prometheus, или подойдёт другой инструмент?
Принцип «смотреть на тренд, а не на значение» реализуем в любой системе, которая хранит историю метрик и умеет строить производную или сравнение с прошлым периодом — Zabbix, Netdata, Datadog, VictoriaMetrics подходят не хуже. Prometheus здесь выбран как пример синтаксиса PromQL, широко распространённого в связке с Grafana.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →