Node.js / Advanced: reliable Node.js services / Lesson 14
Event-loop delay and profiling
Distinguish waiting from CPU pressure and collect evidence before changing a running system’s architecture.
On this page
Lesson goals
- Explain the concepts and trade-offs behind event-loop delay and profiling.
- Predict and verify both examples, including their outputs and cleanup behavior.
- Diagnose the three common failures and apply the worked solutions to related inputs.
Start with an observable symptom
A slow request does not identify its cause. Time can be spent waiting for a connection, reading an upstream response, executing JavaScript, collecting garbage, or sitting in an application queue. Record these stages separately and compare a normal period with an incident. High CPU and elevated event-loop delay suggest a different investigation from low CPU and a saturated database pool. Avoid treating one host-wide utilization number as proof about a specific Node process. Establish the workload, runtime, deployment limits, and time window before drawing conclusions.
Measure delay without promising exact timing
monitorEventLoopDelay samples how late scheduled loop work runs. Its histogram values use nanoseconds, so convert deliberately before displaying milliseconds. The first example enables a short observation window and checks that samples exist instead of publishing a fabricated universal latency. Resolution changes overhead and what the histogram can observe. A short idle sample cannot establish production tail latency, and the monitoring tool itself is not a load test. In a service, reset or export interval data intentionally and separate shutdown artifacts from ordinary measurement windows.
Read utilization as a ratio
eventLoopUtilization reports active and idle loop time and a utilization ratio. It is not identical to operating-system CPU usage: synchronous blocking in certain calls can keep the loop active while CPU behavior differs. The second example captures a baseline and computes a delta after a timer wait. Only the type and valid range are asserted because actual ratios depend on scheduling and workload. Compare ratios across consistent windows and correlate them with delay, request throughput, and host metrics before claiming that the process needs more cores.
import { monitorEventLoopDelay } from 'node:perf_hooks';
import { setTimeout as delay } from 'node:timers/promises';
const histogram = monitorEventLoopDelay({ resolution: 10 });
histogram.enable();
try {
await delay(100);
console.log(histogram.count > 0);
console.log(Number.isFinite(histogram.mean / 1e6));
} finally { histogram.disable(); }
true
true
Samples are collected over a local idle window. The conversion divides nanoseconds by one million; no fixed latency is asserted.
Capture a focused profile
A CPU profile samples executing stacks and helps locate functions consuming CPU during a representative window. Use the official inspector or CPU profiling options in a controlled environment, protect diagnostic artifacts, and avoid exposing an inspector listener publicly. Heap snapshots answer a different question: what objects are retained. They can pause the process and require substantial memory, so choose the capture environment and safety margin carefully. Start with a narrow hypothesis and one artifact. A large collection of unrelated traces can increase operational risk without making the cause clearer.
import { performance } from 'node:perf_hooks';
import { setTimeout as delay } from 'node:timers/promises';
const start = performance.eventLoopUtilization();
await delay(20);
const delta = performance.eventLoopUtilization(start);
console.log(typeof delta.utilization);
console.log(delta.utilization >= 0 && delta.utilization <= 1);
number
true
The delta describes this observation interval. Its numeric value is intentionally not presented as a portable expected benchmark.
Turn evidence into a verified change
If a profile points to repeated JSON serialization, reduce repeated work or cache an appropriate representation before introducing threads. If allocation drives garbage collection, examine retention and object lifetime rather than assuming a leak. A leak means unwanted retention over time, which requires comparable snapshots or metrics under comparable load. Re-run the same representative workload after one change and compare correctness, throughput, tail latency, and memory. Keep rollback possible. Profiling is a way to narrow uncertainty, and an optimization claim is only as strong as the experiment that supports it.
Common errors and fixes
Delay is a million times too large
- Why it happens
- Nanoseconds were labeled as milliseconds.
- How to fix it
- Divide by 1e6 and label units.
- Prevent it next time
- Keep units in metric names and dashboards.
Low CPU is mistaken for a healthy service
- Why it happens
- Requests are queued or blocked on an external dependency.
- How to fix it
- Measure pool wait and upstream timing separately.
- Prevent it next time
- Correlate resource and request metrics.
A diagnostic capture causes an outage
- Why it happens
- A memory-heavy artifact was captured without headroom.
- How to fix it
- Use an isolated replica or controlled reproduction.
- Prevent it next time
- Plan capture overhead and protect artifacts.
Check your understanding
1. A histogram percentile is 25000000 nanoseconds. Convert it and explain its limitation.
Show worked solution
Divide by 1000000 to obtain 25 ms. It describes sampled event-loop delay for that observation window, not total HTTP response time and not every future workload. Compare equivalent windows and correlate with request latency before assigning cause.
2. Requests slow down while CPU stays low and pool wait grows. What should you investigate first?
Show worked solution
Inspect connection usage, unreleased connections, query duration, and dependency capacity. More JavaScript worker threads do not fix a saturated database pool. Verify the suspected cause with pool metrics and a controlled reproduction before changing concurrency or database limits.
Key takeaways
- A slow request does not identify its cause.
- monitorEventLoopDelay samples how late scheduled loop work runs.
- If a profile points to repeated JSON serialization, reduce repeated work or cache an appropriate representation before introducing threads.