Scaling
🏠The everyday picture
A restaurant facing a queue can get a bigger stove (scale up) or open a second counter (scale out). The second option keeps working even if one counter breaks.
📘What it really is
-
Vertical scaling (up): a bigger server (more CPU and RAM). Simple, quick to do, but has a ceiling and leaves one point of failure.
-
Horizontal scaling (out): more copies of the app behind a load balancer (Modules 66 and 68). Handles more traffic and survives one copy failing.
-
Horizontal scaling needs a stateless app: nothing about a user kept only in one copy's memory or disk:
- sessions in the database or Redis (Module 44), not in memory;
- uploads in object storage (Module 45), not the local disk;
-
scheduled jobs run by only one copy (or a separate worker), so they don't run twice.
-
Autoscaling: platforms add and remove copies by CPU, memory or request rate. Set minimums (at least two for availability) and maximums (to cap cost).
-
The database is usually the bottleneck, not the app: fix queries and indexes (Module 57), add caching, use a connection pooler (Module 61), add read replicas for heavy reading, and only then consider bigger database servers.
-
Measure first (Module 72): scale because metrics show a need, not by guesswork.
- Load testing tools like k6 or Artillery simulate many users, on staging, to find limits before real users do.
🖱️Try it
Check your API is stateless: list anything stored in memory or on local disk, and move it if needed. In Compose, run two
copies of the API (docker compose up -d --scale api=2, with Nginx load balancing to both via an upstream). Run a simple
load test with k6 against staging and note requests per second and error rates.
✅Quick check
What does "stateless" mean for horizontal scaling?
Show the answer
No user data is kept only in one copy's memory or disk, so any copy can handle any request.
What's usually the bottleneck when an app gets busy?
Show the answer
The database; fix queries, indexes, caching and connection pooling first.
