Как решать проблему нагрузки на CPU в сервисе
Что проверяют
Понимаете ли вы, что в Node.js весь JavaScript выполняется в одном потоке event loop, и тяжёлое вычисление тормозит не один запрос, а все сразу. Следом обычно просят рассказать, как вы найдёте конкретную функцию, которая ест процессор.
Как отвечать
Сначала диагностика, потом лечение. Высокая загрузка CPU сама по себе не беда, беда в росте задержек из-за заблокированного цикла.
- метрики:
perf_hooks.monitorEventLoopDelay()показывает задержку цикла,performance.eventLoopUtilization()его занятость. Если задержка растёт вместе с CPU, код блокирует цикл; - профиль:
node --cpu-profпишет файл, который открывается во вкладке Performance в DevTools, либо--inspectи запись вживую. На flame graph ищите широкие верхние кадры, это и есть виновник; - GC:
--trace-gcпокажет, не уходит ли время на сборку мусора из-за утечки или огромных объектов.
Типичные виновники: синхронные JSON.parse и JSON.stringify на мегабайтных телах, регулярные выражения с катастрофическим бэктрекингом, crypto.pbkdf2Sync и синхронный bcrypt, fs.readFileSync в обработчике запроса, тяжёлая сериализация логов.
Лечение по возрастанию цены:
- убрать лишнюю работу: кэш результатов, потоковая обработка вместо разбора целого тела, асинхронные версии API, которые уходят в пул libuv (по умолчанию 4 потока, меняется через
UV_THREADPOOL_SIZE); worker_threads: вынести вычисления в пул потоков, например черезpiscina, чтобы основной цикл продолжал отвечать;- горизонтально: несколько процессов через
cluster(в старых примерахcluster.isMaster, сейчасcluster.isPrimary) или реплики за балансировщиком. В Kubernetes проще второе.
Где спотыкаются
Советуют «переписать на async/await». Асинхронность помогает с вводом-выводом, а CPU-bound код внутри промиса блокирует цикл точно так же.
Запускают cluster с os.cpus().length воркерами в контейнере. Функция видит ядра хоста, а не лимит контейнера, и десяток процессов дерутся за одно ядро.
Масштабируют, не найдя причину. Регулярка с бэктрекингом на одном злом запросе положит и десять реплик.