Custom Software

Engineering High-Concurrency Backends: 100K+ Requests Per Second

Engineering High-Concurrency Backends: 100K+ Requests Per Second

High concurrency is achieved by removing queues, not by adding servers. The decisive levers are efficient runtimes, disciplined connection pooling, layered caching, explicit backpressure and load tests that measure tail latency rather than averages.

Find the queue, not the CPU

Throughput collapses when work queues somewhere: an undersized connection pool, a lock held across an input and output operation, an unbounded worker queue, a synchronous call to a slow dependency. Adding replicas does not remove the queue, it multiplies it and shifts the bottleneck downstream.

Instrument with queueing in mind. Measure saturated resource utilisation, queue depth and wait time rather than only CPU and memory. If utilisation on a resource is above roughly 70 percent under peak, latency will rise non-linearly and the system is already fragile.

Little's law is the practical guide: concurrency equals arrival rate multiplied by service time. Halving service time doubles the throughput a fixed pool can sustain, which is why optimising the slow query beats adding another pod.

Runtime and data access

Compiled runtimes with predictable memory behaviour, such as Go and Rust, reduce per-request overhead and eliminate stop-the-world pauses that dominate tail latency. Interpreted runtimes can reach similar throughput but usually require more replicas and more careful tuning.

Most backend latency, however, is data access. A missing index, an N plus one query pattern or an unbounded fetch will cost more than any runtime difference. Query plans, index coverage and payload size should be measured before language or framework preferences are debated.

Connection pooling deserves particular attention. Pools that are too small serialise requests; pools that are too large overwhelm the database with context switching. Size them from measured database capacity, and use a pooler in front of PostgreSQL when connection counts from many replicas become unmanageable.

Common bottlenecks and their symptoms
Bottleneck Symptom Fix
Undersized connection poolRequests wait before any work beginsSize from measured DB capacity, add pooler
Missing indexLatency grows with table sizeAdd covering index, verify plan
N plus one queriesLinear query count per itemBatch or join, enforce in review
Lock held over I/OSpiky p99 under loadReduce critical section, use optimistic concurrency
Unbounded queueMemory growth, then collapseBounded queues with load shedding
Synchronous slow dependencyCascading timeoutsTimeout budget, circuit breaker, async path

Caching in layers

Caching is the most effective way to raise throughput, and the most effective way to create subtle correctness bugs. Cache at the edge for public, cacheable content, in-process for hot immutable data, and in a shared store for expensive computations that many instances need.

Every cache needs an explicit invalidation strategy chosen at design time: time-based expiry, versioned keys, or write-through invalidation. Versioned keys are usually the safest because they make invalidation atomic and avoid stale reads during a partial rollout.

Protect the origin against cache stampedes. When a hot key expires, thousands of concurrent requests can miss simultaneously and hammer the database. A short lock, request coalescing or probabilistic early expiry prevents that.

  • Cache public content at the edge with explicit cache-control directives.
  • Use versioned cache keys so invalidation is atomic.
  • Coalesce concurrent misses to prevent stampedes.
  • Treat a cache miss as a normal path, never as an error.

Backpressure, load shedding and honest load tests

Every queue must be bounded and every dependency call must have a timeout. Without a timeout, an unhealthy dependency consumes worker capacity until the caller fails too, which is how a minor outage becomes a total one.

Decide in advance what to shed: health checks and authenticated core transactions should survive, while analytics, exports and low-priority batch work should be rejected early. Fast rejection with a clear response is better than slow failure for everyone.

Load test with production-shaped traffic, measure percentiles not averages, and test beyond the expected peak until the system degrades predictably. A system that has never been pushed past its limit has an unknown failure mode.

  • Bound every queue and set timeouts on every outbound call.
  • Shed low-priority work explicitly before it consumes capacity.
  • Test with production-shaped payloads and realistic think time.
  • Report p50, p95 and p99, and record where degradation begins.

Frequently asked questions

Which language is best for high-concurrency backends?

Go and Rust offer predictable latency and low per-request overhead, which helps tail latency. The choice matters far less than data access efficiency, pool sizing and caching, which dominate real throughput.

How do I reduce p99 latency?

Find the queue rather than optimising averages. Typical causes are undersized connection pools, missing indexes, locks held across input and output, unbounded queues and synchronous calls to slow dependencies without timeouts.

How much load testing is enough?

Test past the peak you expect until behaviour degrades, and record where that happens and how it degrades. A system that has never been pushed to failure has an untested and therefore unknown failure mode.

References

Related engineering guides

Engineer your next strategic platform

Estimate your project investment and delivery timeline instantly, or schedule an architectural discovery call with our engineering leadership.

Launch Cost Estimator Speak with an Architect