Skip to content
MaziiDev

Scaling Big Systems: How to Handle Millions of Users

Discover how scaling big systems to millions of users requires mastering load balancing, database sharding, and caching strategies without hype.

Godswill Nathaniel

Software engineer · · 4 min read

𝕏fWin

When applications grow from thousands of daily requests to millions of concurrent active users, conventional monolithic architectures and single-node databases inevitably hit physical limits. In my view, scaling big systems is not about adopting every trending framework or adding servers blindly; it requires understanding the core bottlenecks of compute, network, and storage, then applying proven patterns like load balancing, database sharding, and efficient caching with disciplined engineering judgment.

Every backend application eventually encounters resource exhaustion. When an API server runs out of CPU or memory, the immediate instinct is often to upgrade the machine instance. Vertical scaling, however, is finite and expensive. Eventually, you reach hardware limits, or worse, a single point of failure where a database lock or memory leak takes down the entire application.

Horizontal scaling—adding more application instances behind a router—solves compute bottlenecks, but it introduces distributed state challenges. If Server A handles a user session, how does Server B know about it? This is where stateless application design becomes non-negotiable. Storing session data in a centralised, fast key-value store or using JSON Web Tokens allows any request to land on any server instance safely.

Example: Picture a payments API processing fifty thousand requests per minute during peak trading hours. If all requests hit a single PostgreSQL instance without connection pooling, connection limits are exhausted within seconds. Routing traffic across four stateless FastAPI containers and pooling database connections via PgBouncer prevents thread starvation and stabilizes latency.

How Does Scaling Big Systems Handle Load Balancing?

Once you have multiple application servers, you need a reliable traffic router. A load balancer sits between clients and your server cluster, distributing incoming requests based on algorithms like round-robin, least connections, or IP hashing.

However, layer-4 (transport layer) load balancing differs fundamentally from layer-7 (application layer) routing. Layer-7 load balancers can inspect HTTP headers, paths, and cookies, enabling intelligent routing where read requests go to cache-heavy replicas and write requests go to primary database nodes.

from fastapi import FastAPI, Request
import time

app = FastAPI()

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response

@app.get("/health")
async def health_check():
    return {"status": "healthy"}

This FastAPI middleware snippet records request processing time to help downstream load balancers monitor node latency and health. Notice how simple performance instrumentation can be built directly into application layers without expensive monitoring tools. When designing distributed backends, keeping observability lightweight prevents your monitoring infrastructure from adding unnecessary load to the system.

Database Sharding and Horizontal Storage for Scaling Big Systems

Application servers are easy to scale because they are stateless. Databases are hard to scale because they maintain state, consistency, and disk storage. When a single relational database runs out of disk space or IOPS, you must split the data.

As detailed in the MongoDB documentation on sharding, horizontal scaling through sharding distributes data across multiple machines to support massive datasets and high-throughput operations. Sharding strategies generally fall into three categories:

  • Range-based sharding: Dividing data by ranges of a key (e.g., user IDs 1–1,000,000 on node A).
  • Hash-based sharding: Passing a key through a hash function to assign it deterministically to a specific shard.
  • Directory-based sharding: Using an external lookup service to track which shard holds specific records.

Choosing the right shard key is critical. A poor shard key leads to hot spotting, where ninety percent of your traffic hits a single database node while other machines sit idle.

Caching Strategies Without Illusion

Caching is often touted as a silver bullet for performance, but adding Redis or Memcached without a clear eviction and invalidation strategy will introduce subtle data bugs. If you cache database query results, you must answer two difficult questions: When does the cache expire? And how do you invalidate it when underlying data changes?

Blindly setting a sixty-second TTL can serve stale financial balances or user profiles, leading to data corruption bugs that are notoriously difficult to debug. Read-through and write-through caching patterns help maintain consistency by ensuring the cache layer stays synchronised with the primary datastore. Use caching to reduce read load on immutable or slowly changing data, not as a bandage for inefficient SQL queries. For broader architectural context on handling complex data safely, I examine similar reliability challenges in my guide on blockchain architecture and consensus patterns.

Handling Database Failures and Replication Lag

When scaling big systems across distributed regions, data replication lag becomes an inevitable operational hurdle. As outlined in the InfoQ presentation on Netflix observability and further explored in the InfoQ presentation on Eve Online telemetry sampling, complex distributed architectures require continuous telemetry to detect when read replicas fall out of sync with primary data nodes. If an application performs a write operation on the primary database and immediately attempts to read that data from a lagging replica, users will experience missing updates or inconsistent UI states.

Example: Say a high-volume e-commerce platform processes a flash-sale transaction. If the inventory counter update on the primary database takes five hundred milliseconds to replicate to read nodes, users checking their order history immediately after checkout might see stale inventory data. Routing read requests for critical transactional paths directly to the primary node or using read-after-write consistency patterns prevents this race condition.

Managing connection pools, statement timeouts, and circuit breakers protects your primary database from cascading failures when downstream services experience latency spikes. Engineers must design systems to degrade gracefully rather than fail completely when a single component exhausts its resource limits.

Key takeaways

  • Scaling big systems requires stateless application layers, intelligent load balancing, and careful database sharding to distribute traffic and storage safely.
  • Choosing the right shard key prevents database hot spotting and ensures balanced resource utilization across distributed nodes.
  • Caching reduces database read load, but requires strict invalidation and TTL policies to prevent stale data bugs in production.