0

Horizontal scaling vs vertical scaling — more machines vs bigger machine

Beginner5 min read·eng-18-005
interviewperformance

Concept

Horizontal vs vertical scaling — two strategies for handling more traffic or load.

Vertical scaling (scale up): Give the existing server more resources — bigger CPU, more RAM, faster disk. Simple: no application changes required. But limited: there's a maximum server size. Also a single point of failure.

Horizontal scaling (scale out): Add more servers and distribute the load between them. Theoretically unlimited. Requires a load balancer to distribute requests. Stateless applications scale horizontally with no issues. Stateful applications (session in memory) require shared session storage (database or Redis).

Which to use:

  • Vertical: Simple apps, early stage, single database, when you need to scale quickly with minimal architectural change.
  • Horizontal: High availability requirements, traffic spikes, unlimited scale potential, microservices.

PHP and horizontal scaling:

  • PHP-FPM is stateless per request — naturally horizontally scalable.
  • Sessions must be in Redis/database, not file (shared across servers).
  • File uploads must go to shared storage (S3), not local disk.
  • Caches must be in Redis, not in-memory (each server would have different cache).

Database scaling: Usually vertical first (more powerful DB server). Read replicas for horizontal read scaling. Sharding for write scaling.

Auto-scaling: Cloud platforms (AWS, GCP, Azure) can automatically add/remove servers based on CPU or request volume. Laravel Vapor (serverless) handles horizontal scaling automatically.

Code Example

php
<?php
// HORIZONTAL SCALING REQUIREMENTS

// BAD: Session in file (each server has different sessions)
// config/session.php
'driver' => 'file', // ← won't work across multiple servers

// GOOD: Session in Redis (shared across all servers)
'driver' => 'redis',
'connection' => 'default',

// BAD: Cache in file/array (different cache per server)
// config/cache.php
'default' => 'file', // ← each server has different cache state

// GOOD: Shared cache in Redis
'default' => 'redis',

// BAD: Storing uploads on local disk
public function upload(Request $request): void
{
    $request->file('avatar')->store('avatars'); // local disk
    // Server 2 can't access Server 1's local disk!
}

// GOOD: Uploads to shared storage
public function upload(Request $request): void
{
    $request->file('avatar')->store('avatars', 's3'); // shared S3 storage
    // All servers read from the same S3 bucket
}

// Queue workers scale horizontally too
// Instead of one queue:work process, run many workers across many servers
// Each worker picks up the next available job from the shared Redis queue
yaml
# docker-compose.yml — horizontal scaling with replicas
services:
  app:
    image: myapp:latest
    deploy:
      replicas: 3    # 3 instances of the app
    environment:
      CACHE_DRIVER: redis
      SESSION_DRIVER: redis
      FILESYSTEM_DISK: s3

  load_balancer:
    image: nginx
    ports:
      - "80:80"
    depends_on:
      - app
    # Distributes traffic across the 3 app replicas
bash
# AWS: Vertical scaling (resize EC2 instance)
# t3.micro (2 vCPU, 1GB RAM) → c6i.xlarge (4 vCPU, 8GB RAM)
# Requires stop/start of instance — downtime!

# AWS: Horizontal scaling (Auto Scaling Group)
# Sets min=2, max=10 instances
# Adds instances when CPU > 70%, removes when CPU < 30%
# Zero downtime — requests just go to remaining instances