lenec ru

← все посты

Connection pooling в Node.js: postgres, redis, HTTP keep-alive

13K

Когда ваш Node.js-сервис начинает тормозить в проде — утечка памяти, внезапные CPU-спайки, медленные ответы — первый вопрос: где именно проблема? Логи молчат, метрики показывают только симптомы. Профилирование в production — это не роскошь, а необходимость. В этой статье разберём три главных инструмента: Clinic.js, 0x и pprof — и научимся читать flamegraph так, чтобы находить bottleneck за минуты, а не дни.

Зачем профилировать в проде: реальные кейсы

Типичная история: сервис работал месяцами, и вдруг p99-латентность выросла с 50ms до 800ms. Мониторинг показывает, что CPU скачет до 100%, но какой именно код жрёт циклы — неясно. Или другой сценарий: память растёт на 50MB в час, через сутки процесс падает с OOM. Heap snapshot в Chrome DevTools показывает миллион объектов, но какой из них — утечка?

Профилирование отвечает на вопрос «где». Не «почему медленно», а «какая функция съедает 80% времени». Не «есть ли утечка», а «какой event listener висит 10 000 раз». Конкретные примеры из практики:

  • JWT-верификация дважды на запрос — middleware проверял токен, потом route handler проверял снова. Flamegraph показал два одинаковых стека по 40ms. Убрали дубль — латентность упала вдвое.
  • Синхронный JSON.parse огромного payload — клиент слал 5MB JSON, парсинг блокировал event loop на 200ms. Flamegraph показал широкий блок JSON.parse. Решение: streaming parser или ограничение размера.
  • Event listener на каждый запрос — middleware добавлял process.on('unhandledRejection', ...), но никогда не удалял. Через 10k запросов — 10k слушателей. Heap snapshot показал массив listeners. Фикс: once вместо on.

Главное правило: профилируй под нагрузкой. Flamegraph idle-сервера бесполезен — там только epoll_wait. Нужен реальный трафик или нагрузочный тест (autocannon, k6). Только тогда горячие пути проявятся.

Clinic.js: doctor, flame, bubbleprof

Clinic.js — это швейцарский нож для диагностики Node.js. Три инструмента, каждый решает свою задачу. Установка глобально:

npm install -g clinic

clinic doctor — первичная диагностика

Запускаешь, когда не знаешь, в чём проблема. Doctor собирает четыре метрики: event loop delay, CPU, память, активные handles. Затем выдаёт вердикт: «CPU-bound», «I/O-bound», «memory issue» или «event loop lag».

clinic doctor -- node server.js

Генерируешь нагрузку (например, autocannon -c 100 -d 30 http://localhost:3000), жмёшь Ctrl+C — clinic открывает HTML-отчёт. Четыре панели: event loop delay (верхний левый), CPU (верхний правый), память (нижний левый), handles (нижний правый). Если event loop delay коррелирует с CPU — это синхронный hotspot, иди в clinic flame. Если CPU низкий, а delay высокий — это I/O, иди в clinic bubbleprof.

Важно: doctor — это staging-инструмент, не production. Overhead ощутимый, файлы отчётов большие. Для прода используй perf_hooks.monitorEventLoopDelay() из Node.js core — zero overhead, экспортируй в Prometheus.

clinic flame — CPU-профилирование

Когда doctor сказал «CPU-bound», запускай flame. Это flamegraph-генератор с чистым UI:

clinic flame -- node server.js

Flamegraph читается снизу вверх: внизу — entry point (например, http.Server.emit), вверху — листовые функции. Ширина блока = CPU-время. Ищи широкие блоки — это hotspot. Пример: если bcrypt.hashSync занимает 60% ширины, значит, 60% CPU уходит на хеширование. Решение: async-версия (bcrypt.hash) или воркеры.

Clinic flame позволяет фильтровать по имени функции и скрывать Node.js internals — критично, чтобы видеть только свой код. Если видишь стену epoll_wait или uv__io_poll — это не CPU-проблема, это I/O-wait. Возвращайся к doctor.

clinic bubbleprof — async-bottleneck

Уникальная фича Clinic.js. Bubbleprof строит граф async-операций: промисы, коллбеки, таймеры. Показывает не CPU-время, а время ожидания. Если три запроса к БД идут последовательно, хотя могли бы параллельно — bubbleprof это покажет.

clinic bubbleprof -- node server.js

Граф выглядит как пузыри, соединённые стрелками. Большой пузырь = долгая операция. Цепочка пузырей = последовательное выполнение. Если видишь три пузыря «DB query» подряд — это N+1 или отсутствие Promise.all. Решение: батчинг или параллелизация.

Пример из практики: эндпоинт делал await getUser(), потом await getOrders(), потом await getProfile(). Три запроса по 50ms = 150ms total. Bubbleprof показал цепочку. Переписали на Promise.all([getUser(), getOrders(), getProfile()]) — латентность упала до 50ms.

pprof + 0x: flamegraph на практике

0x — однокомандный flamegraph

Если нужен только flamegraph без doctor/bubbleprof, используй 0x. Это самый быстрый способ получить интерактивный HTML с flame-визуализацией:

npm install -g 0x
0x -- node server.js

Генерируешь нагрузку, жмёшь Ctrl+C — 0x создаёт папку PID.0x с flamegraph.html. Открываешь в Chrome, видишь стеки. Можно автоматизировать с нагрузочным тестом:

0x -P 'autocannon -c 100 -d 30 localhost:$PORT' server.js

Флаг -P запускает команду, когда сервер откроет порт, затем автоматически останавливает процесс и генерирует flamegraph. Удобно для CI/CD: профилируешь каждый релиз, сравниваешь flamegraph — регрессии видны сразу.

Как читать flamegraph: ищи плато — широкие горизонтальные участки. Это функции, которые сами или через вызовы потомков съедают много CPU. Если видишь узкую башню — это глубокий call stack, но мало времени. Если видишь широкую низкую гору — это hotspot. Кликай на блок — 0x покажет процент от total time.

pprof — Google-формат профилей

pprof — это npm-пакет от Google для сбора CPU и heap-профилей в формате protobuf. Удобен, если у вас уже есть инфраструктура для pprof (например, Google Cloud Profiler).

npm install pprof

Сбор CPU-профиля:

const pprof = require('pprof');
const fs = require('fs');

async function profile() {
  const profile = await pprof.time.profile({
    durationMillis: 10000  // 10 секунд
  });
  const buf = await pprof.encode(profile);
  fs.writeFileSync('cpu.pb.gz', buf);
}

profile();

Просмотр через CLI:

pprof -http=:8080 cpu.pb.gz

Откроется веб-интерфейс с flamegraph, top-функциями, call graph. pprof умеет сравнивать два профиля — полезно для A/B-тестирования оптимизаций. Heap-профили собираются аналогично через pprof.heap.profile().

Минус pprof: нужен нативный модуль (node-gyp), на некоторых окружениях сборка падает. Плюс: интеграция с облачными профайлерами, долгосрочное хранение профилей.

Сравнение инструментов: когда что использовать

ПроблемаИнструментЧто покажет
Не знаю, в чём проблемаclinic doctorКатегория: CPU / I/O / memory / event loop
CPU высокий0x или clinic flameКакие функции жрут циклы
Сервис медленный, но CPU низкийclinic bubbleprofAsync-цепочки, последовательные операции
Память растётHeap snapshot (Chrome DevTools)Какие объекты не удаляются
Event loop lagperf_hooks.monitorEventLoopDelay + clinic flameСинхронные блокировки
Нужен профиль для CI/CD0x с автотестомFlamegraph каждого билда
Интеграция с облакомpprofProtobuf-профили для GCP/AWS

Правило большого пальца: начинай с clinic doctor в staging. Он скажет, куда копать. Если CPU — иди в 0x (быстрее) или clinic flame (красивее). Если I/O — иди в bubbleprof. Если память — делай heap snapshot до и после нагрузки, сравнивай в Chrome DevTools (Memory → Comparison).

Чеклист: что делать при деградации перформанса

  1. Воспроизведи проблему локально или в staging. Профилирование production-процесса опасно (overhead, риск падения). Если нельзя воспроизвести — используй perf_hooks для сбора метрик в проде, анализируй офлайн.
  2. Запусти clinic doctor под нагрузкой. Используй autocannon или k6 для генерации трафика, максимально близкого к продовому. Прочитай вердикт: CPU / I/O / memory / event loop.
  3. Если CPU-bound: запусти 0x или clinic flame, найди широкие блоки в flamegraph. Типичные виновники: синхронный crypto (bcrypt, JWT), JSON.parse больших payload, регулярки на длинных строках, циклы по большим массивам.
  4. Если I/O-bound: запусти clinic bubbleprof, найди последовательные async-операции. Типичные виновники: N+1 queries, отсутствие Promise.all, медленные внешние API без таймаутов.
  5. Если memory issue: сделай heap snapshot до нагрузки, после нагрузки, сравни в Chrome DevTools. Ищи объекты, количество которых растёт. Типичные виновники: event listeners без removeListener, кеши без TTL, замыкания, держащие большие объекты.
  6. Если event loop lag: добавь мониторинг perf_hooks.monitorEventLoopDelay({ resolution: 10 }), экспортируй в метрики. Если lag > 50ms — ищи синхронные операции через clinic flame. Типичные виновники: fs.readFileSync, crypto.pbkdf2Sync, тяжёлые вычисления в main thread.
  7. Примени фикс, измерь снова. Профилируй до и после — если латентность не упала, фикс не сработал. Не оптимизируй на глаз, только по данным.
  8. Добавь инструментацию. После фикса добавь performance.mark / performance.measure вокруг критичного кода, экспортируй в мониторинг. Если проблема вернётся — увидишь сразу.

Подводные камни

  • Не профилируй idle-процесс. Flamegraph без нагрузки покажет только event loop internals. Нужен реальный трафик.
  • Не оптимизируй то, что уже быстро. Flamegraph показывает, где 80% времени. Не трать неделю на оптимизацию функции, которая занимает 2%.
  • Async-проблемы не видны в CPU-профайлере. Если CPU низкий, а сервис медленный — это не CPU-проблема. Используй bubbleprof или логируй время каждого await.
  • Clinic.js может не работать на новых версиях Node. Проект не активно поддерживается с 2023 года. Если clinic падает — используй 0x или встроенный node --prof.
  • Heap snapshot огромного процесса может съесть всю память. Если heap 2GB — snapshot будет 4GB+. Делай snapshot на staging с меньшим dataset.

Вывод

Профилирование Node.js — это не магия, а инженерный процесс. Clinic.js даёт быструю диагностику, 0x — простой flamegraph, pprof — интеграцию с облаком. Flamegraph читается просто: широкий блок = много времени, узкий = мало. Начинай с clinic doctor, следуй его рекомендациям, измеряй до и после фикса. Главное правило: профилируй под нагрузкой, оптимизируй по данным, а не по интуиции. Один час с flamegraph экономит неделю гаданий на кофейной гуще.

Когда в следующий раз увидишь CPU-спайк в Grafana — не гадай, запусти 0x. Flamegraph покажет виновника за пять минут. Это работает.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.