Call
Home>Blogs & Insights>Node.js at High Traffic: Event-Loop Health, Backpressure, Connection Reuse, Workers, and Load Shedding
Node.js

Node.js at High Traffic: Event-Loop Health, Backpressure, Connection Reuse, Workers, and Load Shedding

Scale Node.js traffic by protecting the event loop, streaming with backpressure, bounding database/provider concurrency, reusing connections, isolating CPU work, load shedding, controlling retries, scaling across processes/containers, and measuring saturation.

July 30, 2024
7 min read
1 views
Lofingo Team
Node.js at High Traffic: Event-Loop Health, Backpressure, Connection Reuse, Workers, and Load Shedding

Node.js at High Traffic: Event-Loop Health, Backpressure, Connection Reuse, Workers, and Load Shedding

Node.js can handle high-traffic network services well when most request time is spent on asynchronous I/O and the application keeps CPU work, buffering, dependency concurrency, and memory growth bounded.

The wrong scaling model is:

> Node is non-blocking, so just accept more requests.

At high traffic, the bottleneck usually moves somewhere else: the event loop, database pool, downstream provider, memory buffers, queue backlog, or one CPU-heavy handler.

Protect the event loop first

All ordinary JavaScript callbacks in one Node process share the main event loop.

A long synchronous task can delay unrelated requests on that process.

Watch for:

  • large JSON parse/stringify work
  • synchronous crypto/compression/filesystem APIs
  • expensive regular expressions
  • big loops/transforms
  • unexpected CPU-heavy library code

Measure event-loop delay/utilization together with route latency so you can distinguish CPU blocking from a slow database or network dependency.

Keep request work small and asynchronous

Node performs best when handlers mostly coordinate I/O:

validate
query DB
call service
compose response

Avoid doing image processing, large document conversion, or other heavy computation on the main thread.

For CPU-intensive JavaScript, Node's official worker_threads API provides parallel threads. Use a worker pool, not a new worker per request, because worker creation has overhead.

Scale CPU-heavy work separately

For expensive jobs, choose one of:

  • worker-thread pool
  • separate child process
  • dedicated job worker service
  • native library where appropriate

This also lets the API apply independent concurrency limits.

If the CPU pool has eight useful workers, accepting 50,000 CPU jobs into memory is not scaling—it is queueing toward an OOM.

Bound database concurrency

High request concurrency can overwhelm a relational database long before Node itself is saturated.

Your database pool should have explicit limits.

Monitor:

  • connections in use
  • connection waits
  • query latency
  • lock contention
  • database CPU/I/O

Do not set the pool to thousands simply because the API has thousands of concurrent sockets.

The database's useful concurrency is usually much smaller than the HTTP layer's connection capacity.

Reuse outbound connections

Creating new TCP/TLS connections to every downstream service adds latency and resource cost.

Reuse HTTP clients/agents and database pools so keep-alive connections can be reused.

For every dependency configure:

  • connect/read/request deadlines
  • maximum sockets/connections where appropriate
  • retry policy

A connection pool is both a performance optimization and a concurrency boundary.

Streams are a major Node strength

Node's HTTP APIs are built around streams and are designed so large requests/responses do not have to be fully buffered in memory.

For large data, prefer:

read chunk
process chunk
write chunk

over:

read whole file into memory
transform whole object
send whole buffer

Streaming reduces peak memory and enables backpressure.

Respect backpressure

A producer can be faster than a consumer.

If application code keeps writing without respecting stream backpressure, buffered data can grow until memory becomes the bottleneck.

Classic Node writable streams signal pressure through write() and drain semantics; modern stream APIs also expose bounded/backpressure-oriented models.

The important rule is simple: do not produce unlimited data merely because writes are asynchronous.

Slow clients need bounded response buffers

At high traffic, some clients will read slowly.

If every slow client accumulates a large in-memory response queue, a small percentage of users can consume most process memory.

For APIs and WebSockets:

  • cap per-client pending bytes/messages
  • stream where possible
  • drop/coalesce disposable realtime updates
  • disconnect clients that cannot keep up and let them recover durable state later

Memory safety is part of traffic scalability.

Limit request body sizes early

Do not parse an unlimited JSON body and only later decide it is too large.

Set body/upload limits at the proxy/server/application boundary.

Large request limits protect against:

  • accidental huge payloads
  • memory spikes
  • expensive JSON parsing
  • decompression abuse

Different endpoints can have different limits; a file-upload endpoint and a login endpoint should not share the same maximum body size blindly.

Rate limiting should reflect cost

One request is not always one unit of work.

Examples:

GET /health        very cheap
POST /search       potentially expensive
POST /export       should likely become async job

Rate-limit by meaningful identity such as:

  • user
  • API key
  • tenant
  • IP for anonymous traffic

and consider separate limits for expensive operations.

Use load shedding before collapse

When dependencies or the service itself reach capacity, deliberately reject/defer work instead of letting latency and queues grow without limit.

Possible controls include:

  • maximum in-flight requests
  • bounded job queues
  • database pool limits
  • provider semaphores
  • 429 for rate policies
  • 503 for temporary capacity/unavailability where appropriate

Fast failure can preserve healthy traffic better than accepting work that cannot complete before client deadlines.

Retries can multiply high traffic

During an outage, naive retries amplify load.

A retry policy needs:

  • selected transient errors only
  • exponential backoff
  • jitter
  • maximum attempts
  • maximum total duration

Mutation retries also require idempotency keys or naturally idempotent semantics.

Do not retry every timeout automatically: the original operation may already have committed.

Use several Node processes/containers for CPU and fault isolation

One Node process executes ordinary JavaScript on one main event-loop thread.

For multi-core API capacity, run multiple processes or container replicas behind a load balancer.

Options include:

  • orchestrator/container replication
  • process manager
  • Node's cluster/child-process mechanisms where appropriate

The cluster module can distribute connections across worker processes, but modern container environments often make process/container replication easier to observe and manage externally.

Keep processes stateless where practical

If a request can arrive at any replica, avoid storing irreplaceable session or business state only in process memory.

Use shared durable/appropriate stores for:

  • sessions when server-side sessions are needed
  • jobs
  • message history
  • business data

In-process caches can remain local acceleration as long as correctness does not depend on every replica having identical memory.

WebSockets need cross-node fan-out

With multiple realtime gateway instances:

user A -> node 1
user B -> node 3

a message created on node 1 may need to reach a user connected to node 3.

Use a broker/pub-sub/routing layer for cross-instance fan-out and keep durable message state outside the socket process when offline recovery matters.

Sticky sessions alone do not solve cross-node delivery.

Gracefully drain deployments

A deployment that kills thousands of connections at once creates a reconnect spike.

A better flow:

  1. mark replica unready
  2. stop new traffic
  3. finish bounded in-flight HTTP requests
  4. close/drain long-lived connections according to protocol
  5. enforce a maximum grace period
  6. exit

Clients should reconnect with backoff and jitter rather than all reconnecting immediately.

Observe saturation, not only average latency

High-traffic dashboards should include:

  • requests/sec by route
  • p50/p95/p99 latency
  • status/error rate
  • event-loop delay/utilization
  • process CPU and RSS/heap
  • GC behavior
  • database pool waits
  • downstream socket/pool utilization
  • queue depth
  • active WebSockets
  • retry count
  • load-shed/rejected requests

Averages often look fine while a small overloaded dependency destroys tail latency.

Capacity-test the real dependency stack

A hello-world Node benchmark tells you little about a production request that performs SQL, authentication, JSON serialization, and several network calls.

Load-test:

  • realistic payload sizes
  • realistic DB queries
  • connection pools
  • external-service mocks with real latency distributions
  • failure/retry behavior
  • memory growth over time

Measure the first resource that saturates.

High-traffic checklist

Before increasing traffic, verify:

  • main event loop has no long synchronous work
  • CPU jobs have bounded worker capacity
  • database/provider concurrency is bounded
  • outbound clients reuse connections
  • request/response sizes are limited
  • streams respect backpressure
  • slow-client buffers are bounded
  • queues cannot grow forever
  • retries are bounded and idempotency-safe
  • multiple processes/replicas can serve stateless requests
  • realtime fan-out works across nodes
  • graceful drain/reconnect behavior is tested
  • saturation metrics are visible

Node.js scales best when you treat its event-driven runtime as a high-concurrency coordinator for bounded I/O, not as an excuse to create unlimited work. The highest-traffic systems stay healthy because every queue, buffer, dependency, and CPU path has a limit.

Official references

Tags:Node.jsScalabilityPerformanceBackpressureHigh TrafficBackend Engineering
Lofingo Team
Written by

Lofingo Team

Official writer and content strategist at Lofingo. Dedicated to delivering high-quality insights on technology and market trends.

Share your thoughts:

Discussion (0)

No comments yet. Be the first to start the discussion!