Horizontal vs Vertical Scaling
Traffic climbs and something saturates — CPU, memory, disk, or connection count. Vertical scaling gives one machine more resources. Horizontal scaling adds machines and spreads work. The wrong move either burns budget on a larger box that remains a single point of failure, or fans out a stateful app that breaks sessions. Layer 4 vs Layer 7 load balancing explains how traffic reaches those added instances.
This article compares both, traces a request across a scaled-out API, and gives a progression you can justify with load-test evidence — not guesswork. When the bottleneck is stateful storage, use the separate database scaling techniques decision path.
Vertical Scaling (Scale Up)
Vertical scaling upgrades one host: more CPU, RAM, or faster disk. No load balancer, no distributed session redesign. For a monolithic API or a database with moderate traffic, resizing the instance is often the fastest path to relieve a measured bottleneck.
The ceiling is real: cloud instance families top out, and one machine remains one failure domain. Many resizes still need a maintenance window. Cost often rises faster than linear with size. When not to stop here: you need multi-instance availability, or profiling shows you are already on a large SKU with no headroom.
| Move | Use when | Trade-off |
|---|---|---|
| Scale up | One host is limited by CPU, memory, or I/O and can be resized | Simple change; finite ceiling and one host failure domain |
| Scale out | Work partitions across stateless workers or HA needs replicas | More capacity domains; routing and shared-state complexity |
Quick reference
- Best for: early growth, hard-to-shard workloads, single-primary databases.
- Strengths: minimal code change; direct relief for CPU/RAM/I/O bottlenecks.
- Weaknesses: hard ceiling, single failure domain, possible resize downtime.
- Database path: bigger primary first → read replicas → sharding only when needed.
- Resize from evidence: CPU steal, memory pressure, disk queue — not vibes.
- When not to: HA requires more than one healthy instance.
Remember this
Scale up first when a single measured resource is the bottleneck and HA is not yet the requirement.
Horizontal Scaling (Scale Out)
Horizontal scaling adds instances behind a load balancer. Autoscaling groups add capacity on CPU, request rate, or custom metrics, and remove it when load falls. That only works if the app is stateless on the node: sessions in Redis (or signed cookies), uploads in object storage, no reliance on local disk for durable data.
Request trace: Client → LB → instance A handles GET /orders/42 → Redis session lookup → Postgres → response. Failure path: instance A dies mid-request → LB stops routing new traffic there (health check fail) → retry or next request lands on instance B with the same session store. If sessions lived only in A’s memory, B cannot continue the user’s flow.
Quick reference
- Best for: web APIs, workers, HA, elastic traffic.
- Strengths: add/remove capacity without a single-box ceiling.
- Weaknesses: requires stateless design + LB + shared data stores.
- Stateless checklist: no local sessions, no local durable uploads, shared cache carefully keyed.
- Autoscale on evidence (CPU, RPS, queue depth); scale out fast, scale in slowly to avoid thrash.
- When not to: the workload cannot be partitioned and a single writer is mandatory — fix the data model first.
Remember this
Scale out only after the node is stateless — otherwise the load balancer multiplies pain.
Combining Both Strategies
Production systems mix strategies by tier. App servers usually go horizontal early. Databases often go vertical first, then read replicas for read scale, then partitioning when writes demand it. Caches and CDNs are horizontal by design.
Evidence-based progression: (1) load-test and find the bottleneck, (2) vertical if one resource is cheap to buy, (3) horizontal app tier + shared session/cache, (4) DB replicas, (5) shard or split only with a measured write ceiling. Skipping steps without metrics is how teams invent distributed complexity before they need it.
Quick reference
- App tier: prefer horizontal once traffic or HA needs more than one node.
- Database: vertical → replicas → shard/partition with a plan for cross-partition queries.
- Cache/CDN: horizontal; watch stampede and key design.
- Load-test before and after each change at the same percentile.
- Document the next bottleneck you expect — so the next change is deliberate.
Remember this
Use vertical and horizontal as a progression per tier, driven by measured bottlenecks.
Recover One Instance Failure Behind the Load Balancer
Trigger. Instance A, one of four behind the load balancer, crashes mid-request — an OOM kill, a bad deploy, a hardware fault. Symptom. The in-flight request to A fails; the next request from the same user, routed to instance B, either continues seamlessly or forces a re-login, depending entirely on where that user's session actually lived.
Root mechanism. The load balancer's health check needs one or more failed probes before it stops routing new traffic to A — a brief window where new connections can still land on a dead instance, the same detection-delay trade-off covered in Load Balancing: Layer 4 vs Layer 7. Separately, whether B can pick up the user's session depends on whether that session was ever node-local. Recovery: once the health check fails A, all new traffic routes to B, C, and D only; if sessions live in shared Redis, B reads the same session state A would have and the user never notices. Verification: kill one instance mid-session in a local two-process test and confirm the next request still authenticates on the survivor — the practice exercise below is exactly this test. Prevention: treat any node-local state (in-memory sessions, local file uploads, in-process caches assumed durable) as a horizontal-scaling blocker to fix before adding instances, not an acceptable trade-off to discover during an actual instance failure.
Quick reference
- Trigger: one instance behind the load balancer crashes mid-request.
- Symptom: whether the next request continues seamlessly depends entirely on where session state actually lived.
- Root mechanism: health-check detection delay is a separate, expected gap from the session-locality question.
- Recovery: shared session storage (Redis, signed cookies) lets any surviving instance continue the user's flow.
- Prevention: audit for node-local state before scaling out — an instance failure is the wrong time to discover it.
Remember this
An instance crash behind a load balancer is only invisible to the user if session state was never node-local to begin with — verify this with an actual kill-and-retry test, not an assumption.
Key takeaway
Vertical scaling is the simplest relief for a single-box bottleneck. Horizontal scaling is the path to elastic capacity and instance-level HA — after you remove node-local state. Defend each step with load-test numbers and a clear failure domain story.
Practice (20 min): Take one authenticated endpoint. Confirm where the session lives today. If it is in process memory, move it to Redis (or signed cookies), run two local app processes behind a simple LB/proxy, kill one process mid-session, and verify the next request still authenticates on the survivor.
Related Articles
Explore this topic