{"id":"load-balancing","title":"Load balancers","sections":[{"id":"overview","title":"Overview","html":"<p>This chapter answers five questions:</p>\n<ul>\n<li>What does a load balancer do, and how does it know which servers are healthy?</li>\n<li>How does it choose which server gets the next request?</li>\n<li>What is the difference between layer 4 and layer 7 load balancing?</li>\n<li>How do you stop the load balancer itself from taking your site down?</li>\n<li>What does scaling out behind a load balancer cost elsewhere in the system?</li>\n</ul>\n<p>We'll keep using <strong>Crumb</strong>, the recipe-sharing site from the <a href=\"/learn/scalability\">Scalability</a> chapter. Crumb runs several identical app servers behind a load balancer. Sessions live in Redis, photos live in object storage, and the app servers share a cache and a database with read replicas. The Scalability chapter treated the load balancer as a box that sends each request to a free server. This chapter opens that box.</p>","headings":[]},{"id":"what-a-load-balancer-does","title":"What a load balancer does","html":"<h3 id=\"what-a-load-balancer-does\">What a load balancer does</h3>\n<p>Think of the host at a busy restaurant. Guests don't pick their own waiter. The host seats them in a section whose waiter has room, and stops seating people in a section whose waiter has gone home sick. Guests never need to know who is working tonight.</p>\n<p>A <strong>load balancer</strong> plays the host's role for servers. Clients connect to one address, the load balancer's. For each request or connection, it picks one server from the group behind it and forwards the request there. When the server replies, the load balancer passes the response back to the client. The servers behind it are called <strong>backends</strong>, the <strong>pool</strong>, or <strong>targets</strong> (AWS's word).</p>\n<p>A load balancer has four jobs:</p>\n<ol>\n<li><strong>Spread the work</strong> across all the servers, so you can add capacity by adding servers.</li>\n<li><strong>Avoid unhealthy servers.</strong> If a server crashes or starts failing, stop sending it requests.</li>\n<li><strong>Avoid overloading any one server</strong> while others sit idle.</li>\n<li><strong>Remove a single point of failure.</strong> A <strong>single point of failure</strong> is one part whose failure takes the whole system down. One app server is one. With three behind a load balancer, any one can fail and the site stays up.</li>\n</ol>\n<figure class=\"diagram\" data-text=\"Users -> Load balancer (HTTPS); Load balancer -> App server 1; Load balancer -> App server 2; Load balancer -> App server 3 (failing) (health checks only)\"><div class=\"diagram-scroll\"><svg class=\"dg\" viewBox=\"0 0 640 320\" width=\"640\" role=\"img\" aria-label=\"Crumb&#x27;s load balancer stops sending requests to the server that failed its health check.\"><defs><marker id=\"dg-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path class=\"dg-arrowhead\" d=\"M0,1 L9,5 L0,9 z\"></path></marker></defs><line class=\"dg-edge\" x1=\"151\" y1=\"162\" x2=\"167\" y2=\"162\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"311\" y1=\"135.4\" x2=\"487\" y2=\"69.4\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"311\" y1=\"162\" x2=\"487\" y2=\"162\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge dg-dashed\" x1=\"311\" y1=\"188.6\" x2=\"487\" y2=\"254.6\" marker-end=\"url(#dg-arrow)\"></line><rect class=\"dg-node dg-client\" x=\"12\" y=\"138\" width=\"136\" height=\"48\" rx=\"24\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"80\" y=\"166.5\">Users</tspan></text><rect class=\"dg-node dg-box\" x=\"172\" y=\"138\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"240\" y=\"166.5\">Load balancer</tspan></text><rect class=\"dg-node dg-box\" x=\"492\" y=\"18\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"560\" y=\"46.5\">App server 1</tspan></text><rect class=\"dg-node dg-box\" x=\"492\" y=\"138\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"560\" y=\"166.5\">App server 2</tspan></text><rect class=\"dg-node dg-box\" x=\"492\" y=\"256\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"560\" y=\"278.5\">App server 3</tspan><tspan x=\"560\" y=\"294.5\">(failing)</tspan></text><rect class=\"dg-edge-bg\" x=\"138.5\" y=\"116\" width=\"41\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"159\" y=\"129\" text-anchor=\"middle\">HTTPS</text><rect class=\"dg-edge-bg\" x=\"338.2\" y=\"212.6\" width=\"121.6\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"399\" y=\"225.6\" text-anchor=\"middle\">health checks only</text></svg></div><figcaption>Crumb's load balancer stops sending requests to the server that failed its health check.</figcaption></figure>\n<p>The same idea works in front of database read replicas, a cache cluster, or an internal service. Wherever several identical servers can do a job, something has to decide which one gets each piece of work.</p>\n<h4 id=\"hardware-software-and-managed-load-balancers\">Hardware, software and managed load balancers</h4>\n<ul>\n<li><strong>Hardware appliances</strong> are dedicated boxes from networking vendors. They are fast but expensive, and you usually scale them by buying a bigger box.</li>\n<li><strong>Software load balancers</strong> such as HAProxy, NGINX and Envoy run on ordinary servers. For more capacity, you run more copies.</li>\n<li><strong>Managed cloud load balancers</strong>, such as AWS Elastic Load Balancing, are run for you by the cloud provider. You configure rules and pay for use.</li>\n</ul>\n<p>Software on ordinary machines now handles enormous traffic. Google's network load balancer, Maglev, runs on standard Linux servers. Google's <a href=\"https://research.google/pubs/maglev-a-fast-and-reliable-software-network-load-balancer/\" target=\"_blank\" rel=\"noopener noreferrer\">2016 paper</a> says it \"has been serving Google's traffic since 2008,\" and that one Maglev machine \"is able to saturate a 10Gbps link with small packets.\"</p>","headings":[{"id":"what-a-load-balancer-does","title":"What a load balancer does","depth":3},{"id":"hardware-software-and-managed-load-balancers","title":"Hardware, software and managed load balancers","depth":4}]},{"id":"health-checks","title":"Health checks","html":"<h3 id=\"health-checks\">Health checks</h3>\n<p>A load balancer can only avoid a broken server if it notices the server is broken. It does this with <strong>health checks</strong>, of two kinds.</p>\n<h4 id=\"active-health-checks\">Active health checks</h4>\n<p>In an <strong>active</strong> check, the load balancer sends its own test request to each server on a schedule, such as <code>GET /health</code> every few seconds. A server that fails several checks in a row is taken out of rotation. It comes back after passing several in a row. Defaults vary a lot:</p>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<div class=\"table-wrap\"><table><thead><tr><th>Setting</th><th>AWS Application Load Balancer</th><th>HAProxy</th></tr></thead><tbody><tr><td>Time between checks</td><td>30 seconds</td><td>2 seconds</td></tr><tr><td>Failures before marked down</td><td>2</td><td>3</td></tr><tr><td>Successes before marked up</td><td>5</td><td>2</td></tr></tbody></table></div>\n<p>The AWS values are from its <a href=\"https://docs.aws.amazon.com/elasticloadbalancing/latest/application/target-group-health-checks.html\" target=\"_blank\" rel=\"noopener noreferrer\">health check settings</a>, which also set a 5-second timeout per check. The HAProxy values are the <code>inter</code>, <code>fall</code> and <code>rise</code> defaults in its <a href=\"https://www.haproxy.org/download/2.8/doc/configuration.txt\" target=\"_blank\" rel=\"noopener noreferrer\">configuration manual</a>.</p>\n<p>Here is what that means for Crumb. Say an app server crashes just after passing a check. With AWS's defaults, the next two checks fail about 30 and 60 seconds later, and only then is the server marked down. For roughly a minute, it keeps getting a share of real requests, and they fail. With HAProxy's defaults, the same crash is noticed in about 6 seconds. Checking more often finds failures sooner, at the cost of a little extra load.</p>\n<h4 id=\"passive-health-checks\">Passive health checks</h4>\n<p>In a <strong>passive</strong> check, the load balancer watches the real traffic it forwards. If requests to a server keep failing, it stops using that server for a while. In NGINX's <a href=\"https://nginx.org/en/docs/http/load_balancing.html\" target=\"_blank\" rel=\"noopener noreferrer\">load balancing guide</a>, <code>max_fails</code> sets how many failures mark a server as failed (the default is 1). After the <code>fail_timeout</code> period, NGINX tries the server again with live requests and brings it back if they succeed.</p>\n<p>Passive checks react at once, but only after some users' requests have failed. Active checks find a dead server even when no traffic flows, but only on their schedule. Most setups use both.</p>\n<h4 id=\"what-to-check-and-what-if-everything-fails\">What to check, and what if everything fails</h4>\n<p>A check that only proves the process is running can pass while the server can't do useful work. A check that tests a shared dependency, such as the database, can fail on every server at once. The <a href=\"/learn/availability#health-checks-that-test-the-right-thing\">availability chapter</a> covers how to design a check that avoids both problems.</p>\n<p>When every server fails its checks, the check itself is often wrong, and the servers may still answer some requests. So AWS's Application Load Balancer <strong>fails open</strong>: if every target is unhealthy, it sends traffic to all of them \"regardless of their health status.\" Possibly failing requests beat a certain outage.</p>","headings":[{"id":"health-checks","title":"Health checks","depth":3},{"id":"active-health-checks","title":"Active health checks","depth":4},{"id":"passive-health-checks","title":"Passive health checks","depth":4},{"id":"what-to-check-and-what-if-everything-fails","title":"What to check, and what if everything fails","depth":4}]},{"id":"choosing-a-server-routing-methods","title":"Choosing a server: routing methods","html":"<h3 id=\"choosing-a-server-routing-methods\">Choosing a server: routing methods</h3>\n<p>For each request, the load balancer picks a server using a rule called the <strong>load balancing algorithm</strong> or <strong>routing method</strong>.</p>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<div class=\"table-wrap\"><table><thead><tr><th>Method</th><th>How it picks</th><th>Good for</th><th>Watch out for</th></tr></thead><tbody><tr><td>Round robin</td><td>Each server in turn</td><td>Similar servers and requests</td><td>Ignores how busy servers are</td></tr><tr><td>Weighted round robin</td><td>In turn, bigger servers get more turns</td><td>Servers of different sizes</td><td>Keeping weights up to date</td></tr><tr><td>Random</td><td>Any server, by chance</td><td>Many load balancers, no shared state</td><td>Short streaks of bad luck</td></tr><tr><td>Least loaded</td><td>The server doing the least work now</td><td>Requests of very different cost</td><td>New servers get flooded</td></tr><tr><td>Power of two choices</td><td>Two at random, then the less busy</td><td>Large fleets, many load balancers</td><td>A little more work per pick</td></tr><tr><td>Hashing</td><td>A hash of a key, such as IP or URL</td><td>Same key to the same server</td><td>Adding a server moves many keys</td></tr><tr><td>Session cookie</td><td>The server the cookie names</td><td>Apps with sessions on the server</td><td>Uneven load, lost sessions</td></tr></tbody></table></div>\n<h4 id=\"round-robin-weighted-round-robin-and-random\">Round robin, weighted round robin and random</h4>\n<p><strong>Round robin</strong> works like dealing cards: server 1, server 2, server 3, then back to server 1. It is the default in both NGINX and HAProxy.</p>\n<p>It assumes every server is the same size. When Crumb adds a server with twice the CPU of the others, <strong>weighted round robin</strong> gives it weight 2 and the old servers weight 1. Out of every four requests, the big server gets two and each small one gets one. NGINX's guide shows the same idea with weights of 3, 1 and 1.</p>\n<p>It also assumes every request costs about the same. A Crumb recipe page takes 20 ms. A search across five ingredients can take two seconds. If several slow searches land on one server, round robin keeps sending it its full share anyway.</p>\n<p><strong>Random</strong> spreads load as evenly as round robin over thousands of requests, and needs no memory of whose turn it is. That helps when Crumb runs several load balancer machines: each can pick randomly without coordinating with the others.</p>\n<h4 id=\"least-loaded\">Least loaded</h4>\n<p><strong>Least loaded</strong> methods send each request to the server doing the least work right now. Products measure work differently:</p>\n<ul>\n<li><strong>Least connections</strong> counts open connections (NGINX's <code>least_conn</code>, HAProxy's <code>leastconn</code>).</li>\n<li><strong>Least outstanding requests</strong> counts requests sent but not yet answered. AWS Application Load Balancers <a href=\"https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html\" target=\"_blank\" rel=\"noopener noreferrer\">offer it</a> alongside round robin and weighted random.</li>\n<li><strong>Least response time</strong> favours servers that have answered fastest. NGINX's guide describes a <code>least_time</code> method that weighs average response time and active connections.</li>\n</ul>\n<p>These handle Crumb's slow searches well. A server stuck on three searches has more requests open than its neighbours, so new work goes elsewhere.</p>\n<p>They aren't always worth it. HAProxy's manual recommends <code>leastconn</code> \"where very long sessions are expected,\" such as database connections, and calls it \"not very well suited for protocols using short sessions such as HTTP.\" When requests finish in milliseconds, every server's count hovers near zero and says little.</p>\n<p>There is also a trap. A new server has zero connections, so it looks like the best choice for every request at once and gets flooded before its caches warm up. <strong>Slow start</strong> fixes this by raising a new server's share gradually. HAProxy's <code>slowstart</code> grows it \"linearly from 0 to 100%\" over the time you set. AWS allows 30 to 900 seconds and leaves it off by default.</p>\n<h4 id=\"the-power-of-two-random-choices\">The power of two random choices</h4>\n<p>A cheaper way to get most of the benefit: pick <strong>two</strong> servers at random, and send the request to the less busy one.</p>\n<p>Michael Mitzenmacher, Andréa Richa and Ramesh Sitaraman explain why this works in their <a href=\"https://www.eecs.harvard.edu/~michaelm/postscripts/handbook2001.pdf\" target=\"_blank\" rel=\"noopener noreferrer\">survey</a>. Picture balls dropped into bins. If each ball goes into one random bin, some bin ends up far fuller than average. If each ball checks two random bins and picks the emptier one, the fullest bin is dramatically less full. In their words, \"even a small amount of choice can lead to drastically different results,\" and each choice beyond two helps only a little more.</p>\n<p>It also suits several load balancers working side by side. If they all pick \"the least loaded server,\" they may all see the same quiet server and pile onto it together. With two random choices, they spread out. HAProxy's <code>random</code> algorithm draws 2 servers by default, which its manual says \"generally shows very good distribution and performance.\"</p>\n<h4 id=\"hashing-the-same-key-to-the-same-server\">Hashing: the same key to the same server</h4>\n<p>Sometimes requests that share something should land on the same server. <strong>Hash-based</strong> routing takes a key from the request, hashes it, and uses the result to pick a server. The same key always gives the same server.</p>\n<ul>\n<li><strong>Source IP hashing</strong> uses the client's address. HAProxy's manual says it is \"generally used in TCP mode where no cookie may be inserted.\" NGINX calls it <code>ip_hash</code>.</li>\n<li><strong>URL hashing</strong> uses the request path, so every request for <code>/recipes/812</code> reaches the same cache server. HAProxy's manual says this is done \"to maximize the cache hit rate.\"</li>\n</ul>\n<p>The usual method divides the hash by the number of servers and keeps the remainder. Add or remove a server and that number changes, so most keys move. HAProxy warns that \"many clients will be directed to a different server.\" For a cache, that means a sudden wave of misses. <strong>Consistent hashing</strong> moves only a small share of keys when the pool changes. It has <a href=\"/learn/consistent-hashing\">its own chapter</a>.</p>\n<p>For Crumb: start with round robin, or least outstanding requests if request costs vary a lot. Add weights when servers differ in size. Use hashing only when keeping a key on one server pays off, as it does for caches.</p>","headings":[{"id":"choosing-a-server-routing-methods","title":"Choosing a server: routing methods","depth":3},{"id":"round-robin-weighted-round-robin-and-random","title":"Round robin, weighted round robin and random","depth":4},{"id":"least-loaded","title":"Least loaded","depth":4},{"id":"the-power-of-two-random-choices","title":"The power of two random choices","depth":4},{"id":"hashing-the-same-key-to-the-same-server","title":"Hashing: the same key to the same server","depth":4}]},{"id":"session-persistence-sticky-sessions","title":"Session persistence (sticky sessions)","html":"<h3 id=\"session-persistence-sticky-sessions\">Session persistence (sticky sessions)</h3>\n<p><strong>Session persistence</strong>, or <strong>sticky sessions</strong>, means sending all of one user's requests to the same server. It exists for apps that keep each user's login in the memory of the server they first reached. <a href=\"/learn/scalability#scaling-out-clones-behind-a-load-balancer\">Scalability</a> explains why such users get logged out when a request lands elsewhere.</p>\n<p>Load balancers usually do this with a <strong>cookie</strong>, a small piece of data the browser sends back with every request.</p>\n<ul>\n<li>The load balancer can <strong>insert its own cookie</strong> naming the server. An AWS Application Load Balancer's cookie lasts one day by default.</li>\n<li>It can <strong>reuse the app's cookie</strong>. HAProxy's <a href=\"http://www.haproxy.org/download/1.2/doc/architecture.txt\" target=\"_blank\" rel=\"noopener noreferrer\">architecture guide</a> prefixes the app's <code>JSESSIONID</code> cookie with the server's name, so the load balancer can read which server each user belongs to.</li>\n<li>Without cookies, as with plain TCP, <strong>source IP hashing</strong> gives weaker stickiness. Many users behind one office network share an IP and all land on one server. A phone that moves from Wi-Fi to mobile data gets a new IP and a new server.</li>\n</ul>\n<p>Stickiness has costs. Load becomes uneven, because a server holding a few very active users can't hand them off. A server failure still logs out everyone stuck to it. And removing a server means waiting for its sessions to end.</p>\n<p>That's why Crumb keeps its servers <strong>stateless</strong>, with sessions in Redis. Any server can handle any request. Stickiness remains useful as an optimization: if a server caches a user's recent data locally, sending the user back gives more cache hits, and nothing breaks if they land elsewhere.</p>","headings":[{"id":"session-persistence-sticky-sessions","title":"Session persistence (sticky sessions)","depth":3}]},{"id":"tls-termination","title":"TLS termination","html":"<h3 id=\"tls-termination\">TLS termination</h3>\n<p>Crumb's traffic is encrypted with <strong>TLS</strong> (Transport Layer Security), the protocol behind the browser's padlock. Its older name, <strong>SSL</strong>, survives in many product settings. A TLS server needs a <strong>certificate</strong> in the <a href=\"https://en.wikipedia.org/wiki/X.509\" target=\"_blank\" rel=\"noopener noreferrer\">X.509</a> format, which proves it owns its domain name, plus a matching private key.</p>\n<p>Setting up encrypted connections costs CPU. <strong>TLS termination</strong> (or SSL termination) means the load balancer does this work. AWS's <a href=\"https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-listener-config.html\" target=\"_blank\" rel=\"noopener noreferrer\">listener documentation</a> puts it plainly: the load balancer \"decrypts requests from clients before sending them to the back-end instances.\" It encrypts the responses on the way back.</p>\n<p>This has three benefits:</p>\n<ol>\n<li><strong>App servers do less work</strong>, because they skip encryption.</li>\n<li><strong>Certificates live in one place.</strong> Without termination, every app server needs the certificate and private key. With it, you install and renew them on the load balancer only, and there are fewer copies of the key to leak.</li>\n<li><strong>The load balancer can read requests</strong>, which layer 7 routing needs.</li>\n</ol>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<div class=\"table-wrap\"><table><thead><tr><th>Setup</th><th>Load balancer to app servers</th><th>Who holds certificates</th><th>Load balancer reads requests?</th></tr></thead><tbody><tr><td>Terminate</td><td>Plain HTTP on a private network</td><td>Load balancer</td><td>Yes</td></tr><tr><td>Terminate and re-encrypt</td><td>A new TLS connection</td><td>Load balancer and servers</td><td>Yes</td></tr><tr><td>Pass-through</td><td>The client's encrypted stream, untouched</td><td>Servers</td><td>No</td></tr></tbody></table></div>\n<p>Re-encryption suits rules that require encryption inside the data center. Pass-through keeps the load balancer from seeing any data, but limits it to layer 4 routing.</p>\n<p>Proxying also hides the client's address: app servers see connections from the load balancer. For HTTP, the load balancer adds an <code>X-Forwarded-For</code> header with the client's IP. For plain TCP, the <strong>proxy protocol</strong> adds a short header with it at the start of the connection. The <a href=\"/learn/security#in-transit-tls-13\">security chapter</a> covers TLS itself.</p>","headings":[{"id":"tls-termination","title":"TLS termination","depth":3}]},{"id":"layer-4-and-layer-7-load-balancing","title":"Layer 4 and layer 7 load balancing","html":"<h3 id=\"layer-4-and-layer-7-load-balancing\">Layer 4 and layer 7 load balancing</h3>\n<p>Networking is often described in layers, using the OSI model. Two layers matter here:</p>\n<ul>\n<li><strong>Layer 4, the transport layer</strong>, is where TCP and UDP work. Traffic is described by source and destination <strong>IP addresses</strong> and <strong>ports</strong>. A port is a number that says which program the traffic is for, such as 443 for HTTPS.</li>\n<li><strong>Layer 7, the application layer</strong>, is where HTTP works. Here you can see the method (<code>GET</code>), the path (<code>/recipes/812</code>), headers, cookies and the body.</li>\n</ul>\n<p>Picture two people sorting mail. One reads only the envelope. The other opens each letter before deciding which department gets it. The first is faster. The second makes better decisions.</p>\n<h4 id=\"layer-4\">Layer 4</h4>\n<p>A <strong>layer 4 load balancer</strong> looks only at addresses and ports, never at the contents. It doesn't know whether the traffic is HTTP, a database protocol or a game. It picks a server when a connection starts and sends the whole connection there, usually by rewriting the packets' addresses. That rewriting is called <strong>network address translation</strong> (NAT).</p>\n<p>AWS's Network Load Balancer works this way. Its <a href=\"https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html\" target=\"_blank\" rel=\"noopener noreferrer\">documentation</a> says it \"can handle millions of requests per second.\" It picks a target by hashing the protocol, addresses, ports and TCP sequence number, and \"each individual TCP connection is routed to a single target for the life of the connection.\" It also offers a fixed IP address per zone, which helps customers who must allow it through a firewall.</p>\n<p>Choosing per connection has a catch. HTTP/2 and gRPC send many requests over one long-lived connection, so a layer 4 load balancer sends all of them to one server. The <a href=\"/learn/reverse-proxy-and-api-gateway#server-side-discovery\">reverse proxy chapter</a> notes the same problem in Kubernetes.</p>\n<h4 id=\"layer-7\">Layer 7</h4>\n<p>A <strong>layer 7 load balancer</strong> accepts the client's connection itself, reads the full request, picks a server, and sends the request over a separate connection to it. It usually keeps connections to each server open and ready, so it doesn't pay to open one per request.</p>\n<p>Because it reads requests, it can route on their contents:</p>\n<ul>\n<li><strong>By path.</strong> Crumb sends <code>/search</code> to servers with extra memory and <code>/upload</code> to servers that handle large files.</li>\n<li><strong>By host name.</strong> <code>api.crumb.example</code> and <code>www.crumb.example</code> can share one load balancer.</li>\n<li><strong>By cookie or header.</strong> Beta testers, marked by a cookie, can reach servers running the new version.</li>\n<li><strong>By sensitivity.</strong> Payment pages can go to a small, locked-down pool.</li>\n</ul>\n<p>It can also retry a failed request on another server, add headers, set sticky cookies, and answer some requests itself with a redirect or an error page.</p>\n<h4 id=\"the-trade-off\">The trade-off</h4>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<div class=\"table-wrap\"><table><thead><tr><th></th><th>Layer 4</th><th>Layer 7</th></tr></thead><tbody><tr><td>Reads</td><td>Addresses and ports</td><td>Method, path, headers, cookies, body</td></tr><tr><td>Picks a server</td><td>Once per connection</td><td>Once per request</td></tr><tr><td>Protocols</td><td>Any TCP or UDP</td><td>HTTP and protocols built on it</td></tr><tr><td>TLS</td><td>Can pass it through</td><td>Must decrypt to route</td></tr><tr><td>Cost per request</td><td>Lower</td><td>Higher</td></tr><tr><td>AWS example</td><td>Network Load Balancer</td><td>Application Load Balancer</td></tr></tbody></table></div>\n<p>Layer 4 gives up flexibility to use less time and computing power. On modern hardware the gap is often small for web traffic, so many teams default to layer 7 for its features. AWS's <a href=\"https://aws.amazon.com/elasticloadbalancing/features/\" target=\"_blank\" rel=\"noopener noreferrer\">comparison page</a> suggests its Network Load Balancer when \"extreme performance and static IP is needed,\" and its Application Load Balancer for \"flexible application management.\"</p>\n<p>Large sites often use both. GitHub's <a href=\"https://github.blog/engineering/infrastructure/introducing-glb/\" target=\"_blank\" rel=\"noopener noreferrer\">2016 post introducing GLB</a> describes a tier of layer 4 \"director\" machines spreading connections across a tier of layer 7 \"proxy\" machines running HAProxy.</p>\n<figure class=\"diagram\" data-text=\"Users -> Layer 4 load balancers; Layer 4 load balancers -> Layer 7 load balancers (connections); Layer 7 load balancers -> Web pool (everything else); Layer 7 load balancers -> Search pool (/search); Layer 7 load balancers -> Upload pool (/upload)\"><div class=\"diagram-scroll\"><svg class=\"dg\" viewBox=\"0 0 800 318\" width=\"800\" role=\"img\" aria-label=\"A layer 4 tier spreads connections. A layer 7 tier reads each request and picks a pool.\"><defs><marker id=\"dg-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path class=\"dg-arrowhead\" d=\"M0,1 L9,5 L0,9 z\"></path></marker></defs><line class=\"dg-edge\" x1=\"151\" y1=\"162\" x2=\"167\" y2=\"162\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"311\" y1=\"162\" x2=\"327\" y2=\"162\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"471\" y1=\"135.4\" x2=\"647\" y2=\"69.4\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"471\" y1=\"162\" x2=\"647\" y2=\"162\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"471\" y1=\"188.6\" x2=\"647\" y2=\"254.6\" marker-end=\"url(#dg-arrow)\"></line><rect class=\"dg-node dg-client\" x=\"12\" y=\"138\" width=\"136\" height=\"48\" rx=\"24\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"80\" y=\"166.5\">Users</tspan></text><rect class=\"dg-node dg-box\" x=\"172\" y=\"136\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"240\" y=\"158.5\">Layer 4</tspan><tspan x=\"240\" y=\"174.5\">load balancers</tspan></text><rect class=\"dg-node dg-box\" x=\"332\" y=\"136\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"400\" y=\"158.5\">Layer 7</tspan><tspan x=\"400\" y=\"174.5\">load balancers</tspan></text><rect class=\"dg-node dg-box\" x=\"652\" y=\"18\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"720\" y=\"46.5\">Web pool</tspan></text><rect class=\"dg-node dg-box\" x=\"652\" y=\"138\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"720\" y=\"166.5\">Search pool</tspan></text><rect class=\"dg-node dg-box\" x=\"652\" y=\"258\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"720\" y=\"286.5\">Upload pool</tspan></text><rect class=\"dg-edge-bg\" x=\"279.9\" y=\"114\" width=\"78.2\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"319\" y=\"127\" text-anchor=\"middle\">connections</text><rect class=\"dg-edge-bg\" x=\"507.5\" y=\"93.4\" width=\"103\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"559\" y=\"106.4\" text-anchor=\"middle\">everything else</text><rect class=\"dg-edge-bg\" x=\"532.3\" y=\"153\" width=\"53.4\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"559\" y=\"166\" text-anchor=\"middle\">/search</text><rect class=\"dg-edge-bg\" x=\"532.3\" y=\"212.6\" width=\"53.4\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"559\" y=\"225.6\" text-anchor=\"middle\">/upload</text></svg></div><figcaption>A layer 4 tier spreads connections. A layer 7 tier reads each request and picks a pool.</figcaption></figure>","headings":[{"id":"layer-4-and-layer-7-load-balancing","title":"Layer 4 and layer 7 load balancing","depth":3},{"id":"layer-4","title":"Layer 4","depth":4},{"id":"layer-7","title":"Layer 7","depth":4},{"id":"the-trade-off","title":"The trade-off","depth":4}]},{"id":"when-the-load-balancer-is-the-problem","title":"When the load balancer is the problem","html":"<h3 id=\"when-the-load-balancer-is-the-problem\">When the load balancer is the problem</h3>\n<p>Every request to Crumb now passes through the load balancer, which brings three risks:</p>\n<ol>\n<li><strong>One load balancer is a single point of failure.</strong> If it dies, every app server is healthy and unreachable.</li>\n<li><strong>It can become a bottleneck.</strong> If it lacks CPU, memory or bandwidth, or a setting such as a connection limit or timeout is wrong, every request slows down.</li>\n<li><strong>It adds complexity</strong>: one more thing to configure, monitor and upgrade. Running a second one to fix risk 1 adds more.</li>\n</ol>\n<h4 id=\"running-load-balancers-in-pairs\">Running load balancers in pairs</h4>\n<p>The fix for risk 1 is more than one load balancer, in one of the two fail-over patterns from the <a href=\"/learn/availability#fail-over-active-passive\">availability chapter</a>:</p>\n<ul>\n<li><strong>Active-passive.</strong> One load balancer handles all traffic while a standby waits. They exchange <strong>heartbeats</strong>, small \"I'm alive\" messages. Clients use a <strong>virtual IP address</strong> that can move between machines. If heartbeats stop, the standby claims the virtual IP and takes over. HAProxy's architecture guide shows two load balancers sharing an address this way, using a protocol called VRRP and a tool called keepalived.</li>\n<li><strong>Active-active.</strong> Both handle traffic all the time, spread by DNS or by routers splitting one address across machines. Each needs enough spare capacity to carry everything if the other fails. GitHub's GLB post notes a subtlety: when routers split traffic and one machine leaves, about \"1/N connections will get reassigned to the remaining servers,\" and those break unless the load balancers share connection information.</li>\n</ul>\n<figure class=\"diagram\" data-text=\"Users -> Virtual IP; Virtual IP -> Load balancer 1 (active); Virtual IP -> Load balancer 2 (standby) (on failover); Load balancer 1 (active) <-> Load balancer 2 (standby) (heartbeats); Load balancer 1 (active) -> App servers\"><div class=\"diagram-scroll\"><svg class=\"dg\" viewBox=\"0 0 800 200\" width=\"800\" role=\"img\" aria-label=\"Active-passive. The standby takes over the shared virtual IP when heartbeats stop.\"><defs><marker id=\"dg-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path class=\"dg-arrowhead\" d=\"M0,1 L9,5 L0,9 z\"></path></marker></defs><line class=\"dg-edge\" x1=\"151\" y1=\"102\" x2=\"167\" y2=\"102\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"311\" y1=\"84.3\" x2=\"407\" y2=\"60.3\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge dg-dashed\" x1=\"311\" y1=\"119.8\" x2=\"407\" y2=\"143.8\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"480\" y1=\"73\" x2=\"480\" y2=\"131\" marker-end=\"url(#dg-arrow)\" marker-start=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"551\" y1=\"42\" x2=\"647\" y2=\"42\" marker-end=\"url(#dg-arrow)\"></line><rect class=\"dg-node dg-client\" x=\"12\" y=\"78\" width=\"136\" height=\"48\" rx=\"24\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"80\" y=\"106.5\">Users</tspan></text><rect class=\"dg-node dg-note\" x=\"172\" y=\"78\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label dg-muted\" text-anchor=\"middle\"><tspan x=\"240\" y=\"106.5\">Virtual IP</tspan></text><rect class=\"dg-node dg-box\" x=\"412\" y=\"16\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"480\" y=\"38.5\">Load balancer 1</tspan><tspan x=\"480\" y=\"54.5\">(active)</tspan></text><rect class=\"dg-node dg-box\" x=\"412\" y=\"136\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"480\" y=\"158.5\">Load balancer 2</tspan><tspan x=\"480\" y=\"174.5\">(standby)</tspan></text><rect class=\"dg-node dg-box\" x=\"652\" y=\"18\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"720\" y=\"46.5\">App servers</tspan></text><rect class=\"dg-edge-bg\" x=\"319.9\" y=\"122.8\" width=\"78.2\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"359\" y=\"135.8\" text-anchor=\"middle\">on failover</text><rect class=\"dg-edge-bg\" x=\"444\" y=\"93\" width=\"72\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"480\" y=\"106\" text-anchor=\"middle\">heartbeats</text></svg></div><figcaption>Active-passive. The standby takes over the shared virtual IP when heartbeats stop.</figcaption></figure>\n<p>Managed cloud load balancers handle this redundancy for you. The managed service can still fail, though.</p>\n<h4 id=\"a-real-incident-christmas-eve-2012\">A real incident: Christmas Eve 2012</h4>\n<p>On 24 December 2012, part of AWS's load balancing service in its US-East region broke. According to <a href=\"https://aws.amazon.com/message/680587/\" target=\"_blank\" rel=\"noopener noreferrer\">AWS's summary</a>, a maintenance process was run by mistake against the live data describing customers' load balancers. The developer's access was meant to need approval each time, but it \"was incorrectly set to be persistent.\" Part of the data was deleted at 12:24 PM Pacific time.</p>\n<p>Load balancers left alone kept working. Ones that were changed or scaled during the event were left misconfigured. At the peak, 6.8% of running load balancers in the region were affected. AWS restored the data from a snapshot overnight and had almost all of them working by 10:30 AM on Christmas Day.</p>\n<p>Netflix was hit. In <a href=\"https://web.archive.org/web/20170210200715/http://techblog.netflix.com:80/2012/12/a-closer-look-at-christmas-eve-outage.html\" target=\"_blank\" rel=\"noopener noreferrer\">its write-up</a>, Adrian Cockcroft explained that Netflix used hundreds of load balancers, and \"a handful failed.\" Streaming on TV-connected devices in the US, Canada and Latin America was disrupted, and game consoles were affected for about seven hours. The servers behind them were fine: \"our applications continued to respond normally whenever the requests were able to get through.\"</p>\n<p>The lessons for Crumb:</p>\n<ul>\n<li><strong>Healthy servers behind a broken load balancer are still an outage.</strong> Users can't tell the difference.</li>\n<li><strong>A managed load balancer is still a dependency that can fail.</strong> Someone else runs it, but it can still break.</li>\n<li><strong>Configuration changes are risky during an incident.</strong> Here, load balancers broke when they were changed or scaled.</li>\n</ul>\n<p>Netflix responded by working toward surviving the loss of a whole region, which is where global load balancing comes in.</p>","headings":[{"id":"when-the-load-balancer-is-the-problem","title":"When the load balancer is the problem","depth":3},{"id":"running-load-balancers-in-pairs","title":"Running load balancers in pairs","depth":4},{"id":"a-real-incident-christmas-eve-2012","title":"A real incident: Christmas Eve 2012","depth":4}]},{"id":"adding-and-removing-servers-safely","title":"Adding and removing servers safely","html":"<h3 id=\"adding-and-removing-servers-safely\">Adding and removing servers safely</h3>\n<p>Crumb's pool changes daily. Deploys replace servers, and autoscaling adds servers at dinner time and removes them overnight.</p>\n<p><strong>Connection draining</strong> handles removal. The load balancer stops sending a departing server new requests but lets its current ones finish before it shuts down. AWS calls this the <strong>deregistration delay</strong>. It <a href=\"https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html\" target=\"_blank\" rel=\"noopener noreferrer\">defaults to 300 seconds</a> and can be set from 0 to 3,600. Set it a little above your slowest normal request. If Crumb's slowest uploads take 40 seconds, 60 seconds is enough. Long-lived connections, such as WebSockets, may never end by themselves, so the app should ask those clients to reconnect.</p>\n<p><strong>Slow start</strong>, from the routing section, handles the other direction: a fresh server's share of traffic rises gradually while its caches warm up.</p>","headings":[{"id":"adding-and-removing-servers-safely","title":"Adding and removing servers safely","depth":3}]},{"id":"global-load-balancing","title":"Global load balancing","html":"<h3 id=\"global-load-balancing\">Global load balancing</h3>\n<p>So far, everything happens inside one region. A load balancer in Virginia can't send a Sydney user to a nearby server, or help if Virginia goes down. <strong>Global load balancing</strong> picks which region a user reaches. The <a href=\"/learn/dns-and-cdn\">DNS and CDN chapter</a> covers both main tools:</p>\n<ul>\n<li><strong>DNS-based routing</strong> answers each lookup with a region chosen by location, latency or health (<a href=\"/learn/dns-and-cdn#dns-routing-policies\">routing policies</a>). Its weakness is caching: old answers linger until they expire. AWS notes that clients may keep using a removed address until its 60-second time-to-live runs out. See <a href=\"/learn/dns-and-cdn#why-dns-failover-is-slow\">why DNS failover is slow</a>.</li>\n<li><strong>Anycast</strong> announces one IP address from many sites, and internet routing sends each user to the nearest one. A failed site stops announcing, and traffic moves within seconds. See <a href=\"/learn/dns-and-cdn#anycast-routing-one-address-many-places\">Anycast routing</a>.</li>\n</ul>\n<p>A large site stacks them: DNS or anycast picks the region, a layer 4 tier spreads connections, a layer 7 tier picks a pool, and the servers do the work.</p>","headings":[{"id":"global-load-balancing","title":"Global load balancing","depth":3}]},{"id":"horizontal-scaling-and-what-it-costs","title":"Horizontal scaling and what it costs","html":"<h3 id=\"horizontal-scaling-and-what-it-costs\">Horizontal scaling and what it costs</h3>\n<p>Load balancers make <strong>horizontal scaling</strong> possible: adding more ordinary machines. <strong>Vertical scaling</strong> means replacing one machine with a bigger, pricier one. <a href=\"/learn/scalability#scaling-up-a-bigger-machine\">Scalability</a> compares them.</p>\n<p>For the app tier, scaling out on commodity machines usually wins:</p>\n<ul>\n<li><strong>Cost.</strong> Ordinary servers give more capacity per dollar than top-end machines.</li>\n<li><strong>Availability.</strong> Losing one of ten small servers costs 10% of capacity. Losing your one big server costs all of it.</li>\n<li><strong>Hiring.</strong> More engineers know common hardware and open-source software than know a particular vendor's enterprise system.</li>\n</ul>\n<p>GitHub's own load balancers went this way. Its GLB post describes the old setup as a few large, vertically scaled HAProxy machines, where \"scaling horizontally was deemed too difficult.\" GLB was built to run on commodity hardware and scale out.</p>\n<p>Scaling out has three costs.</p>\n<p><strong>1. Complexity.</strong> You need a reliable way to build identical servers and deploy every change to all of them. The Scalability chapter covers images, containers and autoscaling.</p>\n<p><strong>2. Servers must be stateless.</strong> If a server keeps a session or an uploaded photo locally, the user's next request may land on a server without it. Keep sessions in a shared database or cache such as Redis, and files in object storage. Choose the session store carefully: Memcached keeps data only in memory, so a restart logs out everyone whose session it held. Redis with persistence turned on, or a database, survives restarts.</p>\n<p><strong>3. Downstream servers see more connections.</strong> Each app server opens its own connections to the cache and database. If each Crumb server keeps 20 database connections, 5 servers need 100 and 50 servers need 1,000. PostgreSQL's <a href=\"https://www.postgresql.org/docs/current/runtime-config-connection.html\" target=\"_blank\" rel=\"noopener noreferrer\">documentation</a> gives its default limit as \"typically 100,\" and says raising it costs more shared memory. Growing the app tier can exhaust the database's connections even when query volume barely changes. Fixes include a connection pooler that lets many app servers share fewer real connections, smaller pools per server, and sending more reads to the cache and replicas.</p>","headings":[{"id":"horizontal-scaling-and-what-it-costs","title":"Horizontal scaling and what it costs","depth":3}]},{"id":"check-yourself","title":"Check yourself","html":"<h3 id=\"check-yourself\">Check yourself</h3>\n<details>\n<summary>Crumb uses round robin across four identical servers. Most requests take 20 ms, but searches take up to 2 seconds. At dinner time one server's latency spikes while the others look fine. What's happening, and what would you change?</summary>\n<p>Round robin gives each server the same number of requests regardless of cost. One server has picked up several slow searches by chance, and it keeps getting its full share of new requests on top of them. Switch to a method that sees current load, such as least outstanding requests or the power of two random choices, so new requests avoid the busy server. You could also send <code>/search</code> to its own pool with a layer 7 rule, so slow searches can't delay ordinary pages.</p>\n</details>\n<details>\n<summary>An app server crashes. With AWS's default health checks, roughly how long does it keep receiving requests, and how could you shorten that?</summary>\n<p>Checks run every 30 seconds, and two failures in a row mark a server down. If it dies just after a passing check, it is marked down about a minute later, and requests sent to it in that minute fail. Shorten it by checking more often (HAProxy's default is every 2 seconds) and adding passive checks that remove a server as soon as real requests to it fail. Retrying failed requests on another server also hides some errors from users.</p>\n</details>\n<details>\n<summary>Crumb wants to send `/upload` to a separate pool and keep TLS certificates off the app servers. Does it need layer 4 or layer 7 load balancing? Why?</summary>\n<p>Layer 7. Every request arrives on the same address and port 443, so a layer 4 load balancer can't tell an upload from a recipe view. A layer 7 load balancer reads the path, but to do so it has to decrypt the request, so it terminates TLS. That also keeps the certificate and private key on the load balancer only. If traffic must stay encrypted inside the data center, it can re-encrypt toward the app servers.</p>\n</details>\n<details>\n<summary>Crumb's cache servers sit behind a load balancer that hashes the URL and divides by the number of servers. After a fifth cache server is added, the database is overloaded. Why, and what's the fix?</summary>\n<p>Going from four servers to five changes the result of the division for most URLs. Most requests now reach a cache server that doesn't hold their page, miss, and fall through to the database at once. Consistent hashing fixes this: adding a fifth server moves only about a fifth of the keys, and the rest keep hitting the server that already caches them.</p>\n</details>\n<details>\n<summary>A teammate says, \"Our load balancer is a managed cloud service, so it can't be a single point of failure.\" How would you respond?</summary>\n<p>A managed load balancer is usually redundant within a region, so one machine dying won't take it down. It is still one dependency for the whole site. In December 2012, AWS's load balancing service lost configuration data and some customer load balancers in one region failed for hours, including several Netflix used, while the servers behind them were healthy. Plan for it with more than one region behind global load balancing, few configuration changes during incidents, and monitoring from outside the load balancer so you can tell \"servers are down\" from \"nothing can reach them.\"</p>\n</details>\n<h3 id=\"further-reading\">Further reading</h3>\n<p>Background and classics:</p>\n<ul>\n<li><a href=\"https://en.wikipedia.org/wiki/Load_balancing_(computing)\" target=\"_blank\" rel=\"noopener noreferrer\">Load balancing (computing), Wikipedia</a>: a broad overview of algorithms and approaches.</li>\n<li><a href=\"http://www.haproxy.org/download/1.2/doc/architecture.txt\" target=\"_blank\" rel=\"noopener noreferrer\">HAProxy architecture guide (1.2)</a>: Willy Tarreau's 2008 working configurations for cookie persistence, a VRRP pair of load balancers, and layer 4 load balancers in front of HAProxy. Old, but the patterns still apply.</li>\n<li><a href=\"https://web.archive.org/web/20221128230738/https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale/\" target=\"_blank\" rel=\"noopener noreferrer\">Inside NGINX: How We Designed for Performance &#x26; Scale</a>: how NGINX's event-driven design handles hundreds of thousands of connections. Archived copy, since the original now redirects.</li>\n<li><a href=\"https://web.archive.org/web/20240117134735/https://www.nginx.com/resources/glossary/layer-4-load-balancing/\" target=\"_blank\" rel=\"noopener noreferrer\">What is layer 4 load balancing?</a> and <a href=\"https://web.archive.org/web/20220517230652/https://www.nginx.com/resources/glossary/layer-7-load-balancing/\" target=\"_blank\" rel=\"noopener noreferrer\">What is layer 7 load balancing?</a>: NGINX's short definitions of the two layers, as archived copies.</li>\n<li><a href=\"https://g33kinfo.com/info/round-robin-vs-weighted-round-robin-lb/\" target=\"_blank\" rel=\"noopener noreferrer\">Round robin vs weighted round robin</a>: a short 2010 explanation of how weights share load across servers of different sizes.</li>\n<li><a href=\"https://web.archive.org/web/20220530193911/https://www.lecloud.net/post/7295452622/scalability-for-dummies-part-1-clones\" target=\"_blank\" rel=\"noopener noreferrer\">Scalability for Dummies, part 1: clones</a>: Sebastian Kreutzberger's 2011 case for servers that keep no user data.</li>\n</ul>\n<p>Real configuration and products:</p>\n<ul>\n<li><a href=\"https://www.haproxy.org/download/2.8/doc/configuration.txt\" target=\"_blank\" rel=\"noopener noreferrer\">HAProxy configuration manual</a>: the <code>balance</code> section explains each algorithm and when to use it. See <code>fall</code>, <code>rise</code>, <code>inter</code> and <code>slowstart</code> for health checks.</li>\n<li><a href=\"https://nginx.org/en/docs/http/load_balancing.html\" target=\"_blank\" rel=\"noopener noreferrer\">NGINX load balancing guide</a>: round robin, least connections, IP hashing, weights and passive health checks in a few short examples.</li>\n<li><a href=\"https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-listener-config.html\" target=\"_blank\" rel=\"noopener noreferrer\">Classic Load Balancer listeners</a>: AWS on TCP versus HTTP listeners, TLS termination, <code>X-Forwarded-For</code> and the proxy protocol.</li>\n<li><a href=\"https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html\" target=\"_blank\" rel=\"noopener noreferrer\">What is a Network Load Balancer?</a> and <a href=\"https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html\" target=\"_blank\" rel=\"noopener noreferrer\">Application Load Balancer target groups</a>: a real layer 4 and layer 7 load balancer, including algorithms, draining, slow start and stickiness.</li>\n<li><a href=\"https://docs.aws.amazon.com/elasticloadbalancing/latest/application/target-group-health-checks.html\" target=\"_blank\" rel=\"noopener noreferrer\">ALB health checks</a>: default timings and the fail-open rule.</li>\n<li><a href=\"https://aws.amazon.com/elasticloadbalancing/features/\" target=\"_blank\" rel=\"noopener noreferrer\">Elastic Load Balancing features</a>: AWS's comparison of its load balancer types.</li>\n<li><a href=\"https://www.postgresql.org/docs/current/runtime-config-connection.html\" target=\"_blank\" rel=\"noopener noreferrer\">PostgreSQL connection settings</a>: <code>max_connections</code> and why raising it costs memory.</li>\n</ul>\n<p>Research and real systems:</p>\n<ul>\n<li><a href=\"https://www.eecs.harvard.edu/~michaelm/postscripts/handbook2001.pdf\" target=\"_blank\" rel=\"noopener noreferrer\">The Power of Two Random Choices</a>: Mitzenmacher, Richa and Sitaraman's survey. The introduction is readable without the maths.</li>\n<li><a href=\"https://research.google/pubs/maglev-a-fast-and-reliable-software-network-load-balancer/\" target=\"_blank\" rel=\"noopener noreferrer\">Maglev</a>: Google's NSDI 2016 paper on a software network load balancer at Google's scale.</li>\n<li><a href=\"https://github.blog/engineering/infrastructure/introducing-glb/\" target=\"_blank\" rel=\"noopener noreferrer\">Introducing the GitHub Load Balancer</a>: GitHub's move from a few big HAProxy machines to layer 4 and layer 7 tiers on commodity hardware.</li>\n<li><a href=\"https://aws.amazon.com/message/680587/\" target=\"_blank\" rel=\"noopener noreferrer\">AWS's summary of the December 24, 2012 ELB event</a> and <a href=\"https://web.archive.org/web/20170210200715/http://techblog.netflix.com:80/2012/12/a-closer-look-at-christmas-eve-outage.html\" target=\"_blank\" rel=\"noopener noreferrer\">Netflix's view of the same outage</a>: one incident seen from the provider's side and the customer's side.</li>\n</ul>","headings":[{"id":"check-yourself","title":"Check yourself","depth":3},{"id":"further-reading","title":"Further reading","depth":3}]}]}