Back to Archive
May 2, 2026 12 min read Performance Engineering

Node.js Profiling.

Sub-Millisecond Performance: Profiling and Optimizing Node.js Under Load

Share Architecture:

Your Node.js API handles 500 requests per second without breaking a sweat. Then you add a logging middleware. Suddenly, it maxes out at 300 requests per second. You didn't add much code—just a few JSON.stringify calls.

This is the invisible tax of performance. A single optimization might be "only" 2ms per request. At 500 requests/second, that's 1,000 milliseconds of cumulative wasted time every second. Ten small inefficiencies compound to become a bottleneck.

The only way to find these invisible taxes is **profiling**. Not guessing. Not intuition. Profiling: running your code under load, measuring CPU and memory usage, and identifying exactly where time is spent.

The Problem: Invisible Inefficiencies

Your code has hot paths—functions that run thousands of times per minute. If a hot path is 1ms slower than optimal, that's 1,000ms wasted per minute. The problem is that inefficiencies are **invisible** without instrumentation.

For OrdersPilot's webhook handler, I guessed the bottleneck was database writes. Latency was at 120ms. Only when I profiled the actual code did I discover the real culprit: **JSON serialization in logs**. The handler was logging every event, and each log call did `JSON.stringify` on the entire order object (hundreds of fields).

The Solution: Profiling Tools

Clinic.js

The gold standard. Visualizes CPU, memory, and event loop delays in interactive reports.

0x

Flamegraph generator. Shows exactly which functions are wide (slow) or narrow (fast).

Autocannon

Load testing tool. Simulates concurrent requests to profile your app under real pressure.

Case Study: OrdersPilot Optimization

Before Optimization

Latency120ms
CPU Usage45%
Throughput500 w/m

After Optimization

Latency50ms
CPU Usage18%
Throughput1,000 w/m

The Optimization Path

// 1. Async Logging & Selective Serialization

// Log IDs, not full objects
logger.info({ orderId, event });

// Serialize only needed fields for external payloads
const payload = {
  orderId: order._id,
  status: order.status,
  timestamp: Date.now()
};

// 2. Connection Pooling (MongoDB)

const mongoClient = new MongoClient(url, {
  maxPoolSize: 50,
  minPoolSize: 10,
  maxIdleTimeMS: 60000
});

The Profiling Checklist

01BaselineMeasure current latency with `autocannon`.
02ProfileRun `clinic doctor` or `0x` under load.
03IdentifyFind the hot path consuming the most time (80/20 rule).
04OptimizeTarget the biggest bottleneck first.
05VerifyRe-measure latency and throughput.
06RepeatProfile again, find the next bottleneck.

Lessons Learned

01

Profiling Before Optimizing Saves Weeks

I could have spent weeks on database optimizations. Profiling found the real culprit (log serialization) in 30 minutes.

02

80/20 Rule Is Real

The top 3 bottlenecks accounted for 90% of the latency. Focus on the biggest first.

03

Event Loop Blocking Is Silent

When synchronous work blocks the event loop, requests just 'feel slow' because they queue and wait. Profiling reveals these hidden queues.

Performance optimization is systematic, not mystical. Measure, profile, identify, optimize, repeat. Without profiling, you're just guessing.