Наблюдаемость и устранение проблем: логи, метрики, трассировка, профилирование
Зачем нужна наблюдаемость именно в Node.js backend
В прошлых темах курса мы разобрали, что под нагрузкой деградация обычно начинается из-за:
блокировок Event Loop синхронным CPU-кодом
очередей на I/O (пул БД, сеть, Redis), таймаутов и ретраев
проблем на сетевом уровне (keep-alive, лимиты соединений, TCP/HTTP таймауты)Наблюдаемость нужна, чтобы быстро и доказательно отвечать на вопросы:
что именно медленно: наш код, база данных, сеть, Redis, внешний сервис
где узкое место: CPU, память, пул соединений, блокировки в БД, backpressure
почему это произошло сейчас: релиз, рост трафика, деградация зависимостиЦель этой темы — собрать практическую систему инструментов: логи, метрики, трассировки, профилирование, и понять, как ими пользоваться при инцидентах.
!Как логи, метрики и трассировки связывают запрос пользователя с зависимостями
Базовые понятия и «три столпа» наблюдаемости
Логи
Логи — это события, которые происходят в системе. Они отвечают на вопрос что случилось и с какими данными.
Хорошие логи в backend обычно:
структурированные (JSON), чтобы их можно было фильтровать и агрегировать
с корреляцией (например, request_id или trace_id)
с корректными уровнями (debug, info, warn, error)Метрики
Метрики — это численные временные ряды. Они отвечают на вопрос насколько плохо и как меняется ситуация во времени.
Типовые метрики для Node.js highload:
RPS, ошибки по кодам, p95/p99 latency
event loop lag
использование памяти, частота и длительность GC
состояние пулов (пул соединений к БД, активные сокеты)Трассировки
Трассировка (distributed tracing) — это дерево/граф операций одного запроса через несколько компонентов. Она отвечает на вопрос где именно в цепочке потеря времени.
Ключевые сущности:
trace — весь путь запроса
span — отдельный участок работы (например, HTTP upstream, запрос к БД)
context propagation — перенос идентификаторов между сервисамиПрофилирование
Профилирование — это инструмент для доказательного ответа на вопрос какой участок кода сжигает CPU/память.
Для Node.js это особенно важно из-за одного главного потока выполнения JavaScript (см. тему про Event Loop): если вы упёрлись в CPU или сделали тяжёлую синхронную работу, вы ухудшаете latency для всех запросов.
Как связать логи, метрики и трассировки в одну систему
Главная идея: любой запрос должен быть узнаваем во всех сигналах.
Практический минимум корреляции:
на входе запроса генерировать request_id (или принимать из заголовка, например X-Request-Id)
включать request_id в каждый лог в рамках запроса
пробрасывать request_id в вызовы upstream (HTTP) и в фоновые задачиЕсли вы используете OpenTelemetry, то часто достаточно trace_id и span_id, но request_id всё равно бывает удобен для прикладных сценариев.
Логи: как логировать так, чтобы это реально помогало
Структурированные логи
Структурированные логи обычно пишут в JSON: это делает логи машиночитаемыми и позволяет быстро строить фильтры.
В Node.js часто используют pino как быстрый логгер:
PinoПример (упрощённо):
Ключевые моменты:
logger.child(...) добавляет контекст (например, request_id) ко всем логам
длительность запроса логируется как число, чтобы потом строить агрегаты
ошибка логируется как объект err, чтобы сохранить stack traceУровни логирования и правило «не утонуть в логах»
Типовая дисциплина:
debug: подробности для локальной диагностики (обычно выключено в проде)
info: бизнес-события и завершение запросов
warn: подозрительное поведение, но сервис жив (долгие ответы upstream, ретраи)
error: запрос не выполнен или зависимость недоступнаВажно: большие объёмы логов под нагрузкой могут сами стать проблемой (I/O и стоимость хранения). Поэтому логирование должно быть дозированным.
Что логировать в highload-сервисе
Практический минимум для HTTP API:
На каждый запрос
-
request_id, метод, путь (без секретов), статус,
duration_ms
На каждый вызов зависимости (БД/Redis/HTTP upstream)
- имя зависимости, тип операции,
duration_ms, результат (успех/таймаут/ошибка)
На ретраи
- номер попытки, причина, задержка
На деградацию
- сработал ли circuit breaker, fallback, rate limit
Отдельно важно избегать:
логирования персональных данных и секретов
логирования огромных payload (это ломает стоимость и скорость)Метрики: что измерять, чтобы видеть проблему до пользователей
Типы метрик
В прикладной эксплуатации полезно различать:
counter — только растёт (например, число запросов)
gauge — текущее значение (например, число активных соединений)
histogram — распределение (например, latency)Для Node.js-экосистемы часто используют Prometheus + клиентскую библиотеку:
Prometheus
prom-clientЗолотые сигналы
Минимальный набор метрик, который почти всегда нужен:
latency: p50/p95/p99 по ключевым ручкам и по upstream
traffic: RPS
errors: доля ошибок, отдельно таймауты
saturation: признаки упора в ресурсВ Node.js к saturation часто относятся:
event loop lag
рост очереди ожидания в пуле БД
рост числа активных сокетов и повторных подключений
рост RSS/heap и частоты GCEvent loop lag как индикатор блокировок
Event loop lag показывает, насколько Event Loop запаздывает относительно реального времени. Если лаг растёт, значит:
вы выполняете слишком много синхронного CPU-кода
или создаёте слишком много микрозадач (например, неконтролируемые Promise)Для продакшена удобно измерять event loop delay через встроенный модуль:
Node.js: perf_hooksПример:
Как это связывается с предыдущими темами:
если p99 latency растёт вместе с event loop lag, вы почти наверняка блокируете основной поток
если lag нормальный, а latency растёт, чаще виноваты сеть/БД/пулы/таймаутыМетрики пулов и очередей
Под highload система часто ломается не «в среднем», а из-за очередей.
Практика:
Для БД и Redis
- количество активных соединений
- число ожидающих получение соединения из пула
- время ожидания соединения
Для HTTP-клиента
- число активных сокетов, число свободных сокетов
- частота reconnect
Это напрямую связано с темами про highload и сети: если вы не видите очереди, вы не понимаете p99.
Трассировка: как быстро найти узкое место в цепочке запроса
Когда трассировка незаменима
Трассировка особенно полезна, когда:
один HTTP запрос включает несколько запросов к БД, Redis и внешним API
есть микросервисы и нужно понять, в каком компоненте деградация
проблема проявляется в p99, и по логам сложно понять, где именно потеря времениOpenTelemetry как стандарт
OpenTelemetry — это стандарт де-факто для инструментирования:
OpenTelemetryКлючевая инженерная идея: вы генерируете trace/span в сервисе и экспортируете данные в коллектор/бекенд (Jaeger, Tempo и т.д.).
В Node.js важно понимать один практический риск: контекст должен корректно переноситься через async-код. Это тесно связано с темой асинхронности.
Что нужно трассировать в Node.js backend
Базовый набор span:
Входящий HTTP запрос
Запросы к БД (SQL/NoSQL)
Команды Redis
HTTP/TCP вызовы в другие сервисыНаиболее полезные атрибуты:
имя операции (db.system, http.method, http.route)
статус (успех/ошибка)
длительностьСмысл: в интерфейсе трассировки вы хотите увидеть, что именно съело, например, 800 мс из 1 секунды.
Профилирование Node.js: CPU, память, GC
Когда нужно профилирование, а не просто метрики
Профилирование имеет смысл, когда метрики уже показали симптом, но причина в коде:
event loop lag растёт при росте трафика
CPU на одном ядре близок к 100%
p99 растёт даже без деградации БД/сетиCPU profiling
Инструменты:
встроенный профилировщик V8 через --cpu-prof
Chrome DevTools через --inspectДокументация Node.js:
Node.js: Command-line API
Node.js: InspectorПрактический подход в продакшен-подобной среде:
Воспроизвести нагрузку (локально или на стенде)
Снять CPU профиль на отрезке деградации
Найти «горячие» функции и понять, почему они горячие (алгоритм, парсинг, сериализация, regex, лишние JSON)Для нагрузочного теста часто используют:
autocannonMemory profiling и утечки
Симптомы утечек памяти:
RSS растёт, не возвращается вниз
heap usage растёт ступеньками
GC становится чаще и дольшеИнструменты:
heap snapshot через DevTools (--inspect)
анализ объектов, которые продолжают удерживатьсяПрактический совет: утечка в Node.js часто связана не с «низкоуровневой памятью», а с тем, что вы удерживаете ссылки:
глобальные кэши без ограничений
Map/Set, которые никогда не очищаются
подписки на события без отпискиClinic.js как набор практических профайлеров
Clinic.js удобен как «комбайн» для диагностики CPU/IO/Event Loop:
Clinic.jsОн помогает быстро получить отчёты, если вы ещё не уверены, где причина: CPU, I/O или блокировки.
Типовые инциденты и как их расследовать
Рост p99 latency без роста event loop lag
Частые причины:
Проблемы сети или upstream
- вырос TTFB
- истёк keep-alive, много reconnect
Очередь в пуле БД
- слишком много параллельных запросов
- медленные запросы без индексов
Redis/кэш деградирует
- таймауты
- hot key
Что делать:
Сначала смотреть метрики по зависимостям (p95/p99 для БД/Redis/upstream)
Проверить таймауты и долю ретраев
Открыть трассировки и найти самый долгий spanРост p99 latency вместе с ростом event loop lag
Частые причины:
Синхронные тяжёлые операции в обработчиках
- сериализация больших объектов
- дорогие regex
- криптография синхронными методами
Слишком много работы в микрозадачах
- неконтролируемая генерация Promise/
process.nextTickЧто делать:
Снять CPU профиль и найти горячие функции
Убрать синхронную тяжёлую работу из Event Loop
- вынести в
worker_threads
- переписать алгоритм
- сделать потоковую обработку вместо накопления в памяти
Падения по памяти (OOM) или постоянный рост RSS
Частые причины:
Утечки ссылок (кэши, глобальные структуры)
Накопление буферов из-за backpressure
Слишком большие ответы/запросы без лимитовЧто делать:
Включить метрики heap/RSS и частоты GC
Снять heap snapshot и найти «retainers» (кто удерживает объекты)
Добавить лимиты на размер входных данных и защиту от больших payloadМинимальный «продакшен-стандарт» для Node.js сервиса
Обязательные практики
Логи
- структурированные JSON
-
request_id/
trace_id в каждом запросе
- единый формат ошибок
Метрики
- RPS, ошибки, latency p95/p99
- event loop lag
- метрики зависимостей (БД/Redis/upstream)
Трассировка
- входящие запросы + зависимости
- корректная передача контекста
Профилирование
- умение снять CPU/heap профили на стенде
- понимание, как интерпретировать «горячие» места
Почему это связано со всем курсом
Асинхронность и Event Loop дают вам язык, чтобы интерпретировать event loop lag, микрозадачи и блокировки.
Highload-архитектура объясняет, почему важны очереди, лимиты и saturation-метрики.
Базы данных и Redis — главные источники латентности и очередей, поэтому их нужно измерять и трассировать.
Сети (HTTP/TCP) объясняют, почему без таймаутов и keep-alive вы не сможете стабильно держать p99.Куда дальше
Следующие логичные шаги после этой темы:
практики модульного тестирования, чтобы безопасно менять код во время оптимизации
Linux-эксплуатация: лимиты файловых дескрипторов, сетевые очереди, диагностика соединений
углублённая оптимизация производительности: профилирование, аллокации, GC, оптимизация горячих путей