System Design (High-Level Design) Module 19 – Building blocks: traffic, storage and messaging
Design a software load balancer
Design a layer-4 load balancer from ordinary servers, with ECMP, Maglev hashing, connection tables, direct server return and a separate control plane.
What you will learn
- Estimate the packets, connections and connection-table memory of a region's load balancer
- Design a layer-4 balancer tier that scales out behind ECMP with direct server return
- Build a Maglev lookup table and measure how many flows move when backends change
- Prevent broken connections when balancer nodes fail, restart or change
- Separate the control plane from a data plane that keeps forwarding without it
Before you start
What you’ll learn · Before you start
What you will learn
- Estimate the packets, connections and connection-table memory of a region's load balancer
- Design a layer-4 balancer tier that scales out behind ECMP with direct server return
- Build a Maglev lookup table and measure how many flows move when backends change
- Prevent broken connections when balancer nodes fail, restart or change
- Separate the control plane from a data plane that keeps forwarding without it
Before you start
On this page
A software load balancer is a fleet of ordinary servers that share one virtual IP address (VIP): An IP address that belongs to a service rather than to one machine: several servers answer for it, so clients keep one address while the servers behind it come and go., or VIP: in the design below, 10 nodes for 30 million packets a second. The network’s routers spread the packets of that address over the fleet with equal-cost multipath (ECMP): A routing feature that spreads the traffic for one destination over several next hops of equal cost, usually by hashing each packet's addresses and ports, so that every packet of a flow takes the same next hop. (ECMP); each balancer node then picks a backend for the packet’s flow and tunnels the packet to it, and the backend replies to the client directly. The design has to keep every packet of a connection on the same backend while balancer nodes and backends come and go, so every node must choose the same backend for the same flow: a consistent-hashing table that all nodes build alike, plus a connection table: A load balancer node's memory of the backend it chose for each flow it has seen, so that the flow's later packets go to the same backend without a new choice. on each node. A control plane hands out backend lists, and the nodes keep forwarding without it.
Requirements and estimate: direct return and 30 million open connections
A region serves web pages and APIs to millions of users through a few public addresses. A pair of hardware balancers carries it today, and every traffic peak is a capacity problem, because a pair grows only by replacing both boxes. Ask first:
| Question | Assumed here |
|---|---|
| Which protocols: TCP only, or UDP and QUIC as well? | TCP and UDP |
| What are the peak packet and connection rates, how large are packets, and how long do connections last? | 30 million packets a second arriving, 120 bytes on average; 1.5 million new connections a second that stay open 20 seconds |
| How fast must a dead backend stop receiving new connections, and may connections break when a balancer node fails? | Out of new flows within seconds; no connection broken by a change in the balancer tier |
| Do backends need the client’s address, and do they end TLS themselves? | L7 proxies that end TLS and need it |
| Who changes the backend lists, how often, and what happens to forwarding while that system is down? | Hundreds a day; forwarding never waits |
Functional requirements: forward TCP and UDP for many VIPs; spread flows over a VIP’s backends by weight; health checks, draining and slow start; connection limits per backend; configuration by API. Non-functional requirements: line-rate forwarding of small packets; growth by adding nodes; no broken connections when a node fails or restarts; forwarding that continues while the control plane is down.
The first estimate, to change and run again:
# The first estimate for a region's layer-4 software load balancer. Every input is an assumption, written down so
# that you can change it and run the script again.
import math
PACKETS_IN = 30e6 # packets a second arriving for the region's virtual IPs at peak (requests and ACKs)
PACKET_BYTES = 120 # average size of an arriving packet
RESPONSE_RATIO = 12 # bytes sent back for every byte that arrives (pages, images, API responses)
NEW_CONNECTIONS = 1.5e6 # new TCP connections a second at peak
LIFETIME = 20 # seconds an average connection stays open
NODE_PPS = 8e6 # packets a second one balancer node can forward
TARGET = 0.5 # run the nodes at half their capacity at peak
SPARE = 2 # nodes that may be down or in maintenance at the same time
ENTRY_BYTES = 64 # one connection-table entry: flow key, backend, timestamps
inbound = PACKETS_IN * PACKET_BYTES * 8 / 1e9
print("Traffic")
print(f" arriving: {PACKETS_IN / 1e6:.0f} million packets a second x {PACKET_BYTES} bytes = {inbound:.1f} Gbit/s")
print(f" leaving: {inbound * RESPONSE_RATIO:.1f} Gbit/s, sent by the backends straight to the clients")
print(f" through the balancer, both ways would be {inbound * (1 + RESPONSE_RATIO):.1f} Gbit/s, "
f"{1 + RESPONSE_RATIO:.0f} times as much")
nodes = math.ceil(PACKETS_IN / (NODE_PPS * TARGET))
print("Balancer nodes")
print(f" {PACKETS_IN / 1e6:.0f} million / ({NODE_PPS / 1e6:.0f} million x {TARGET:.0%}) = {nodes} nodes, "
f"plus {SPARE} spare = {nodes + SPARE}")
open_now = NEW_CONNECTIONS * LIFETIME
per_node = open_now / (nodes + SPARE)
print("Connection tables")
print(f" open connections (Little's law): {NEW_CONNECTIONS / 1e6:.1f} million a second x {LIFETIME} s "
f"= {open_now / 1e6:.0f} million")
print(f" per node: {per_node / 1e6:.0f} million entries x {ENTRY_BYTES} bytes = {per_node * ENTRY_BYTES / 1e6:.0f} MB")
print(f" when one node fails, at least its share, {open_now / (nodes + SPARE) / 1e6:.0f} million connections, "
f"arrives at nodes that have never seen them") Output
Traffic arriving: 30 million packets a second x 120 bytes = 28.8 Gbit/s leaving: 345.6 Gbit/s, sent by the backends straight to the clients through the balancer, both ways would be 374.4 Gbit/s, 13 times as much Balancer nodes 30 million / (8 million x 50%) = 8 nodes, plus 2 spare = 10 Connection tables open connections (Little's law): 1.5 million a second x 20 s = 30 million per node: 3 million entries x 64 bytes = 192 MB when one node fails, at least its share, 3 million connections, arrives at nodes that have never seen them
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lb_estimate.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Two numbers shape the design. Because responses go straight back, the balancer carries a thirteenth of the bytes. And 30 million open connections mean that every node holds millions of table entries, and that a failed node hands millions of connections to nodes that have never seen them.
Checkpoint
Check the estimate
The first design: every node picks the same backend
The first design of the software load balancer
Text description of the diagram
The diagram shows the first design from top to bottom.
- Clients send packets to the virtual IP, 203.0.113.10.
- A router hashes each flow to one balancer node by equal-cost multipath (ECMP). Every node announces the virtual IP to the router by BGP.
- The balancer nodes are all alike: node 1, node 2 and so on up to node 10. Each holds a connection table and a Maglev lookup table.
- Each node tunnels a packet to the chosen backend, wrapped in GRE or IPIP. The backends are L7 proxies or servers.
- A dashed arrow shows the control plane, which holds the backend lists, weights and health, pushing versioned configuration to the nodes.
- The backends send their responses straight back to the clients, without passing through the balancer: direct server return (DSR).
Each part of the design has one job:
- ECMP at the router: hashes each flow’s addresses and ports to one of the equal-cost next hops, the nodes.
- VIP announcement by BGP: each healthy node announces the virtual IP, and withdraws it when it is unhealthy.
- Maglev lookup table: a table of a prime number of entries filled with backends, the same on every node.
- Connection table: each node’s memory of the backend it chose for every flow it has seen.
- Encapsulation: the node wraps each packet in GRE or IP-in-IP addressed to the backend, leaving it intact.
- Direct server return: the backend answers the client from the virtual IP, never back through a node.
- Control plane: keeps VIPs, pools, weights and health, and pushes versioned configuration to every node.
Follow one connection’s packets through these parts:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The router picks a node. The client's first packet goes to the virtual IP. The router hashes the flow's addresses and ports and sends it to node 3, one of the equal-cost next hops that announce the VIP.
- A new flow. Node 3 has no entry for this flow in its connection table, so it looks the flow up in the Maglev table, which every node builds from the same backend list. The table names backend b7.
- Remembered, then tunnelled. Node 3 records the flow and b7 in its connection table, wraps the packet in GRE or IP-in-IP addressed to b7 and sends it on, with the client's packet intact inside.
- Direct server return. b7 unwraps the packet, sees the client's real address and answers from the virtual IP straight to the client. Responses, 12 bytes for every byte that arrives in the estimate, never pass back through a node.
- The next packet. The flow's next packet hashes to node 3 again, which finds the flow in its connection table and tunnels it to b7 without another lookup.
- Every node would agree. Every node builds the same Maglev table from the same backend list, so any node would have chosen b7 for this flow. That is the property the deep dives below put to the test.
The deep dives that decide whether the design works:
- How is the lookup table built, how even is it, and how many flows move when a backend leaves?
- What breaks connections when a node fails, restarts or joins, and what keeps them alive?
- What happens when a node fails while the backends change at the same time?
- How do the control plane and the data plane stay apart?
- How many packets a second can a node forward, and what does a SYN flood do to its tables?
- What does encapsulation cost, and where do TLS and HTTP features live?
An answer is judged on requirements and estimates, flow placement, connection persistence, failure handling, control-plane design and operations.
Checkpoint
Check the first design
The API, and three structures in every node Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Maglev hashing builds an even table, at the price of a few extra moves
- Connection tables, and the failures they do not cover
- A balancer node dies
- A node dies while a backend changes
- A rolling restart of every node
- Second chance: surviving changes on both sides at once
- Control plane and data plane, kept apart
- Packets per second, SYN floods and the table, with numbers
- Encapsulation costs 20 or 24 bytes a packet, and TLS lives a tier behind
- At 10× the router’s ECMP group gives out, at 100× the pools
- Six alternatives, and why each loses
- What each level is expected to cover
- A sample answer and a 45-minute self-mock
- Practise: fill the lookup table
- Fill a Maglev lookup table from the backends' preferences
- Interview questions
- What is direct server return in a load balancer, and why do software load balancers use it?
- During a rolling restart of your layer-4 balancer fleet, a few thousand long-lived connections were reset, although no backend changed. Each node picks a backend for a new flow by its own round robin and remembers it. Explain why connections broke, and redesign the tier so that restarts break none.
- You must choose between a hash ring and a Maglev lookup table for a layer-4 balancer with 400 backends per virtual IP. Compare them on load balance, disruption when a backend changes, memory and rebuild time, with numbers, and decide.
- A SYN flood fills the connection tables of your layer-4 balancer nodes. What happens to new and existing connections, and how do you design the tables and the nodes for floods?
- Key takeaways
2 diagrams · 3 runnable examples (JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 48 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Terms in this lesson
- connection table
- A load balancer node's memory of the backend it chose for each flow it has seen, so that the flow's later packets go to the same backend without a new choice.
- equal-cost multipath (ECMP)
- A routing feature that spreads the traffic for one destination over several next hops of equal cost, usually by hashing each packet's addresses and ports, so that every packet of a flow takes the same next hop.
- virtual IP address (VIP)
- An IP address that belongs to a service rather than to one machine: several servers answer for it, so clients keep one address while the servers behind it come and go.
References
- Maglev: A Fast and Reliable Software Network Load Balancer (NSDI 2016) (USENIX)
- RFC 2992: Analysis of an Equal-Cost Multi-Path Algorithm (IETF)
- RFC 2991: Multipath Issues in Unicast and Multicast Next-Hop Selection (IETF)
- RFC 2784: Generic Routing Encapsulation (GRE) (IETF)
- RFC 2003: IP Encapsulation within IP (IETF)
- Supported load balancers (Envoy documentation) (Envoy Project)
- Katran, a high-performance layer 4 load balancer (Meta Platforms (facebookincubator on GitHub))
- GLB Director: an open-source load balancer (GitHub)
- HAProxy documentation (HAProxy Technologies)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress