What is LOAD BALANCING? ⚖️
As web applications grow and attract more traffic, a single computer or server eventually reaches its hardware limits. To maintain performance, developers scale horizontally by adding more servers. However, this introduces a core architectural challenge: how do you evenly distribute incoming client requests across multiple backend servers without crashing or breaking cached state?
1. The Single Server Status Quo and the Scaling Problem
Imagine you build a successful algorithm—such as a facial recognition filter that adds a mustache to user photos—running on a single computer.
Client Mobile App
|
| sends HTTP request (e.g., image upload)
v
Single Server (Runs Algorithm)
|
| returns JSON/Image response
v
Client Mobile App receives response
- The Setup: A user connects via a mobile device, sending a request to your server. The server processes the request and returns a response.
- The Breaking Point: Your application goes viral. Thousands of concurrent requests flood in. Your single computer's CPU and memory max out, leading to latency, timeouts, and dropped connections.
- The Solution: You earn enough revenue to purchase additional computers (servers) to handle the surge in traffic. But this immediately triggers a routing dilemma: Which server should handle the next incoming request?
2. Core Mental Model: What is Load Balancing?
At its core, load balancing is the process of taking servers and distributing incoming client requests evenly across all of them so that no single server becomes a bottleneck.
Real-Life Analogy: Supermarket Checkout Lanes
Think of a busy supermarket. If there is only one cashier open (a single server), the queue stretches all the way down the aisle. When the store manager opens three new cash registers, they need a greeter (the load balancer) to direct arriving shoppers to the shortest line. The goal is to keep the number of items (load) balanced evenly across all cashiers so that checkout times remain fast for everyone.
3. The Naive Approach: Modulo-Based Hash Routing
To distribute requests evenly, the standard textbook approach uses a hash function combined with a modulo operator.
How Modulo Hashing Works
- Every incoming request includes a unique Request ID (), or the client generates a uniformly random number.
- You pass the Request ID through a hash function to generate a large pseudo-random integer.
- You use the modulo operator with the total number of servers () to determine the target server index:
Code Implementation (Naïve Modulo Router)
class NaiveLoadBalancer:
def __init__(self, server_count: int):
self.server_count = server_count
def route_request(self, request_id: int) -> int:
"""
Hashes the request ID and applies modulo N
to determine the target server index.
"""
hash_value = hash(request_id)
server_index = abs(hash_value) % self.server_count
return server_index
# Example usage with 4 servers (S0, S1, S2, S3)
balancer = NaiveLoadBalancer(server_count=4)
print(balancer.route_request(10)) # Maps to Server 3 (e.g., 3 mod 4 = 3)
print(balancer.route_request(20)) # Maps to Server 3 (e.g., 15 mod 4 = 3)
print(balancer.route_request(35)) # Maps to Server 0 (e.g., 12 mod 4 = 0)
- Uniform Distribution: Assuming the hash function is uniformly random, requests will be distributed evenly as across all servers, resulting in an optimal load factor of .
4. The Hidden Failure: What Happens When You Add a Server?
While modulo hashing works perfectly when the number of servers () remains constant, it breaks down catastrophically the moment you scale your infrastructure by adding or removing a server.
Step-by-Step Breakdown of Re-sharding Chaos
Imagine you scale from servers to servers (adding Server ). Because the denominator in our modulo operation changes from 4 to 5, the remainder calculation alters drastically:
The Pie Chart Analogy of Redistribution
Visualize your hash space as a circular pie chart divided equally among your servers:
- Initial State (): Each server owns 25% of the hash ring (buckets).
- Adding a 5th Server (): Each server now needs to own 20% of the ring. To achieve this, portions of buckets must be stripped away from and handed over to the new server .
- The Cost: In a standard modulo scheme, changing invalidates and shuffles almost every single mapping in the system. The total cost of remapping operations approaches 100% of the request space.
5. Production Reality: Why Modulo Hashing Destroys Caches
In real-world distributed systems, Request IDs are never truly random. They encapsulate user-specific information, such as a User ID ().
The Cache Disastrous Collapse
- Local Caching: Because user requests consistently map to the same server thanks to stable hashing, servers cache user profiles locally in memory to minimize database queries.
- The Scaling Event: You add Server , changing the server count to .
- The Catastrophe: Because modulo hashing alters virtually all hash mappings, User is now routed to Server instead of .
- Result: Server has no record of in its local cache. All useful cache information across the entire cluster is instantly invalidated and rendered useless, causing a massive spike in database load (a cache stampede/thundering herd problem).
6. Naive vs. Advanced Hashing Strategy Comparison
7. Summary and Key Takeaways
- The Scaling Imperative: As traffic grows, systems must scale horizontally by adding multiple backend servers to process concurrent requests.
- Load Balancing Definition: The architectural practice of evenly distributing incoming client request load across servers.
- The Modulo Strategy: Traditionally uses to achieve uniform distribution ( load factor).
- The Fatal Flaw: Standard modulo hashing fails when servers are added or removed, because changing shifts nearly 100% of the hash space.
- Cache Annihilation: In production, request IDs link to User IDs; shifting hash mappings dumps all local server caches, overwhelming underlying databases.
- The Need for Advanced Architecture: To prevent massive cache invalidation during scaling events, systems require Consistent Hashing to ensure only minimal key remapping occurs.