Skip to main content

Command Palette

Search for a command to run...

Designing High-Throughput Node.js Backends: Caching, Load Balancing, and Database Connection Pooling

Updated
5 min readView as Markdown
B
Senior Software Architect with 30+ years of experience building enterprise systems using Java, Spring Boot, and cloud-native technologies.

How modern Node.js systems handle millions of requests without melting down.

When people think about backend performance, they often focus on writing faster code.

But in production, the biggest bottlenecks are rarely CPU cycles.

They're usually:

  • Repeated database queries

  • Poor traffic distribution

  • Database connection exhaustion

  • Cache misses

  • Network latency

I've seen APIs that processed requests in 15ms locally suddenly respond in 800ms under production traffic.

The code wasn't the problem.

The architecture was.

Here's how high-performance Node.js systems are designed to scale.


1. Understand Where Time Is Actually Spent

A typical API request rarely spends most of its time executing JavaScript.

Instead, latency often looks like this:

Incoming Request
       │
       ▼
Load Balancer
       │
       ▼
Node.js API
       │
       ├── Redis Cache
       │
       └── Database

Typical latency breakdown:

Node.js execution      5–10 ms
Network               10–30 ms
Database              50–300 ms
Third-party APIs      100–1000 ms

Optimizing JavaScript while ignoring the database is like polishing your car while the engine is broken.


2. Cache Everything That Doesn't Change Frequently

Databases are expensive.

Memory is cheap.

A good cache can reduce database traffic by 80–95%.

Cache-Aside Pattern

Request
   │
   ▼
Check Redis
   │
 ┌─┴─────────────┐
 │ Hit           │
 ▼               ▼
Return Data   Query Database
                   │
                   ▼
             Save to Redis
                   │
                   ▼
              Return Result

Example:

const key = `user:${id}`;

let user = await redis.get(key);

if (!user) {
    user = await db.getUser(id);
    await redis.set(key, JSON.stringify(user), {
        EX: 300
    });
}

return JSON.parse(user);

Benefits:

  • Lower latency

  • Reduced database load

  • Better scalability

  • Lower cloud costs


3. Choose the Right Caching Strategy

Not all data should be cached the same way.

StrategyBest ForCache AsideRead-heavy APIsWrite ThroughStrong consistencyWrite BehindHigh write throughputRefresh AheadFrequently accessed data

For most REST APIs:

Cache Aside remains the simplest and most effective approach.


4. Load Balance Across Multiple Node.js Instances

A single Node.js process has limits.

Modern deployments typically look like this:

 Clients
             │
             ▼
      Load Balancer
      /     |      \
     ▼      ▼       ▼
 Node 1   Node 2   Node 3
     │      │       │
      └─────┼───────┘
            ▼
         Redis
            │
            ▼
        PostgreSQL

Popular load-balancing algorithms include:

  • Round Robin

  • Least Connections

  • IP Hash

  • Weighted Round Robin

  • Least Response Time

For stateless APIs, Round Robin is often sufficient.


5. Keep Your APIs Stateless

One of the most common scalability mistakes is storing user sessions in process memory.

Bad:

User
   │
   ▼
Node A
(Session stored locally)

If the next request reaches Node B:

User
   │
   ▼
Node B
Session Missing

Instead:

  • Store sessions in Redis

  • Use JWT tokens

  • Keep API servers disposable

Stateless services scale horizontally with minimal friction.


6. Use Database Connection Pooling

Creating a new database connection for every request is extremely expensive.

Instead:

Application
      │
      ▼
Connection Pool
 ┌───────────────┐
 │ Conn 1        │
 │ Conn 2        │
 │ Conn 3        │
 │ Conn 4        │
 └───────────────┘
      │
      ▼
 PostgreSQL

Using pg:

const pool = new Pool({
    max: 20,
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 2000
});

Benefits:

  • Faster response times

  • Lower CPU usage

  • Reduced connection overhead

  • Better database stability


7. Prevent the "Connection Storm"

Imagine:

1000 Requests
       │
       ▼
1000 Database Connections

Your database will likely become the bottleneck.

With pooling:

1000 Requests
      │
      ▼
20 Database Connections
      │
      ▼
Queue Requests

The system remains stable under heavy load.


8. Scale Reads Separately from Writes

For read-heavy applications:

 API
               │
      ┌────────┴────────┐
      ▼                 ▼
Primary DB        Read Replicas
   Write              Read

Examples:

  • Product catalogs

  • Analytics dashboards

  • Search APIs

  • User profiles

This approach significantly increases throughput without scaling the primary database vertically.


9. Monitor the Metrics That Actually Matter

High-performing systems are observable systems.

Track metrics such as:

  • Cache hit ratio

  • Database latency

  • Connection pool utilization

  • Request throughput (RPS)

  • P95/P99 latency

  • Error rates

  • Queue length

  • CPU utilization

  • Memory usage

If you can't measure it, you can't optimize it.


10. Combine These Techniques

A scalable production architecture often looks like this:

 Internet
                   │
                   ▼
           Load Balancer
                   │
      ┌────────────┴────────────┐
      ▼                         ▼
 Node.js API               Node.js API
      │                         │
      └────────────┬────────────┘
                   ▼
                Redis
                   │
                   ▼
         PostgreSQL Primary
                   │
          ┌────────┴────────┐
          ▼                 ▼
     Read Replica      Read Replica

Each layer solves a different problem:

  • Load balancing distributes traffic.

  • Caching eliminates unnecessary work.

  • Connection pooling protects the database.

  • Read replicas increase query capacity.

  • Stateless services enable effortless horizontal scaling.

Together, they transform a backend that struggles under thousands of requests into one that can reliably handle millions.


Final Thoughts

Performance isn't about writing clever JavaScript.

It's about reducing unnecessary work.

The fastest database query is the one you never execute.

The fastest server is the one that doesn't have to process the request.

And the most scalable architecture isn't the one with the biggest machines—it's the one that shares work intelligently across the system.

As traffic grows, success comes less from optimizing individual functions and more from designing an architecture where every component has a clear responsibility and scales independently.

What has been the biggest bottleneck you've encountered in scaling a Node.js backend—database performance, caching, load balancing, or something else? I'd love to hear your experiences in the comments.

1 views

More from this blog

B

Bill LIao's Blog

137 posts

A technical blog on modern backend development, software architecture, and practical AI agent workflows