# Client-side Load Balancer for Twitter

### Introduction <a href="#introduction-0" id="introduction-0"></a>

In the previous lesson, we conceived the design of Twitter using a dedicated load balancer. Although this method works, and we’ve employed it in other designs, it may not be the optimal choice for Twitter. This is because Twitter offers a variety of services on a large scale, using numerous instances and dedicated load-balancers are not a suitable choice for such systems. To understand the concept better, let’s understand the history of Twitter’s design.

#### Twitter’s design history <a href="#twitters-design-history-1" id="twitters-design-history-1"></a>

The initial design of Twitter included a monolithic (Ruby on Rails) application with a MySQL database. As Twitter scaled, the number of services increased and the MySQL database was sharded. A monolithic application design like this is a disaster because of the following reasons:

* A large number of developers work on the same codebase, which makes it difficult to update individual services.
* One service’s upgrade process may lead to the breaking of another.
* Hardware costs grow because a single machine performs numerous services.
* Recovery from failures is both time-consuming and complex.

With the way Twitter has evolved, the only way out was many microservices where each service can be served through hundreds or thousands of instances.

#### Client-side load balancing <a href="#client-side-load-balancing-2" id="client-side-load-balancing-2"></a>

In a client-side load balancing technique, there’s no dedicated intermediate load-balancing infrastructure between any two services with a large number of instances. A node requesting a set of nodes for a service has a built-in load balancer. We refer to the requesting node or service as a client. The client can use a variety of techniques to select a suitable instance to request the service. The illustration below depicts the concept of client-side load balancing. Every new arriving service or instance will register itself with a service registry so that other services are aware of its existence

<figure><img src="https://1055613405-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MlQzjDKRh_AaQvZflWz%2Fuploads%2FptK4OVfwZPblb7EK1Mts%2FScreenshot%202023-09-05%20at%2011.46.11%20PM.png?alt=media&#x26;token=27d04685-1a76-472d-aa79-b461e543b976" alt=""><figcaption></figcaption></figure>

In the diagram above, Service A has to choose a relatively less-burdened instance of Service B. Therefore, it will make use of the load balancer component inside it to choose the most suitable instance of Service B. Using the same client-side load balancing method, Service B will talk to other services. It’s clear that Service A is the client when it is calling Service B, whereas Service B is the client when it is talking to Service C and D. As a result, there will be no central entity doing load balancing. Instead, every node will do load balancing of its own.

**Advantages**: Using client-side load-balancing has the following benefits.

* Less hardware infrastructure/layers are required to do load balancing.
* Network latency will be reduced because of no intermediate hop.
* Client-side load balancers eliminate bandwidth bottlenecks. On the other hand, in a dedicated load balancing layer, all requests go through a single machine that can choke if traffic multiplies.
* Fewer points of failure in the overall system.
* There is no end users queue waiting for the resource (server) for the particular services because many load balancers are routing the traffic. Eventually, it increases the quality of experience (QoE).

Many real-world applications like Twitter, Yelp, Netflix, and others use client-side load balancing. We’ll discuss the client-side load balancing techniques used by Twitter in the next section.

### Client-side load balancing in Twitter <a href="#client-side-load-balancing-in-twitter-0" id="client-side-load-balancing-in-twitter-0"></a>

Twitter uses a client-side load balancer referred to as **deterministic aperture** that is part of a larger RPC framework called Finagle. **Finagle** is a protocol-agnostic, open-source, and asynchronous RPC library.

> **Note:** Consider the following question. What does the client-side load balancer of Twitter balance on? That is, what parameters are used to fairly balance the load across different servers?

Twitter primarily uses two distributions to measure the effectiveness of a load balancer:

1. The distribution of requests (OSI layer 7)
2. The distribution of sessions (OSI layer 5)

Although requests are the key metric, sessions are an important attribute for achieving a fair distribution. Therefore, we’ll develop techniques for fair distribution of requests as well as sessions. Let’s start with the simple technique of Power of Two Random Choices (P2C) for request distribution first.

#### Request distribution using P2C <a href="#request-distribution-using-p2c-1" id="request-distribution-using-p2c-1"></a>

The **P2C technique** for request distribution gives uniform request distribution as long as sessions are uniformly distributed. In P2C, the system randomly picks two unique instances (servers) against each request, and selects the one with the least amount of load. Let’s look at the illustration below to understand P2C.

<figure><img src="https://1055613405-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MlQzjDKRh_AaQvZflWz%2Fuploads%2FNDhGSlnEJuwkpOZzNHz0%2FScreenshot%202023-09-05%20at%2011.46.49%20PM.png?alt=media&#x26;token=7e4e5c32-0fff-4696-8c6d-74884099b032" alt=""><figcaption></figcaption></figure>

P2C is based on the simple idea that comparison between two randomly selected nodes provides load distribution that is exponentially better than random selection

Now that we’ve established how to distribute requests fairly, let’s explore different techniques of session distribution.

#### Session distribution <a href="#session-distribution-0" id="session-distribution-0"></a>

**Solution 1**: We’ll start our discussion with the **Mesh topology**. Using this approach, each client (load balancer) starts a session with all of the given instances of a service. The illustration below represents the concept of mesh topology.

<figure><img src="https://1055613405-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MlQzjDKRh_AaQvZflWz%2Fuploads%2Fy7KFNGIQqgDfLYvqd0Ov%2FScreenshot%202023-09-05%20at%2011.47.27%20PM.png?alt=media&#x26;token=d60f7632-b0f3-489e-9a53-3affb0183428" alt=""><figcaption></figcaption></figure>

Obviously, this method is fair because the sessions are evenly distributed. However, the even distribution of sessions comes at a very high cost, especially when scaled to thousands of servers. Apart from that, some unhealthy or stale sessions may lead to failures.

**Solution 2**: To solve the problem of scalability with solution 1, Twitter devised another solution called **random aperture**. This technique randomly chooses a subset of servers for session establishment.

<figure><img src="https://1055613405-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MlQzjDKRh_AaQvZflWz%2Fuploads%2FQIGUb5XO3NJd2N26Tfb8%2FScreenshot%202023-09-05%20at%2011.47.46%20PM.png?alt=media&#x26;token=076372c1-5c49-4bb0-9c80-650063274810" alt=""><figcaption></figcaption></figure>

Of course, random selection will reduce the number of established sessions as we can see in the diagram above. However, the question is, how many servers will be chosen randomly? This is not an easy question to answer and the answer varies, depending on the request concurrency of a client.

To answer the question above, Twitter installed a **feedback controller** inside the random aperture that decides the subset size based on the client’s load. As a result, we’re able to reduce the session load and ensure scalability. However, this solution isn’t fair. We can see the imbalance in the illustration above such that **Server 4** has no session whereas **Server 0** has three sessions.

This imbalance creates problems such as idle servers, high load on a few servers, and so on. Therefore, we need a solution that is both fair and scalable.

**Solution 3**: We learned from solution 2 that we can scale by subsetting the number of session establishments. However, we failed to do the session distribution fairly. To resolve the problem, Twitter came up with a **deterministic aperture** solution. The key benefits of this approach are:

* Little coordination is required between clients and servers to converge on a solution.
* Minimal disruption occurs if there are any changes in the number of clients or server instances for whatever reason.

For this solution, we represent clients and servers in a ring with equally distanced intervals, where each object on the ring represents the node or server labeled with a number. The illustration below shows what Twitter refers to as the discrete ring coordinates (that is, a specific point on the ring).

<figure><img src="https://1055613405-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MlQzjDKRh_AaQvZflWz%2Fuploads%2FYhnhxzsIKFTI1nh8trI5%2FScreenshot%202023-09-05%20at%2011.48.55%20PM.png?alt=media&#x26;token=ccc5a919-3c9f-4a47-bd43-1e56cddced11" alt=""><figcaption></figcaption></figure>

Now, we compose client and server rings to derive a relationship between them. We’ve also defined an aperture size of 3. Each client will create a session with three servers in the ring. Clients select a server by circulating clockwise in the ring. This approach is similar to consistent hashing, except that here we guarantee equal distribution of servers across the ring. Let’s see the illustration below where clients have sessions with servers chosen from the given list of servers, respectively.

<figure><img src="https://1055613405-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MlQzjDKRh_AaQvZflWz%2Fuploads%2FuHmFymLxLJCSWAPC7RWR%2FScreenshot%202023-09-05%20at%2011.49.22%20PM.png?alt=media&#x26;token=4fbe5c40-f888-4a2e-bb97-a285a15462f8" alt=""><figcaption></figcaption></figure>

In the diagram above, client 0 establishes sessions with servers 4, 5, and 0. Client 1 establishes sessions with servers 0, 1, and 2, whereas Client 2 establishes sessions with servers 2, 3, and 4. When the aperture rotates or moves in the given subset of arrays of the same size, each client’s service has new servers available to create sessions. The number of sessions established per server is shown in the figure below.

<figure><img src="https://1055613405-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MlQzjDKRh_AaQvZflWz%2Fuploads%2FCJnoFTdQkXQk8Bm3Rxvc%2FScreenshot%202023-09-05%20at%2011.49.38%20PM.png?alt=media&#x26;token=845e8f74-7e6f-4328-a3b9-89db2f1a4990" alt=""><figcaption></figcaption></figure>

We can see that there’s no idle server or server with a high load in the histogram graph above. Thus, we achieved a fair distribution for the clients belonging to the same service. It’s clear that we’ve half resolved the problem by improving the session distribution among servers as compared to the random aperture (solution 02). However, the problem of unfair distribution can escalate as the number of clients grows. In that case, some servers will get crowded when more than one client (service) talks to the back-end servers. Let’s see the illustration below to understand the problem.

<figure><img src="https://1055613405-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MlQzjDKRh_AaQvZflWz%2Fuploads%2FTiZXDESUpkzH7zyfQZgS%2FScreenshot%202023-09-05%20at%2011.49.55%20PM.png?alt=media&#x26;token=69bbe0bd-2a2d-44e7-98a1-5eae47fc6fc1" alt=""><figcaption></figcaption></figure>

The illustration above depicts that the problem compounded as we increased the number of clients (services). The solution is continuous ring coordinates where we derive relationships between clients and servers using overlapping ring slices, rather than specific points on the rings. The important thing here is that the overlapping of rings can be partial since servers on the ring may not have enough instances to divide among the clients equally. Let’s look at the illustration below.

<figure><img src="https://1055613405-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MlQzjDKRh_AaQvZflWz%2Fuploads%2FnWNomDhHH4Q7pbFMorQT%2FScreenshot%202023-09-05%20at%2011.50.16%20PM.png?alt=media&#x26;token=38fdbe69-d2c8-4deb-b2ac-2032ef591ccd" alt=""><figcaption></figcaption></figure>

The illustration above shows that clients 0 and 1 (symmetrically) share the same server (server 1). It means that there is fractional overlapping concerning the ring coordinates. However, this overlapping can lead to asymmetric sharing as well. Symmetric or asymmetric, the load balancing can be done through P2C. For instance, in the diagram above, servers 1 and 3 will receive half the load that server 2 gets. This means that a server can receive more or less load, depending on its overlapped slice size with a client.

Now, we know how to map clients to the subset of the servers. We’ll use P2C with continuous ring coordinates. The client will pick points (coordinations) in its range instead of picking the instance of the server. The selection process of clients to create sessions with backend servers are as follows:

1. Pick two coordinates (offset, offset + width) within its range and map these coordinates to the discrete instances (servers).
2. Select the one instance with the most negligible load from two chosen instances (P2C) by its degree of intersection.

> **Note:** Continuous aperture is scalable and fair at the cost of an additional session over boundary node due to partial overlapping. It is also able to achieve little coordination and minimal disruption.

Let’s look at the below table to get an overview of the discussed approaches.

### Summary of Solutions for Session Distribution

| **Approach**                                                                            | **Scalability** | **Fair distribution** | **Cost effectiveness** |
| --------------------------------------------------------------------------------------- | --------------- | --------------------- | ---------------------- |
| **Mesh topology**                                                                       | **✖️**          | **✔️**                | **✖️**                 |
| **Random aperture**                                                                     | **✔️**          | **✖️**                | **✔️**                 |
| <p><strong>Deterministic aperture</strong></p><p><strong>(discrete ring)</strong></p>   | **✔️**          | **weak**              | **✔️**                 |
| <p><strong>Deterministic aperture</strong></p><p><strong>(continuous ring)</strong></p> | **✔️**          | **✔️**                | **✔️**                 |

### Conclusion <a href="#conclusion-0" id="conclusion-0"></a>

This design problem highlights that tweaking the performance of common building blocks and utilizing the right (rich) combination of them is necessary for real-world services. Our needs from one service to the next will dictate which design point we choose in our solution space. In this chapter, we saw examples of how Twitter uses a combination of data stores and custom algorithms for load-balancing.
