Past a certain point, web applications outgrow a single server deployment. Companies either want to increase their availability, scalability, or both! To do this, they deploy their application across multiple servers with a load balancer in front to distribute incoming requests. Big companies may need thousands of servers running their web application to handle the load.
In this post we're going to focus on the ways that a single load balancer might distribute HTTP requests to a set of servers. We'll start from the bottom and work our way up to modern load balancing algorithms.
# Visualising the problem
Let's start at the beginning: a single
When you're ready, press
For a lot of websites, this setup works just fine. Modern
The faster rate causes some
No more
# When round robin doesn't cut it
In the real world, it's rare for
Let's see what happens when we vary
While most
Queues help us deal with uncertainty, but it's a trade-off. We will
If a queue is full when a
Everything said above about
The less powerful
# Improving on round robin
It's possible to tweak round robin to perform better with variance. There's an
algorithm called "weighted round robin" which involves getting humans
to tag each
In this next simulation, we use each
While this handles the variance of
It stands to reason that if one
The result is very similar to the initial weighted round robin simulation, but there's no need to specify the weight of each server up front. This algorithm will also be able to adapt to changes in server performance over time. This is called "dynamic weighted round robin."
Let's see this new algorithm in practice, with high variance in both
If you pick a
# Moving away from round robin
Dynamic weighted round robin seems to account well for variance in both
This is an algorithm called "least connections".
Because the
This algorithm performs well regardless how much variance exists. It cuts
through uncertainty by keeping track of what each
Let's see this in action with the same parameters we gave the dynamic weighted
round robin algorithm above. Again, these parameters are randomised. Press
While this algorithm is a great balance between simplicity and performance,
it's not immune to
# Optimizing for latency
So far we've focused on not
People want websites to load fast, so optimising for lower latencies is beneficial to most businesses. To measure latency, it's common to look at different "percentiles." For example, the 50th percentile, also called the "median," is defined as the duration for which 50% of requests are below, and 50% are above.
I ran 3 simulations with identical parameters for 60 seconds and took a variety of measurements every second. Each simulation varied only by the load balancing algorithm used. Let's compare the medians for each of the 3 simulations:
You might not have expected it, but round robin has the best median latency. If we weren't looking at any other data points, we'd miss the full story. Let's take a look at the 95th and 99th percentiles. Remember, 95th percentile is the latency value where only 5% of requests had a higher latency. 95th and 99th percentile represent the slower end of the distribution.
Note: there's no colour difference between the different percentiles for each load balancing algorithm. Higher percentiles will always be higher on the graph.
We see that round robin doesn't perform well in the higher percentiles. How can it be that round robin has a great median, but bad 95th and 99th percentiles?
In round robin, the state of each
We can take a look at the full data in histogram form:
I chose the parameters for these simulations to avoid
Least connections handles overload much better, but the cost of doing that is slightly higher 95th and 99th percentile latencies. Depending on your use-case, this might be a worthwhile trade-off.
# One last algorithm
Below is a simulation using an algorithm called "peak exponentially weighted
moving average" (or PEWMA). It's a mouthful, isn't it? It uses the latency for
each server to decide which
Press
I set the parameters for this simulation very carefully in order to show you
something. Did you notice that the leftmost
It's difficult to see how the EWMA works by just looking at the current value. You have to see it change over time.
The visual below shows a single
It gradually converges toward whatever new latency value you've set. It takes
big steps at first, but the steps get smaller as it gets closer. Calculating it
this way helps avoid big swings if you have a mix of fast and slow
Each queued_requests + 1 before being used to make a load balancing decision. The
server with the lowest ewma * (queued_requests + 1) gets the next
The visual below shows a more full example, with 5 queued_requests" to multiply the EWMAs by the queued_requests + 1 to see the actual score used to choose a server. Press
PEWMA adapts dynamically to make sure
An improvement across the board! It's more pronounced at the higher percentiles, but consistently present for the median as well. Here we can see the same data in histogram form.
The PEWMA distribution is consistently shifted further left, toward lower latency, than least connections.
How about
It starts out performing better, but over time performs worse than least
connections. This makes sense. PEWMA is opportunistic in that it tries to get
the best latency, and this means it may sometimes leave a
I want to add here that PEWMA has a lot of parameters that can be tweaked. The implementation I wrote for this post uses a configuration that seemed to work well for the situations I tested it in, but further tweaking could get you better results vs least connections. This is one of the downsides of PEWMA vs least connections: extra complexity.
Enjoyed this? Sign up to get notified of new posts.
# Conclusion
I spent a long time on this post. It was difficult to balance realism against ease of understanding, but I feel good about where I landed. I'm hopeful that being able to see how these complex systems behave in practice, in ideal and less-than-ideal scenarios, helps you grow an intuitive understanding of when they would best apply to your workloads.
Obligatory disclaimer: You must always benchmark your own workloads over taking advice from the Internet as gospel. My simulations here ignore some real life constraints (server slow start, network latency), and are set up to display specific properties of each algorithm. They aren't realistic benchmarks to be taken at face value.
To round this out, I leave you with a version of the simulation that lets you tweak most of the parameters in real time. Have fun!
EDIT: Thanks to everyone who participated in the discussions on Hacker News, Twitter and Lobste.rs!
You all had a tonne of great questions and I tried to answer all of them. Some of the common themes were about missing things, either algorithms (like "power of 2 choices") or downsides of algorithms covered (like how "least connections" handles errors from servers).
I tried to strike a balance between post length and complexity of the simulations. I'm quite happy with where I landed, but like you I also wish I could have covered more. I'd love to see people taking inspiration from this and covering more topics in this space in a visual way. Please ping me if you do!
The other common theme was "how did you make this?" The original version used PixiJS. The current version uses a Lit component with SVG for the live simulations and charts. If writing visual explanations like this is something you're interested in, I recommend it!
Wanna tell me what you thought of the post? This form sends a notification directly to my phone.