# Decoding Load Balancing Algorithms: Achieving Peak Performance and Scalability

Imagine that you have a website that has become incredibly popular, attracting millions of users. However, you face the challenge of having only a single server that cannot handle the entire workload.  
In order to meet the demands of millions of users, you must create additional servers. To effectively manage the workload across these multiple servers, you need to employ a technique called load balancing. Load balancing allows you to evenly distribute traffic, thereby preventing failures caused by overloading a specific resource.  
Load balancing can be likened to a "traffic police officer" who ensures that the website does not experience sudden traffic spikes.

## How does Load Balancing work?

The load balancer uses a predetermined pattern, known as a load balancing algorithm or method. This ensures no one server has to handle more traffic than it can process. Different algorithms manage the process using different techniques.

1. A user sends a request and attempts to establish a connection with the servers.
    
2. A load balancer receives the request and, based on the current algorithms and patterns, it routes the request to one of the servers in a server farm.
    
3. The server receives a connection request and responds to the client through the load balancer.
    
4. The load balancer receives the response and matches the client's IP with it.
    
5. When applicable, the load balancer handles SSL offload, which involves decrypting data using the Secure Socket Layer (SSL) encryption protocol. This process relieves the servers from having to perform the decryption themselves.
    
6. The process continues to repeat until the session is concluded.
    

## Load Balancing Algorithm

### Round Robin Method

This method of distributing incoming network traffic or requests across multiple servers or resources in a cyclical manner. It ensures that each server or resource receives an equal workload over time.

However, one limitation of round-robin is that it does not consider the actual load or capacity of each server. If some servers are more powerful or have higher processing capabilities than others, round-robin may not distribute the workload optimally. To address this, more advanced load balancing algorithms, such as weighted round-robin or dynamic load balancing, can be employed.

Here is a simple implementation of this algorithm

```cpp
class RoundRobinLoadBalancer
{
private:
    vector<string> servers;
    size_t currentServerIndex;

public:
    RoundRobinLoadBalancer() : currentServerIndex(0){};

    void addServer(const string &server)
    {
        servers.push_back(server);
    }

    string nextServer()
    {
        if (servers.empty())
        {
            return ""; // if server is empty
        }
        string nextServer = servers[currentServerIndex];
        currentServerIndex = (currentServerIndex + 1) % servers.size();
        return nextServer;
    }
};
```

You can get the full Code:- [https://github.com/adasarpan404/refactored-octo-bassoon/blob/master/LoadBalancingAlgorithm/roundRobin.cpp](https://github.com/adasarpan404/refactored-octo-bassoon/blob/master/LoadBalancingAlgorithm/roundRobin.cpp)

### Weighted Round Robin Method

  
The ***Weighted Round Robin*** (WRR) method is an extension of the basic round-robin algorithm used in load balancing. It allows for more granular control over how requests or traffic are distributed across servers or resources by assigning different weights or priorities to each server.

Limitations of the Weighted Round Robin method include:

1. Complexity: Implementing the Weighted Round Robin algorithm requires additional configuration and management compared to the basic round robin. Administrators need to assign appropriate weights to servers and adjust them dynamically based on server performance or capacity changes.
    
2. Lack of adaptability: The Weighted Round Robin method does not dynamically adapt to changes in server load or traffic patterns. Once the weights are set, they remain fixed until manually adjusted. If the capacity of servers changes over time, the load balancing might become suboptimal.
    
3. Uniformity assumption: The Weighted Round Robin method assumes that all servers within the pool have the same processing capabilities except for the assigned weights. If servers have significantly different performance characteristics or processing speeds, the distribution of workload based solely on weights may not be optimal.
    
4. Inefficient resource utilization: If servers with higher weights or priorities are idle or underutilized while servers with lower weights are overloaded, the WRR method may not effectively balance the load. This can result in inefficient resource utilization.
    

Here is a simple implementation of this algorithm

```cpp
struct Server {
    string name;
    int weight;

    Server(const string& n, int w) : name(n), weight(w) {}
};

class WeightedRoundRobinLoadBalancer {
private:
    vector<Server> servers;
    size_t currentServerIndex;
    int currentWeight;
    int gcd; // Greatest Common Divisor
    int maxWeight;

public:
    WeightedRoundRobinLoadBalancer() : currentServerIndex(0), currentWeight(0), gcd(0), maxWeight(0) {}

    // Utility function to calculate the greatest common divisor (gcd) of two numbers
    int getGCD(int a, int b) {
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }

    // Utility function to calculate the maximum weight among the servers
    int getMaxWeight() {
        int max = 0;
        for (const auto& server : servers) {
            if (server.weight > max) {
                max = server.weight;
            }
        }
        return max;
    }

    void addServer(const string& serverName, int serverWeight) {
        Server server(serverName, serverWeight);
        servers.push_back(server);
        gcd = getGCD(gcd, serverWeight);
        maxWeight = getMaxWeight();
    }

    string getNextServer() {
        if (servers.empty()) {
            return ""; // No servers available
        }

        while (true) {
            currentServerIndex = (currentServerIndex + 1) % servers.size();
            if (currentServerIndex == 0) {
                currentWeight -= gcd;
                if (currentWeight <= 0) {
                    currentWeight = maxWeight;
                    if (currentWeight == 0) {
                        return ""; // No servers available
                    }
                }
            }

            if (servers[currentServerIndex].weight >= currentWeight) {
                return servers[currentServerIndex].name;
            }
        }
    }
};
```

You can get the full Code:- [https://github.com/adasarpan404/refactored-octo-bassoon/blob/master/LoadBalancingAlgorithm/weightedRoundRobin.cpp](https://github.com/adasarpan404/refactored-octo-bassoon/blob/master/LoadBalancingAlgorithm/weightedRoundRobin.cpp)

### Least Connection Method

The Least Connection method is a load-balancing algorithm that aims to distribute incoming network traffic or requests to servers or resources based on their current connection load. It ensures that the server with the fewest active connections receives the next request, thereby distributing the load evenly across all available resources.

However, it's important to note some limitations of the Least Connection method:

1. Lack of consideration for server capacity: The Least Connection algorithm does not take into account the capacity or processing capabilities of servers. Servers with lower processing power may take longer to handle connections, even if they have a smaller number of active connections. This can lead to slower response times or potential bottlenecks.
    
2. Dynamic connection fluctuations: The number of active connections on a server can change rapidly and frequently. In high-traffic scenarios, servers may experience spikes in connection counts within short periods. The Least Connection method might not be able to adapt quickly enough to dynamically distribute the traffic optimally.
    
3. Persistence and session management: The Least Connection method does not inherently handle session persistence. If maintaining session affinity or sticky sessions is crucial for the application, additional mechanisms need to be implemented to ensure that subsequent requests from the same client are consistently directed to the same server.
    

Here is a simple implementation of this algorithm

```cpp
struct Server
{
    string name;
    int connectionCount;
    Server(const string &n) : name(n), connectionCount(0) {}
};

class LeastConnectionLoadBalancer
{
private:
    vector<Server> servers;
    size_t currentServerIndex;

public:
    LeastConnectionLoadBalancer() : currentServerIndex(0){};

    void addServer(const string &server)
    {
        servers.push_back(server);
    }

    string nextServer()
    {
        if (servers.empty())
        {
            return ""; // if server is empty
        }
        int minConnections = servers[0].connectionCount;
        int minIndex = 0;

        for (size_t i = 0; i < servers.size(); i++)
        {
            if (servers[i].connectionCount < minConnections)
            {
                minConnections = servers[i].connectionCount;
                minIndex = i;
            }
        }
        servers[minIndex].connectionCount++;

        return servers[minIndex].name;
    }
};
```

You can get the full Code:- [https://github.com/adasarpan404/refactored-octo-bassoon/blob/master/LoadBalancingAlgorithm/leastConnection.cpp](https://github.com/adasarpan404/refactored-octo-bassoon/blob/master/LoadBalancingAlgorithm/leastConnection.cpp)

### IP Hash Method

IP hash is a load-balancing method that distributes incoming network traffic or requests across multiple servers or resources based on the source IP address of the client. It aims to ensure that requests from the same client are consistently directed to the same server, allowing for session persistence and maintaining a stable client-server relationship.

However, there are a few considerations and limitations to be aware of when using IP hash load balancing:

1. Uneven distribution with uneven IP address distribution: If the distribution of client IP addresses is not uniform, the IP hash load balancing method may lead to an uneven distribution of requests across servers. Clients with IP addresses that hash to the same value will be directed to the same server, potentially causing a load imbalance if those clients generate a high volume of requests.
    
2. Changes in client IP addresses: Some network architectures or proxy setups can cause client IP addresses to change during a session. In such cases, IP hash load balancing may break the session persistence, as requests with different source IP addresses will be directed to different servers.
    
3. Scalability challenges: IP hash load balancing can pose challenges when adding or removing servers from the pool. As the hashing algorithm is typically static, the addition or removal of servers can cause a significant redistribution of requests across the servers, potentially impacting session persistence and requiring clients to establish new sessions.
    
4. Limited fault tolerance: In case of server failures, clients associated with the failed server may experience service disruption until their sessions are reestablished with a new server.
    

Here is a simple implementation of this algorithm

```cpp
class IpHashLoadBalancer
{
private:
    unordered_map<string, string> serverMap;

public:
    void addServer(const string &ipAddress, const string &serverName)
    {
        serverMap[ipAddress] = serverName;
    }
    string getServer(const string &ipAddress)
    {
        auto it = serverMap.find(ipAddress);
        if (it != serverMap.end())
        {
            return it->second;
        }
        return "";
    }
};
```

You can get the full Code:- [https://github.com/adasarpan404/refactored-octo-bassoon/blob/master/LoadBalancingAlgorithm/Iphash.cpp](https://github.com/adasarpan404/refactored-octo-bassoon/blob/master/LoadBalancingAlgorithm/Iphash.cpp)

### Dynamic Load Balancing

Dynamic load balancing is a load-balancing technique that adjusts the distribution of network traffic or requests across multiple servers or resources in real time based on the current load or performance of each server. It dynamically adapts to changing conditions to optimize resource utilization, improve performance, and ensure high availability.

Some benefits of dynamic load balancing include:

1. Efficient resource utilization: By dynamically adjusting the load distribution, dynamic load balancing optimizes resource usage, ensuring that servers are utilized effectively and evenly.
    
2. Scalability and elasticity: Dynamic load balancing enables the system to handle fluctuations in traffic by automatically scaling resources up or down based on demand. It provides the flexibility to add or remove servers as needed to maintain optimal performance.
    
3. Fault tolerance and high availability: The ability to detect and remove failed servers ensures that traffic is routed only to healthy servers, minimizing the impact of server failures and improving the availability of the system.
    
4. Adaptability to changing conditions: Dynamic load balancing adjusts the load distribution in real time, allowing the system to adapt to changing traffic patterns, variations in server capacity, or fluctuations in resource availability.
    

Here is a simple implementation of this algorithm

```cpp
struct Server {
    string name;
    double load;
};

class DynamicLoadBalancer {
private:
    vector<Server> servers;

public:
    void addServer(const string& serverName) {
        Server server{serverName, 0.0};
        servers.push_back(server);
    }

    string getNextServer(double load) {
        if (servers.empty()) {
            return ""; 
        }

        double minLoad = servers[0].load;
        size_t minIndex = 0;

        for (size_t i = 1; i < servers.size(); ++i) {
            if (servers[i].load < minLoad) {
                minLoad = servers[i].load;
                minIndex = i;
            }
        }
        servers[minIndex].load += load;

        return servers[minIndex].name;
    }
};
```

You can get the full Code:- [https://github.com/adasarpan404/refactored-octo-bassoon/blob/master/LoadBalancingAlgorithm/dynamicLoadBalancing.cpp](https://github.com/adasarpan404/refactored-octo-bassoon/blob/master/LoadBalancingAlgorithm/dynamicLoadBalancing.cpp)

## Benefits of Load Balancing

Other benefits of load balancing include the following:

* **Flexibility:** Besides directing traffic to maximize efficiency, load balancing delivers the flexibility to add and remove servers as demand dictates. It also makes it possible to perform server maintenance without causing disruption for users since traffic gets rerouted to other servers during maintenance.  
    
* **Scalability:** As the use of an application or website increases, the boost in traffic can hinder its performance if not managed properly. With load balancing, you gain the ability to add a physical or virtual server to accommodate demand without causing a service disruption. As new servers come online, the load balancer recognizes them and seamlessly includes them in the process. This approach is preferable to moving a website from an overloaded server to a new one, which often requires some amount of downtime.  
    
* **Redundancy:** In distributing traffic over a group of servers, load balancing provides built-in redundancy. If a server fails, you can automatically reroute the load to working servers to minimize the impact on users.
