Your App Is Slow. Now What?
Traffic doubled and your server is struggling. You have two basic options:
- Vertical scaling (scale up): get a bigger machine: more CPU, RAM, faster disks.
- Horizontal scaling (scale out): add more machines and spread the load across them.
Both work. They have very different costs, limits and engineering requirements, and real systems usually use both at different layers.
🎯 Quick Answer (30-Second Read)
- Vertical: simplest. No code changes. But there's a ceiling (the biggest machine available), costs rise steeply near the top, and one machine is a single point of failure.
- Horizontal: nearly unlimited, better fault tolerance, can scale automatically. Requires stateless app servers, a load balancer, and harder work for databases.
- Typical pattern: scale app servers horizontally, scale the database vertically first, then add read replicas, then shard.
- Start simple: a well-tuned single server goes a long way. Scale out when you need resilience or have hit the ceiling.
The Two Approaches
4 CPU · 16 GB"] -->|"upgrade"| S2["Server
64 CPU · 512 GB"] end subgraph H["Horizontal: scale out"] LB["Load balancer"] --> A1["Server"] LB --> A2["Server"] LB --> A3["Server"] LB --> A4["+ more"] end
Side-by-Side
| Vertical (up) | Horizontal (out) | |
|---|---|---|
| How | Bigger machine | More machines |
| Code changes | Usually none | App must be stateless; data must be shared or split |
| Upper limit | Largest available machine | Very high |
| Fault tolerance | One machine = single point of failure | Survives losing machines |
| Cost curve | Rises steeply for top-end machines | Roughly linear |
| Downtime to scale | Often a restart | Add servers live |
| Complexity | Low | Higher: load balancing, distributed data |
What Makes Horizontal Scaling Possible
Move state out of the app servers:
- Sessions → Redis, the database, or signed cookies/JWTs
- Uploaded files → object storage (S3, Supabase Storage)
- Caches → a shared cache like Redis
- Background jobs → a queue that any worker can consume
Then a load balancer spreads requests, and autoscaling adds or removes servers based on load. (See Load Balancer Explained.)
The Hard Part: Scaling the Database
App servers are easy to clone. Databases hold state, so they scale in stages:
(vertical)"] --> B["2. Read replicas
reads spread out"] B --> C["3. Caching
(Redis) for hot reads"] C --> D["4. Sharding
split data across servers"]
- Scale up the database server. Often enough for a long time.
- Read replicas: copies that serve reads, while one primary handles writes. Great for read-heavy apps; replicas may lag slightly.
- Caching: keep frequently read data in memory.
- Sharding: split data across multiple databases (by user ID, region, etc.). Powerful but complex: cross-shard queries and rebalancing get hard. (See Consistent Hashing Explained and Instagram System Design.)
A Real Progression
| Stage | Setup |
|---|---|
| Launch | One server running app + database |
| First growth | Separate database server; scale both up |
| Thousands of users | 2+ stateless app servers behind a load balancer (also gives redundancy) |
| Read-heavy growth | Read replicas + Redis cache |
| Massive scale | Sharded or distributed database, multiple regions |
Many successful products stay at the middle stages for years. Stack Overflow, for example, has famously run on a small number of powerful servers, showing how far vertical scaling plus good engineering can go.
How to Decide
- Need more capacity quickly, app isn't stateless yet? Scale up.
- Need high availability (no single point of failure)? Scale out with at least two servers.
- Traffic is spiky? Horizontal + autoscaling.
- Database is the bottleneck? Optimise queries and indexes first, then scale up, then replicas and caching, then shard.
Frequently Asked Questions
What is the difference between horizontal and vertical scaling?
Vertical scaling adds resources like CPU and RAM to a single machine. Horizontal scaling adds more machines and distributes the load among them. Vertical is simpler but limited and creates a single point of failure; horizontal scales further and tolerates failures but adds complexity.
Is horizontal scaling always better?
No. It requires stateless application design, load balancing and distributed data strategies, which add complexity. For many applications, a well-optimised single server scaled vertically is cheaper and simpler. Horizontal scaling becomes necessary for high availability or very large workloads.
How do you scale a database horizontally?
Common approaches are read replicas, which copy data to additional servers that handle reads, and sharding, which splits data across multiple databases by a key such as user ID. Distributed SQL and NoSQL databases handle partitioning automatically.
Conclusion
Vertical scaling buys a bigger machine; horizontal scaling adds more machines. Make app servers stateless so they can scale out behind a load balancer, scale the database up first, then add replicas, caching and finally sharding as needed. Choose the simplest option that meets your capacity and availability needs.
Sources
- AWS: Well-Architected Framework, reliability pillar (scale horizontally)
- PostgreSQL docs: High availability, load balancing and replication
- Stack Overflow blog: Stack Overflow's architecture
Last verified: 2 November 2026.
Related reads: Load Balancer Explained · Consistent Hashing Explained · CAP Theorem Explained · Why Apps Crash During High Traffic
I write about system design and how real products are built. Follow on LinkedIn or X, or invite me to speak.