Module A-14·25 min read

PM2 graceful reload, pg-pool sizing mathematics, Redis Cluster client configuration, and Kubernetes liveness vs readiness probe implementation.

Module 13 — Advanced Connection Pooling & Process Management

What this module covers: Your Node.js service processes 50,000 events/second. Every event requires a database write. That means 50,000 PostgreSQL queries/second. A naive implementation opens a new connection for every query — connection establishment costs 5–10ms and PostgreSQL cannot handle 50,000 simultaneous connections. Connection pooling is the mechanism that multiplexes thousands of queries over dozens of connections. This module covers pg-pool sizing mathematics, Redis connection management, PM2 cluster lifecycle, graceful shutdown sequences, and the Kubernetes liveness vs readiness probe distinction that prevents traffic routing before your service is ready.


Why Connection Pooling Is Not Optional

Every database connection is expensive:

  • PostgreSQL: spawns a new OS process per connection (~5MB RAM, ~10ms setup cost)
  • Redis: opens a new TCP socket per connection (~0.5ms setup, persistent)
  • MongoDB: opens a new socket, negotiates auth (~2ms)

Without pooling at 50,000 queries/second:

  • 50,000 new PostgreSQL connections/second × 10ms = 500 seconds of connection overhead per second of operation — impossible
  • At any instant, thousands of concurrent queries would require thousands of simultaneous PostgreSQL processes: 5,000 connections × 5MB = 25GB RAM just for PostgreSQL process overhead

Connection pooling solves this by reusing a small set of long-lived connections for many short-lived queries.


pg-pool: PostgreSQL Connection Pool

pg-pool (used internally by the pg package) maintains a pool of PostgreSQL connections.

The Mathematics of Pool Sizing

text

This is Little's Law applied to database connections. If you need more throughput or have longer queries, you need more connections — up to the database's limit.

javascript

Monitoring Pool State

javascript

Alert thresholds:

  • db_pool_utilization_ratio > 0.8: pool approaching capacity
  • db_pool_waiting_count > 0: pool exhausted — immediate action needed
  • db_pool_waiting_count > 50: serious saturation — likely causing timeouts

Pool Exhaustion: The Silent Performance Killer

When all pool connections are busy, pool.connect() waits. At high throughput, this creates a latency cascade: query takes 15ms → pool slot held for 15ms → at 50 queries/pool × 15ms = 750ms of total query time per second → pool fully booked at 3,333 queries/second.

If your target is 5,000 queries/second:

required_pool_size = ceil(5,000 × 0.015) = 75 connections

Insufficient pool size manifests as:

  • P99 latency spikes (queries waiting for a connection)
  • connectionTimeoutMillis errors (pool exhausted for too long)
  • Not as high CPU or network errors — the service appears healthy from the outside while queries queue silently

Connection Lifecycle: maxUses and Health Checks

Long-lived database connections accumulate state: temporary tables (from past SET commands), prepared statements, and sometimes server-side session state.

maxUses: 7500 causes pg-pool to destroy and recreate a connection after 7,500 uses. This prevents state accumulation without the overhead of creating a new connection per query.

javascript

Redis: Connection Management with ioredis

Redis connections are cheaper than PostgreSQL but still need pooling for high-throughput usage.

javascript

Pipelining: Batching Redis Commands

javascript

PM2: Process Management in Production

PM2 manages Node.js processes: starts them, restarts them on crash, monitors memory usage, and provides zero-downtime reload.

Ecosystem Configuration

javascript

Zero-Downtime Reload vs Restart

bash

How reload works:

  1. PM2 starts a new worker instance
  2. New worker starts, begins accepting connections (via SO_REUSEPORT)
  3. PM2 sends SIGTERM to old worker
  4. Old worker: stops accepting new connections, finishes in-flight requests
  5. Old worker exits cleanly
  6. Repeat for each worker in the cluster

This is why implementing graceful shutdown correctly is essential for pm2 reload to work.


Graceful Shutdown: The Complete Sequence

A Node.js service has multiple resources that must be closed in the correct order on shutdown.

javascript

Kubernetes: Liveness vs Readiness Probes

Kubernetes uses two types of health probes with critically different semantics.

Liveness probe: "Is this pod still alive and should it be restarted?"

  • If liveness fails: Kubernetes kills and restarts the pod
  • Use for: detecting deadlocks, infinite loops, hung processes
  • Be conservative: don't check dependencies (database) in liveness

Readiness probe: "Is this pod ready to receive traffic?"

  • If readiness fails: Kubernetes removes the pod from load balancer rotation (but does NOT restart it)
  • Use for: checking database connectivity, cache warmup, dependency availability
  • A pod can be live but not ready (database is down → no traffic, but no restart needed)
javascript
yaml

The startup scenario:

text

Production Incident: Pool Exhaustion Masking as Latency Spike

Context: A UPI payment service with max: 20 connections in the pool, targeting 3,000 queries/second at 8ms average query time.

The math check no one did:

text

What happened: During normal operation, the service handled load because average query time was actually 6ms (not 8ms as designed for). Required pool: 3,000 × 0.006 = 18 connections. The pool of 20 had a 10% safety margin.

During a marketing push, traffic doubled to 6,000 queries/second. Required pool: 6,000 × 0.006 = 36 connections. Available: 20.

text

Symptom: p99 latency jumped from 12ms to 5,200ms (the connection timeout). CPU was at 30%. Database was at 25% capacity. The bottleneck was invisible without monitoring db_pool_waiting_count.

The fix:

javascript

Additionally, connectionTimeoutMillis was reduced from 5,000ms to 2,000ms. The long timeout was hiding the exhaustion — queries waited 5 seconds before failing, making the P99 look catastrophic while masking the root cause (pool size).


Summary

ConceptKey Takeaway
Pool sizingpool_size = target_qps × avg_query_duration_sec. Recheck when traffic grows.
db_pool_waiting_countFirst metric to add. Any waiting = pool undersized for current load.
connectionTimeoutMillisSet to 2–3s. Long timeouts hide exhaustion and make P99 look catastrophic.
maxUsesRecycle connections after N uses to prevent server-side state accumulation.
ioredis pipeliningBatch Redis commands in one RTT. Use pipeline() for multi-key operations.
PM2 pm2 reloadRolling restart with zero downtime. Requires correct graceful shutdown implementation.
Graceful shutdownStop accepting → drain Kafka → drain DB pool → close Redis → exit. Hard timeout as fallback.
Liveness probeIs the process alive? Check: process responsiveness only. Never check dependencies.
Readiness probeIs the service ready for traffic? Check: database, Redis, Kafka. Fail gracefully during startup.
pool.end()Waits for all active queries to complete before closing connections. Safe for shutdown.

The connection layer determines capacity. Module 14 covers the other end of the scale — ultra-lightweight V8 isolates for edge deployments that need zero cold starts and globally distributed intake proxies.

Next: Module 14 — Edge Runtime Ingestion & V8 Isolates →


Knowledge Check

When configuring a Kubernetes deployment for a Node.js service, what is the critical difference between a liveness probe and a readiness probe, and what should be checked in each?


A Node.js service has an average database query duration of 15ms. During a marketing event, traffic is expected to spike to 4,000 database queries per second. According to Little's Law applied to pool sizing, what is the mathematical minimum PostgreSQL connection pool size required to handle this throughput without queuing?


Why is it crucial to set a relatively short connectionTimeoutMillis (e.g., 2,000ms) on the PostgreSQL connection pool rather than a long default (e.g., 30 seconds)?

Test your knowledge with more question sets

Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.