How do you find and fix performance bottlenecks in a Node app?
3 minadvancednodejsperformanceprofilingevent-loopmemory-leak
Quick Answer
Measure first: profile CPU with --prof/--inspect or clinic.js, watch event-loop lag, and track memory for leaks with heap snapshots. Common fixes are removing event-loop blocking (offload CPU work), eliminating N+1 queries, adding caching, streaming large data, and tuning concurrency.
Detailed Answer
Answer: Optimize based on measurement, not guesses.
1. Profile CPU usage:
node --prof app.jsthennode --prof-processfor a text report.node --inspect+ Chrome DevTools, or clinic.js (clinic flame,clinic doctor) for flame graphs.- Look for hot functions and synchronous work on the request path.
2. Monitor event-loop lag: High lag means something is blocking the loop.
const { monitorEventLoopDelay } = require('perf_hooks');
const h = monitorEventLoopDelay(); h.enable();
setInterval(() => console.log('loop p99 (ms):', h.percentile(99) / 1e6), 5000);
3. Track memory / find leaks:
- Watch
process.memoryUsage()(RSS/heapUsed) over time; steady growth suggests a leak. - Take heap snapshots in DevTools and compare to find retained objects (common causes: unbounded caches/
Maps, un-removed event listeners, closures holding large data, growing global arrays).
Common bottlenecks and fixes:
| Symptom | Likely cause | Fix |
|---|---|---|
| High event-loop lag | CPU work on main thread | offload to worker_threads/queue; chunk work |
| Slow endpoints, high DB load | N+1 queries, missing indexes | batch/join queries, add indexes, cache |
| Memory grows unbounded | leak (caches/listeners/closures) | bound caches (LRU+TTL), remove listeners |
| High memory on big payloads | buffering large data | use streams |
| Latency spikes under load | thread-pool contention, no pooling | raise UV_THREADPOOL_SIZE, use connection pools, limit concurrency |
Also: enable gzip/compression, use HTTP keep-alive and DB connection pools, and add caching/CDN. Always re-measure after each change to confirm the win.