Scalability Basics
Scalability is a system's ability to keep working well as load grows. Vertical scaling buys a bigger machine; horizontal scaling adds more machines. The key to horizontal scale is statelessness — if any instance can serve any request, you can add and remove instances freely.
What is this?
Scalability measures how a system handles growing workload — more users, more requests, more data — without degrading latency or availability. Vertical scaling adds CPU, memory and disk to a single machine, which is simple but has hard physical and cloud instance-size limits. Horizontal scaling runs the workload across many machines and is the strategy behind every large consumer platform, because it can grow (and shrink) without replacing hardware.
Why do you need it?
- Traffic is not steady: launch days, viral posts and sales events can multiply load 10-100x in minutes.
- Vertical scaling hits a ceiling — the biggest cloud instance has finite CPU, memory and I/O bandwidth.
- Horizontal scaling grows capacity linearly: double the instances, roughly double the throughput.
- Stateless horizontal scale gives you elasticity — scale up during a spike and back down after, saving cost.
- Distributing across machines also spreads risk: one failed server no longer takes down the whole service.
Mental model
A single checkout counter at a grocery store
Many checkout lanes with a traffic-directing manager
One counter can only serve one customer at a time; hiring a faster cashier (vertical) helps only up to a point. Adding lanes (horizontal) lets many customers check out in parallel — but now you need a manager pointing people to the emptiest lane. That manager is your load balancer.
How it works
- Requests enter through a single entry point — a DNS name or a load balancer — that hides the pool of servers behind it.
- Application instances are kept stateless: session data and caches live in shared stores (Redis, a database), so any instance can serve any request.
- A load balancer distributes each request to an instance using round-robin, least-connections or hashing.
- An autoscaler watches metrics (CPU, request latency, queue depth) and adds or removes instances to match demand.
- Data stores scale separately because state is the hard part: reads go to replicas, and only the data tier holds durable state.
From one server to an elastic pool of stateless instances behind a load balancer.
Key commands
docker compose up -d --scale api=3Start three stateless API replicas behind the compose service name.
Network created. Container api-1, api-2, api-3 Started.
docker compose psList the running instances and their status.
api-1 Up 1 minute (healthy) 0.0.0.0:8080->8080/tcp
curl -s http://localhost:8080/healthVerify one replica is healthy.
{"status":"ok"}ab -n 5000 -c 100 http://localhost:8080/ApacheBench: 5,000 requests with 100 concurrent connections.
Requests per second: 842.13 [#/sec] (mean)
docker compose logs --tail=20 apiTail logs across all replicas to spot errors or uneven load.
nprocPrint the CPU count — the vertical resource you are buying when you scale up.
8
Examples
A containerized API you can scale horizontally
# docker-compose.ymlservices: api: image: myapp/api:1.4 environment: - DATABASE_URL=postgres://db:5432/app - REDIS_URL=redis://cache:6379 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 10s timeout: 3s retries: 3The API reads its configuration from environment variables and has a health endpoint, so it is portable and stateless. Once the image is built, you run `docker compose up -d --scale api=3` to start three instances and the compose network load-balances between them. Every instance connects to the same Postgres and Redis, so any of the three can handle any request.
In production
- Streaming platforms run the stateless web tier behind an autoscaler driven by CPU utilization and request latency, adding instances during live events and removing them after.
- E-commerce platforms pre-scale to several times baseline before a flash sale, then let the autoscaler shrink the pool back down over the next hour.
- Batch and image-processing workers scale on queue depth: each worker pulls a job, processes it, and exits, so you can run hundreds of workers without worrying about routing.
- Multi-region deployments replicate the same stateless tier in each region so users are served by the nearest pool, with only the data layer needing cross-region care.
Best practices
- Keep application instances stateless: sessions, locks and temp files belong in shared stores or object storage, never on instance disk.
- Make instances disposable — a new instance must be able to boot, register and serve without manual setup.
- Define a health check that reflects real readiness (DB connection, migrations applied), not just process liveness.
- Instrument request latency, error rate and queue depth before scaling; scale on those, not on raw CPU alone.
- Use autoscaling with warm-up and cool-down windows so you do not churn instances during a burst.
Common mistakes
- Scaling the wrong tier: adding API servers while the shared database or a lock service remains the actual bottleneck.
- Storing sessions in memory, then discovering that horizontal scaling randomly logs users out.
- Making instances stateful (uploaded files on disk, in-process caches) so a new instance behaves differently from the rest.
- Sizing for the peak and paying for it all year instead of using elasticity to scale down when traffic is quiet.
Common issues
OutOfMemoryError / Node.js 'heap out of memory' after scaling upEach instance now handles more concurrent requests, or a leak compounds across more instances.
Add memory limits and connection pooling, tune the runtime heap, and inspect heap dumps for the leak.
p95 latency spiked right after adding instancesAll new instances cold-started at once and hammered the database, or warmed caches all expired together.
Pre-warm caches, stagger instance startup, and ramp traffic gradually instead of a full cutover.
All traffic is hitting one instanceSticky sessions or a hash that is not randomized after scale-out.
Remove session affinity or rebalance on instance join; keep instances stateless so routing can be free.
Debugging tips
- Check the load balancer's access log for the instance ID behind each request to spot a single hot instance.
- Compare p50, p95 and p99: if p95 is fine but p99 explodes, look for GC pauses, cold cache misses or tail latency in a shared store.
- Watch the request queue depth at the entry point — if it grows while instance CPU is idle, the bottleneck is downstream, not the app tier.
- Correlate the deployment timestamp with the latency change; many 'scale' problems are actually bad-release problems.
Interview checkpoint
Hands-on lab
- Package a stateless API as a container
- Run one instance and verify health
- Scale the pool to five replicas behind the compose network
- Observe how in-process state breaks horizontal scaling
Scale a Stateless App from 1 to 5 Instances
30 min · beginner
Quick quiz
Which of the following is an example of horizontal scaling?
A stateless application can serve a request from any instance in the pool.
A sales page goes viral and CPU on your single API server is pegged at 100%. You want minimal change and fast relief tonight. What is the best first step?
Cheat sheet
Vertical scalingBigger single machine; simple, boundedHorizontal scalingMore machines; needs statelessnessStateless instanceNo session/disk state; any instance serves any requestAutoscalingAdd/remove instances from metricsElasticityScale up during spikes and back down to save costdocker compose up -d --scale api=NScale a compose service to N replicasab -n 5000 -c 100 URLQuick load test of a stateless tierdocker compose psConfirm how many replicas are actually updocker compose logs --tail=50 apiCheck errors and request distribution