Lesson 12 · 25 min read
ALB and Auto Scaling: Scaling to 1000+ Concurrent Users
One server becomes many. Health checks, target groups, and servers that appear when traffic spikes and vanish when it drops.
What you'll learn in this lesson
- Concept: many servers, one address
- Why one server is not enough
- The prerequisite: stateless servers
- What does “1000 concurrent users” actually mean
- How an ALB works: listener, rule, target group, health check
- How Auto Scaling works: template, group, policy
- Build it, step by step
- Health checks done properly
- Choosing a scaling policy
- Deploying without downtime
- Prove it: load test and kill a server
- The common pitfalls that cause real 502s
- What this costs
- Troubleshooting table
- Interview corner
- Practice task before Lesson 13
- Conclusion
Concept
Two AWS services, one job: make your app survive success.
- An Application Load Balancer (ALB) is an AWS service that gives users one stable address. It shares their requests across many identical servers. It sends traffic only to servers that are healthy.
- Auto Scaling is an AWS service that decides how many servers exist. It starts more servers when traffic rises. It replaces servers that fail. It removes extra servers when the busy time is over.
The ALB is the host at the door. The host greets everyone and sends each group to a free table. The host never seats anyone at a table with a broken leg (that is the health check). Auto Scaling is the manager. The manager calls in more waiters when the queue grows and sends them home when it is quiet. The customers only ever see one restaurant.
Why one server is not enough
- A limit on capacity. A t3.small has 2 vCPUs (virtual CPU cores). After a certain point, extra requests only wait in line, slow down, and then time out.
- A single point of failure. This means one part that stops everything when it breaks. With one server, one crash, one bad deploy or one reboot for updates takes the whole site down.
- Deploys cause downtime. With one server, users have nowhere to go while you restart it.
- You pay for the peak all month. A server big enough for the busiest hour sits idle for the other 23 hours. Elastic capacity (capacity that grows and shrinks) charges you only for what you use.
Now the comparison between AWS and Vercel from Lesson 0 becomes real. Vercel scaled your app for you, out of sight. You are now building the same behaviour yourself. You can see every part and change every part.
The prerequisite: stateless servers
Load balancing has one strict rule: any request may go to any server. A stateless server keeps no data of its own between requests. If a server remembers something that the other servers do not, users get random bugs that are hard to find. Lessons 8 and 11 removed the two biggest causes (the database and the files). Check the rest before you scale:
counter = 1counter = 1counter = 1Each instance disappears after answering. Nothing carries over.
counter = 1counter = 2counter = 3Same process every time, so memory survives between requests.
| If your app does this… | …scaling breaks like this | Fix |
|---|---|---|
| User sessions in server memory | Server B does not know that the user logged in on server A | Signed cookie or JWT (a JSON Web Token: a small signed piece of text that holds the login proof, kept in the browser), or Redis (Lesson 16) |
| Uploaded files on local disk | Only one server has the file | S3 (Lesson 11) |
| Data in a local database or SQLite file | Each server has different data | RDS (Lesson 8) |
| In-memory cache or counters | Each server has its own copy, so answers differ | Redis (a fast data store that many servers can share), or accept a separate cache on each server for data that is not critical |
| Cron jobs / setInterval in the app (a cron job is a task that runs on a timer, for example every night) | With N servers, the job runs N times | Use one scheduler (EventBridge is an AWS service that starts tasks on a schedule), or a single worker. Never run it in the web servers |
| Settings or secrets in a file that you edit by hand | New servers start without that file | SSM Parameter Store or Secrets Manager (AWS services that keep settings and secrets safe), read when the server boots |
| Sticky assumptions like “the same server next time” | The load balancer may pick any server | Make every request work on any server |
The most common failure after “we added a second server” is that users are logged out on every other click. Do this check first. It is much cheaper than debugging in production.
What does “1000 concurrent users” actually mean
The lesson title promises 1000+ concurrent users. Before you buy anything, learn what that number means. “Concurrent users” are people who use the site at the same time. A request is one call from a browser to your server. Concurrent users are not requests per second. A person spends most of the time reading, not clicking.
requests per second ≈ concurrent users ÷ seconds between actions. If 1,000 users each do something every 10 seconds, that is about 100 requests per second. If each does something every 3 seconds, it is about 330 requests per second. One page load is several requests (HTML, API calls, files). But with a CDN, the files never reach your servers.
Next, ask how many requests one server can handle:
| Kind of request | Rough throughput per vCPU |
|---|---|
| Tiny JSON response, no database | 500–2,000 req/s |
| Server-rendered page, a couple of database queries | 30–150 req/s |
| Heavy: image processing, reports, big joins | 1–10 req/s |
These ranges are wide on purpose. You must measure your own app (see the load-test section). Say your app does about 60 requests per second per vCPU. Then a t3.small (2 vCPUs) handles about 120 requests per second. 1,000 casual users send about 100 requests per second. That is close to the limit of one server. You also need extra room for spikes and for the loss of one server, so you need 2–3 servers. That is why the group has a minimum of 2 (one in each Availability Zone, or AZ: a separate group of data centres inside a region) and a maximum of 6.
One more warning about t3 servers. They are burstable. They can use more than their normal CPU share only while they have CPU credits. Under steady heavy load the credits run out. Then the server slows down, or (in the default “unlimited” mode) AWS charges extra. For steady heavy load, test a non-burstable type such as m7i.
A connection pool is a set of ready database connections that a server reuses. Six servers with a pool of 10 each use 60 database connections. A small RDS instance allows about 100 (Lesson 8). When you scale the web servers, work out the connection count at the maximum number of servers. You can also use RDS Proxy, an AWS service that shares database connections for you.
How an ALB works: listener, rule, target group, health check
| Part | What it is | Ours |
|---|---|---|
| Load balancer | The public entry point. It lives in at least two public subnets (parts of your network that the internet can reach) in two AZs. It gets a DNS name, not a fixed IP. | myapp-alb |
| Listener | “Listen on this port and protocol.” Holds the rules and the TLS certificate. | 443 HTTPS, 80 HTTP → redirect |
| Rule | “If host/path/header matches, do this action” (forward, redirect, fixed response). | default: forward to myapp-tg |
| Target group | The group of servers that share the work, plus the rules for checking their health. | myapp-tg, port 80 |
| Health check | The ALB asks for a path every few seconds. Only servers that answer 200 (“OK”) receive traffic. | GET /api/health |
Rules let one ALB serve many apps. A host-based rule looks at the domain name: api.yourapp.com goes to the API target group, and yourapp.com goes to the web target group. A path-based rule looks at the URL path: /api/* goes to one group and everything else goes to another. This way, one load balancer (about $20 a month) does the work of several.
Which load balancer?
AWS has four types. The “layer” is a level in the network model. A layer 7 balancer can read web requests. A layer 4 balancer only sees connections (TCP or UDP).
| Type | Works at | Use for |
|---|---|---|
| ALB (Application) | Layer 7 (the application layer): understands HTTP. Routes by host name, path or header. Also handles TLS, WebSockets and redirects | Web apps and APIs — our choice |
| NLB (Network) | Layer 4 (the transport layer): passes raw TCP/UDP traffic (TCP and UDP are the two basic ways to send data over a network). Handles millions of connections, gives fixed IPs, and adds very little delay | Databases, games, protocols that are not HTTP, or when you need a fixed IP |
| Gateway LB | Layer 3 (the network layer): sends traffic through firewall or inspection appliances | Security appliances (rare) |
| Classic LB | The old load balancer that came before ALB and NLB | Do not use for new work |
The ALB ends the HTTPS connection (TLS is the protocol that encrypts HTTPS traffic). It uses a free ACM certificate that AWS renews for you. So the servers behind it no longer need Certbot or port 443. They speak plain HTTP on port 80 inside the VPC. This is the reward for learning about ACM in Lesson 4.
How Auto Scaling works: template, group, policy
There are three parts. Each one builds on the one before:
- Launch template is a saved recipe for one server. It holds the AMI (a saved copy of a server disk with the operating system), the instance type (the size), the key, the Security Group, the IAM profile (the permissions the server gets), and a user-data script that runs on the first boot. It has versions, so you can change it and go back to an older one.
- Auto Scaling Group (ASG) is a group that follows this rule: “keep between min and max servers from this template, spread across these subnets, and registered in this target group.” It replaces failed servers automatically.
- Scaling policy is the rule that moves the wanted number of servers between min and max. For example: “keep the average CPU near 50%”.
The server in Lesson 7 was a pet. It had a name, you cared for it by hand, and you could not replace it. ASG servers are cattle. They are all the same, they have numbers, and when one is sick you replace it instead of repairing it. The user-data script makes the replacement fast. A new server must get fully ready with zero human steps. This is the reason Lessons 9 and 11 came first.
Build it, step by step
You need the network from Lesson 6, an image in ECR (the AWS store for Docker images, Lesson 9) and a domain (Lesson 3). Each step is one console screen. We only mention the fields that matter.
Request the HTTPS certificate
Open ACM (AWS Certificate Manager) in
ap-south-1, the ALB's region. Request a public certificate foryourapp.comand*.yourapp.com. Choose DNS validation. This means you prove that you own the domain by adding a DNS record. Click “Create records in Route 53”, and the certificate is ready in a few minutes. The wildcard covers every tenant subdomain. The bare domain is listed on its own, because*.yourapp.comdoes not matchyourapp.comitself.Create the target group (the pool)
Go to EC2 → Target groups → Create. Choose type Instances, HTTP port 80, and the
myappVPC. Set the health check path to/api/health, the interval to 15 s, healthy after 2 passes, and unhealthy after 3 failures. Do not register any targets. Auto Scaling will do that. Then, under Attributes, lower the deregistration delay from 300 to 30 seconds. This is how long the ALB waits for requests that are still running on a server before it removes the server.Create the load balancer and its listeners
Go to EC2 → Load balancers → Application Load Balancer. Choose internet-facing, the two public subnets, and the security group
alb-sg. Add two listeners:- HTTPS 443: forward to
myapp-tg, with the ACM certificate. The encryption ends here. - HTTP 80: redirect to HTTPS 443 (a 301 “moved permanently” answer).
alb-sg(Lesson 6) already accepts ports 80 and 443 from everyone.web-sgaccepts port 80 only from alb-sg. So the public can no longer reach a server directly. Remove the temporary0.0.0.0/0rules for ports 80 and 443 onweb-sgthat Lesson 10 added.- HTTPS 443: forward to
Store the deploy settings where new servers can read them
A server that Auto Scaling starts at 3 a.m. has no human to tell it which image to run. It reads this from SSM Parameter Store. This is an AWS service that stores settings and secrets as named values. It is free for standard parameters. Create two:
/myapp/image-tagis a plain String. It holds the commit SHA (the short ID of a Git commit) to run, for examplea1b2c3d./myapp/envis a SecureString (it is stored encrypted). It holds the whole production env file, includingDATABASE_URL, plusKEEP_ALIVE_TIMEOUT=65000(see the common pitfalls below).
Then give
myapp-ec2-rolepermission to read the/myapp/*parameters and to pull from themyappECR repository. Use the AWS-managedAmazonEC2ContainerRegistryReadOnlypolicy, plus a small inline policy that allowsssm:GetParameteron that path. Because the parameter is encrypted, the role also needs permission to decrypt it with its KMS key (KMS is the AWS Key Management Service, which holds encryption keys; the default AWS-managed key needs no extra rule).Write the user-data script — the boot recipe
User data is a script that runs once, as the root user, the first time a server boots. With it, a brand-new machine turns itself into one of your app servers while nobody is logged in:
user-data.sh — the shape of it#!/bin/bash set -euxo pipefail # 1. install Docker and the AWS CLI # 2. read which version to run, and its settings (never hard-code them) TAG=$(aws ssm get-parameter --name /myapp/image-tag --query Parameter.Value --output text) aws ssm get-parameter --name /myapp/env --with-decryption \ --query Parameter.Value --output text > /etc/myapp.env # 3. log in to ECR and start the container on port 80 docker run -d --name myapp --restart unless-stopped \ --env-file /etc/myapp.env -p 80:3000 $REGISTRY/myapp:$TAGThe container is published on port 80, because the target group sends traffic to port 80 and
web-sgallows port 80 from the ALB. If the boot fails, read the log at/var/log/cloud-init-output.log.Create the launch template
Go to EC2 → Launch templates → Create. Fill it in like the server in Lesson 7 (IMDSv2 is the safer version of the metadata service explained below; gp3 is a type of SSD disk): Ubuntu 24.04,
t3.small,web-sg, themyapp-ec2-roleprofile, a 20 GB encrypted gp3 disk, and IMDSv2 required. Paste the script above into User data. There are two differences from Lesson 7:- Metadata hop limit: 2. The instance metadata service (IMDS) is a local address where a server gets its role credentials. A container is one network hop further away from it than the host. With the default limit of 1, your app inside Docker cannot get the role's credentials, and every AWS call fails with “could not load credentials”.
- No key pair, on purpose. Production servers should not accept SSH logins (SSH is the tool for logging in to a server from far away). In Lesson 18 you will get a shell through SSM Session Manager instead.
Create the Auto Scaling group
Go to EC2 → Auto Scaling groups → Create, and use the launch template. These settings matter most:
- Two subnets in two AZs, min 2, desired 2, max 6. If one whole data centre is lost, one server is still running.
- Attach it to
myapp-tg, and turn on ELB health checks. Then the group replaces servers that the load balancer reports as unhealthy. Without this, the group only checks the EC2 machine. It would miss a server whose machine is fine but whose app is dead. - Health check grace period 180 s. Do not judge a new server for 3 minutes while it boots and pulls the image. Make this longer than your real boot time.
Add the scaling policy
On the same screen, choose Target tracking, the metric Average CPU utilisation, and the target 50. This means: “keep the average CPU across the group near 50%.” When the CPU is above 50%, the group adds servers. When it is well below, the group removes servers. AWS creates and manages the CloudWatch alarms for you (CloudWatch is the AWS monitoring service).
Watch it come alive
The group's Activity tab shows “Launching a new EC2 instance…”. If something fails, it also shows why. After 2–4 minutes, the target group's Targets tab shows both servers as healthy. Then open the ALB's DNS name in a browser to reach the app.
Health checks done properly
The health endpoint decides which servers stay and which are replaced. Keep it honest and cheap:
export const dynamic = "force-dynamic"; // never cache a health check
export function GET() {
return Response.json({ ok: true, version: process.env.APP_VERSION ?? "dev" });
}| Shallow check (above) | Deep check (also queries the database) | |
|---|---|---|
| Detects | The app process is stuck or has crashed | App alive and database reachable |
| Risk | If the database is down, the server returns errors but still counts as “healthy” | A short database problem marks every server unhealthy at once. The ASG then replaces them all. A small incident becomes a total outage |
| Use it for | The ALB target-group check | A separate /api/ready for dashboards and alerts, not for removing servers |
Remember this rule: a health check that can fail because of another service (a dependency) can cause a chain of failures. A health check answers “is this server broken?”. An alarm answers “is the whole system broken?”.
Choosing a scaling policy
| Policy | How it decides | Use when |
|---|---|---|
| Target tracking — CPU | Keep average CPU at a target | The default for apps that are limited by CPU. It is the simplest, and it is our choice |
| Target tracking — ALBRequestCountPerTarget | Keep requests per server at a target (e.g. 500/min) | Better when the app mostly waits (for example on the database) and the CPU stays low |
| Step scaling | “CPU > 70% add 1; > 90% add 3” | You want to decide yourself how strongly to react |
| Scheduled | “9 a.m. weekdays: min 4” | You know when traffic comes (a daily peak, a sale, a marketing email) |
| Predictive | Learns your daily and weekly pattern from past data, and adds servers before the load comes | Steady, repeating traffic. It needs at least 24 hours of history, and it uses up to 14 days |
Why scaling is never instant
A new server needs a few minutes. It must launch, boot, pull the image, and pass two health checks. So scaling reacts a few minutes after the load rises. There are three ways to be ready for sudden spikes: a higher minimum, a scheduled action before an event that you know about, and a lower CPU target (40% leaves more room than 70%).
A good rule is to scale out fast and in slowly. Imagine you remove a server during a short quiet moment, and five minutes later you need it again. You waste time and money. This up-and-down switching is called flapping. Target tracking already avoids it by waiting longer before it scales in.
Installing Docker on every boot costs about a minute. When things are stable, set up one server and run aws ec2 create-image. This saves the server as a new AMI (a “golden image”). Put that AMI in the launch template. New servers then start ready in seconds. Packer is a tool that builds such images automatically. Terraform (Lesson 15) is a tool that creates cloud resources from code, and it can connect it all.
Deploying without downtime
With many servers, a deploy is no longer “log in with SSH and restart”. The clean pattern is replace the servers, do not change them:
- CI (a robot that builds and tests your code on every change) pushes the new image to ECR, tagged with the commit SHA.
- Update
/myapp/image-tagto that SHA. - Start an instance refresh on the Auto Scaling group (min healthy 100%, max healthy 110%). An instance refresh is an ASG feature that replaces every server with a new one, a few at a time. AWS waits for each new server to pass its health check.
A rollback is the same move. Put the previous SHA back in the parameter and refresh again.
Min healthy 100% with max healthy 110% means AWS starts a new server before it retires an old one, so capacity never drops. With a very small group, a larger maximum such as 150% or 200% makes sure there is room for an extra server. The ALB only sends traffic to healthy targets, so users never reach a server that is still starting. The deregistration delay of 30 seconds lets requests that are still running on a retiring server finish. Lesson 14 starts the refresh from GitHub Actions.
When a server is retired, Docker first sends SIGTERM (a polite “please stop” signal). It waits a short time (10 seconds by default). Then it sends SIGKILL (a forced stop). The app should stop taking new connections and finish the current ones. The Next.js server handles this by itself. A custom Node server needs a process.on("SIGTERM", …) handler that calls server.close().
Prove it: load test and kill a server
Do not trust a design until you have tried to break it. Two experiments turn diagrams into real confidence.
Experiment 1 — does it scale?
hey -z 3m -c 200 https://yourapp.com/hey is a small tool that sends many requests and reports the speed. This command uses 200 connections at the same time for 3 minutes (install hey with brew; k6 is a good alternative). Meanwhile, watch the instance count of the Auto Scaling group grow in the console
Look at three numbers in the hey summary, and at one graph:
- Requests per second. This is your measured speed. Use it to improve the capacity estimate above.
- Latency percentiles (p50, p95, p99). p95 means that 95% of requests were faster than this number. Averages hide problems. The slowest 1% of users are the ones who complain.
- Status code counts. Any 5xx (server error) means something broke under load.
- In CloudWatch, look at the ALB's
TargetResponseTimeand the database'sDatabaseConnections. The database usually fails first.
Experiment 2 — does it heal?
While the load test runs, terminate one of the group's instances in the console, as if a data centre lost a machine. The ALB needs a few failed health checks, so it takes about a minute to mark the server unhealthy. A few requests may fail during that time. After that, the other server keeps serving the users. The group sees that it has fewer servers than wanted, and it starts a replacement by itself.
If the site shows errors for much longer than that, you have found a real weakness. It is usually a keep-alive timeout, a health check that is too slow, or a min-size of 1. It is cheap to fix it now.
The common pitfalls that cause real 502s
Keep-alive timeout: the intermittent 502
A keep-alive connection is a connection that stays open so that many requests can reuse it. The ALB reuses connections to your servers. It closes a connection after 60 seconds without traffic. Node closes idle connections after only 5 seconds by default. So sometimes the ALB sends a request on a connection that Node has just closed. The result is an instant 502. It happens for about one request in thousands, and you cannot reproduce it on your laptop. The rule is: the server's keep-alive timeout must be longer than the load balancer's idle timeout.
With the Next.js standalone server, you set this with the KEEP_ALIVE_TIMEOUT=65000 line that is already in /myapp/env (the value is in milliseconds). A custom Node server sets server.keepAliveTimeout = 65_000 and a slightly higher headersTimeout. Any value above the ALB's 60 seconds works.
Sticky sessions
The ALB can keep one user on the same server with a cookie. This is called “stickiness”. It is tempting when sessions live in server memory. But it spoils the even sharing of load, and the user loses the session when that server is replaced. Use it only as a short-term bridge while you move sessions to Redis (Lesson 16). It is not a real solution.
WebSockets
A WebSocket is a long-lived two-way connection (used for chat and live updates). ALBs support WebSockets with no extra setup. But such a connection ties a user to one server for as long as it lasts. Idle connections are closed after 60 seconds unless the app sends small “ping” messages. For such apps, scale on the number of connections, not on CPU.
Real client IP
Behind an ALB, every connection comes from the ALB. The user's address is in the X-Forwarded-For header. Set your framework to trust that header only from inside the VPC (see the real-IP note in Lesson 10). If you do not, every rate limit and log shows the ALB's IP.
What this costs
| Item | Approx. per month (prices differ a little by region) |
|---|---|
| Application Load Balancer, base | ~$0.0225/hour ≈ $16.50 |
| ALB usage (LCUs: connections, bytes, rules) | ~$0.008 per LCU-hour (an LCU is the unit AWS uses to measure ALB use); a small app ≈ $2–8 |
| 2 × t3.small, 24/7 | ~$32 |
| Public IPv4 per server + per ALB zone | ~$3.60 each (4 ≈ $14) |
| Auto Scaling, launch templates, target groups | Free |
| Extra servers during a spike | Billed per second, only while they exist |
| ACM certificate | Free |
A realistic total for this tier is about $60–75 a month. That is much more than one server. What you buy is: no single point of failure, deploys without downtime, and room to grow. If you do not need this yet, one server with Nginx is a good choice. Do not add an ALB only because the course has a lesson about it.
When you are not studying, set the ASG to --min-size 0 --desired-capacity 0 (with aws autoscaling update-auto-scaling-group …). It then removes every server. The ALB keeps charging you, so delete it too when you finish. Or run this whole tier for one weekend only and build it again with Terraform in Lesson 15.
Troubleshooting table
| Symptom | Likely cause | Fix |
|---|---|---|
| Targets show “unhealthy” in the target group | The health-check path does not return 200, the port is wrong, web-sg does not allow the ALB, or the app is still starting | Read the reason in the target's Health status details. Run curl on the path from the instance itself. Check that web-sg allows port 80 from alb-sg. If every target is unhealthy, the ALB still sends traffic to all of them (this is called fail-open) |
| 502 Bad Gateway from the ALB | The server closed the connection early (its keep-alive timeout is shorter than the ALB idle timeout), or it crashed during a request | Set the Node keepAliveTimeout above 60 s (see the common pitfalls). Check the app logs for that time |
| 503 Service Unavailable | The target group has no registered targets, for example because the Auto Scaling group has 0 servers | Check that the ASG launched instances (see Activity history) and that it is attached to the target group |
| 504 Gateway Timeout | The app took longer than the ALB idle timeout (60 s by default) | Find the slow endpoint. Move long work to a background job |
| Instances are launched and terminated in a loop | They fail the health check before the app is ready, so the ASG removes them | Raise --health-check-grace-period above the boot time. Read /var/log/cloud-init-output.log |
| Scaling never triggers | The metric is not moving, the policy uses the wrong metric, max-size is already reached, or the group is still in warm-up | Check the alarm state in CloudWatch and the group’s Max |
| Users get logged out randomly | Sessions are stored in server memory, and the ALB sends the user to another server | Move sessions to a cookie/JWT or to Redis. Use sticky sessions only as a short-term fix |
| Some users see old code, some new | A rolling deploy is still running, or one server missed the update | Deploy with an instance refresh so all servers end up on the same version. Tag images with the commit SHA |
Interview corner
What must be true of an application before you put it behind a load balancer?
It must be stateless. Sessions, uploads and data must live outside the server (cookies or JWT or Redis, S3, RDS). Settings must come from a shared place. Scheduled jobs must not run on every server.
ALB vs NLB?
An ALB works at layer 7. It understands HTTP, so it can route by host, path or header, and it handles TLS, redirects and WebSockets. An NLB works at layer 4. It handles TCP and UDP with very high speed and low delay, and it gives fixed IPs. I use an ALB for web apps and APIs. I use an NLB for protocols that are not HTTP, or when I need fixed IPs.
Explain how Auto Scaling replaces an unhealthy instance.
With ELB health checks turned on, the group treats a server that the load balancer marks unhealthy as unhealthy too. It terminates that server and starts a replacement from the launch template, so the wanted number of servers is restored. It waits for the grace period before it judges new servers.
Why can a database-dependent health check be dangerous?
If the shared dependency has a short problem, every server fails the check at the same time, and the group replaces all of them. A small problem becomes a total outage. A health check should test the server itself. The health of dependencies belongs in alarms.
You get random 502s, about 1 in 2,000 requests. Nothing in the app logs. Why?
This is the classic keep-alive mismatch. The backend closes idle connections early (Node waits 5 s by default), before the ALB's 60 s idle timeout. So the ALB sometimes reuses a connection that is already closed. I set the backend keep-alive timeout above the ALB idle timeout.
How do you deploy a new version with zero downtime behind an ASG?
I build an image that never changes after it is built. I update the launch template, or the parameter it reads. Then I run an instance refresh. It starts new servers before it retires old ones, waits for health checks, and lets running requests finish (connection draining). A rollback is the same steps with the previous version.
1,000 concurrent users — how many servers?
First I turn users into requests per second (users divided by seconds between actions). Then I measure one server's speed with a load test and divide. I add extra room for spikes and for the loss of one AZ. Finally I check the number of database connections at the maximum number of servers, because that is often the real limit.
Practice task before Lesson 13
- Check your app with the statelessness table. Write down anything that would break with two servers, and fix it or make a note of it.
- Build the tier: certificate, target group, ALB, parameters, launch template, ASG, policy. Wait until both targets are healthy and load your site via the ALB DNS name.
- Break the health check on purpose (change the path in the target group to
/nope). Watch the targets turn unhealthy. Notice that the ALB still sends traffic to them. When all targets are unhealthy, the ALB tries all of them (this is called fail-open). Restore the path quickly. With ELB health checks on, the ASG will soon start to replace the “unhealthy” servers. - Run the load test and the terminate-a-server experiment. Record requests/sec, p95 latency, and how long the replacement took.
- Publish a new image tag, update
/myapp/image-tagand run an instance refresh. Confirm the site stayed up the whole time. - Work out the monthly cost of your setup at min-size 2, and at max-size 6 for one hour a day. Which line is the biggest?
Add a second listener rule that forwards /api/* to a separate target group and a fixed 404 response for /admin from the internet. Rules are how one ALB becomes the front door for a whole platform.
Conclusion
You now run the same kind of setup that large sites use, only smaller: one address, many servers that are easy to replace, automatic repair, and automatic growth.
- ALB = listener + rules + target group + health checks. It ends HTTPS with a free ACM certificate.
- Auto Scaling = launch template + group + policy. Servers are cattle, made from a recipe with zero manual steps.
- Stateless first. Sessions, files, data and cron jobs must live outside the web servers.
- Health checks test the server, not its dependencies. Otherwise a short problem becomes an outage.
- Do the maths (requests per second, capacity per server, and the number of database connections at maximum size), and test by breaking things.
The ALB gives you a long, ugly DNS name. Next, we connect your real domain to it, and to CloudFront, with Route 53.
End of Lesson 12. Next: Lesson 13 — Route 53: Production Domain.
Published by Jainik · Last updated · How these lessons are made
Keep learning
Part of the study guide
Related series