//------------------------------------------------------------------- //-------------------------------------------------------------------
Why Vertical Scaling Fails

Vertical Scaling Fails in Production

Vertical scaling, which is also known as scaling up, involves increasing the resources of a single server, including CPU, RAM, and storage, to handle growing workloads. Even though it is a simple way and can boot performance quickly, vertical scaling will eventually hit limits in real production systems. This guide explains why vertical scaling fails, how to detect the warning signs early, and what architectural changes to make next.

If you’re running on dedicated servers or VPS infrastructure, PerLod Hosting scaling solutions can help you transition smoothly.

Why Vertical Scaling Fails?

Vertical scaling usually works at the beginning, but in production, this approach hits hard limits and creates new risks, which is why vertical scaling fails over time. Whether you’re on a VPS or dedicated server, understanding these limits helps you scale smarter.

Here are the most common reasons that vertical scaling fails:

1. Hardware and Physical Limitations: Every server has a maximum limit. Cloud servers can only get so big, and physical machines can only take a certain number of CPUs, RAM, and disks. Once you hit that limit, you can’t scale up anymore.

Key limitations include:

  • CPU limit: Even the biggest cloud servers only offer a maximum number of vCPUs, so you can’t add more CPU.
  • Memory limit: Physical machines can only hold so much RAM because the motherboard has a limited number of slots.
  • Storage limit: Disks, even NVMe, and shared storage can only deliver a certain amount of IOPS and bandwidth, and you can’t push past that.
  • Network limit: Network cards have a top speed, so at some point, the network becomes the bottleneck instead of CPU or RAM.

2. Single Point of Failure (SPOF): Putting everything on one server is risky. If that single machine goes down, because of hardware problems, an OS crash, or the app crashing, your whole service goes offline.

  • Hardware failure of a CPU, memory module, or motherboard brings down all services.
  • Software bugs or kernel issues affect the entire system simultaneously.
  • Maintenance becomes harder because you often have to take the whole server offline to upgrade or fix it, which means full downtime. That downtime can affect SLA commitments.

3. Performance Bottlenecks and Diminishing Returns: Vertical scaling often creates new bottlenecks while solving old ones. Adding CPU cores won’t help if disk I/O or network bandwidth becomes the limiting factor.

Common bottleneck scenarios include:

  • Faster CPU, same slow disk or network: The CPU ends up waiting because data can’t arrive fast enough.
  • More RAM, same system speed: Memory is bigger, but data still moves slowly inside the machine.
  • Multi-CPU overhead: The server wastes time keeping all CPUs in sync.

4. High Cost and Economic Inefficiency:

Vertical scaling gets expensive fast. As you move to bigger premium servers, the price usually grows much faster than the performance, so you pay a lot more for a smaller gain.

5. Mandatory Downtime for Upgrades: Upgrading a single server usually means downtime. For physical hardware, you often must shut the machine down to add or replace CPU or RAM, and in the cloud, scaling up requires stopping and restarting the instance, so users see an interruption.

Typical downtime ranges include:

  • Physical CPU and RAM upgrade: Around 30 minutes to 2 hours.
  • Cloud instance resize: 5 to 15 minutes of unavailability.
  • Storage migration: hours to days, depending on how much data you move.

6. Vendor Lock-in and Migration Complexity: When you keep scaling up, you may end up relying on special and expensive hardware or a specific cloud big instance type. That can make it harder to switch providers or move your workload later, because the setup and performance tuning don’t transfer cleanly.

Detect Vertical Scaling Failures

At this point, you can detect warning signs that vertical scaling fails, like rising CPU load, memory pressure, slow disk I/O, and longer response times, before the server hits its hard limit and the whole service becomes unstable.

1. CPU Monitoring Commands:

You can use the top command for real-time process monitoring.

# Basic usage
top

# Show CPU cores individually
top -1

# Filter by user
top -u apache

# Batch mode for scripting
top -b -n 1 | head -20

Key signs of CPU limits include:

  • CPU usage stays high: User and system CPU are around 80% or more most of the time.
  • Load is too high: Load average is higher than the number of CPU cores for long periods.
  • High I/O wait: %wa is high, which usually means the CPU is waiting on disk or network, not doing real work.

You can use the htop command for enhanced visual monitoring:

# Install on Ubuntu/Debian
sudo apt install htop

# Install on RHEL/CentOS
sudo dnf install htop

# Run with tree view
htop -t

Also, you can use the vmstat command for system-wide resource statistics:

# Update every 2 seconds
vmstat 2

# 10 iterations with 1-second delay
vmstat 1 10

# Show disk statistics
vmstat -d 2

Essential signs of limitations include:

  • r (run queue) > CPU cores
  • us + sy > 80% for a while
  • wa > 15%
  • id (idle) < 10%

2. Memory Monitoring Commands:

You can use the free command for memory utilization:

# Human-readable output
free -h

# Show in megabytes
free -m

# Continuous monitoring
watch -n 2 free -h

Warning signs that vertical scaling fails include:

  • Available memory < 10% of total
  • Swap usage > 0
  • Buffer and cache are not reclaiming under load

You can use the vmstat command with the -s flag for memory indicators:

vmstat -s

In the output, watch for:

  • The swap used is increasing over time.
  • The active memory is approaching the total memory.

3. Disk I/O Monitoring:

Use the iostat command to display I/O device statistics:

# Install sysstat package
sudo apt install sysstat  # Ubuntu/Debian
sudo dnf install sysstat    # RHEL/CentOS

# Show extended statistics every 2 seconds
iostat -x 2

# Display in megabytes
iostat -xm 2

Warning signs that vertical scaling fails include:

  • %util > 80% for extended periods
  • await > 20ms
  • avgqu-sz > 1

Also, you can use the df command to monitor disk space usage:

# Human-readable
df -h

# Show inodes
df -i

# Monitor specific filesystem
watch -n 5 'df -h /var/lib/mysql'

4. Network Monitoring Commands:

To monitor network connections, you can use the netstat command:

# Show listening ports
netstat -tuln

# Show connection states
netstat -an | grep ESTABLISHED | wc -l

# Monitor continuously
watch -n 2 'netstat -an | grep :80 | wc -l'

Use the ss command to display modern socket statistics:

# Show TCP connections
ss -t -a

# Show memory usage per socket
ss -m

For real-time bandwidth monitoring, you can use the iftop command:

# Install
sudo apt install iftop  # Ubuntu/Debian
sudo dnf install iftop    # RHEL/CentOS

# Monitor specific interface
sudo iftop -i eth0

5. Application-Level Detection:

Thread Pool Monitoring: The case study showed how vertical scaling broke thread pool behavior. You can use the commands below to monitor thread utilization:

# Count Java threads
jcmd <PID> Thread.print | grep -c "Thread"

# Monitor thread pool sizes
jstat -gcutil <PID> 1000

# Show native threads
ps -eLf | grep java | wc -l

Garbage Collection Pressure: When you give a Java service more memory, it can still run into GC problems, because the JVM may spend more time cleaning up a larger heap. To monitor and enable GC logs, use the commands below:

# Monitor GC activity
jstat -gc <PID> 1000

# Enable GC logging
java -Xlog:gc*:file=/var/log/gc.log:time,level,tags

Warning signs include:

  • FGC (Full GC) frequency is increasing.
  • FGCT (Full GC time) exceeding 5% of total time.

Connection Pool Exhaustion:

# Monitor MySQL connections
mysql -e "SHOW STATUS LIKE 'Threads_connected';"

# Monitor PostgreSQL
psql -c "SELECT count(*) FROM pg_stat_activity;"

Automated Detection Script: You can create a comprehensive monitoring script with:

#!/bin/bash
# vertical_scaling_limits.sh

THRESHOLD_CPU=80
THRESHOLD_MEM=90
THRESHOLD_IO=80
THRESHOLD_LOAD=0.8

echo "=== Vertical Scaling Limit Detection ==="
echo "Timestamp: $(date)"

# CPU check
CPU_IDLE=$(vmstat 1 2 | tail -1 | awk '{print $15}')
CPU_USAGE=$((100 - CPU_IDLE))
echo "CPU Usage: ${CPU_USAGE}%"
if [ $CPU_USAGE -gt $THRESHOLD_CPU ]; then
    echo "WARNING: CPU usage exceeds threshold"
fi

# Memory check
MEM_INFO=$(free | grep Mem)
MEM_TOTAL=$(echo $MEM_INFO | awk '{print $2}')
MEM_USED=$(echo $MEM_INFO | awk '{print $3}')
MEM_PERCENT=$((MEM_USED * 100 / MEM_TOTAL))
echo "Memory Usage: ${MEM_PERCENT}%"
if [ $MEM_PERCENT -gt $THRESHOLD_MEM ]; then
    echo "WARNING: Memory usage exceeds threshold"
fi

# Load average check
CORES=$(nproc)
LOAD1=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//')
LOAD_THRESHOLD=$(echo "$CORES * $THRESHOLD_LOAD" | bc)
echo "Load Average (1min): $LOAD1 (Threshold: $LOAD_THRESHOLD)"
if (( $(echo "$LOAD1 > $LOAD_THRESHOLD" | bc -l) )); then
    echo "WARNING: Load average exceeds threshold"
fi

# Disk I/O check
if command -v iostat &> /dev/null; then
    IO_UTIL=$(iostat -x 1 2 | awk '/^sda/ {print $NF}' | tail -1)
    echo "Disk I/O Utilization: ${IO_UTIL}%"
    if (( $(echo "$IO_UTIL > $THRESHOLD_IO" | bc -l) )); then
        echo "WARNING: Disk I/O exceeds threshold"
    fi
fi

echo "======================================="

Kubernetes-Specific Detection: For containerized environments, you can use Vertical Pod Autoscaler (VPA) recommendations:

# Check VPA recommendations
kubectl describe vpa <vpa-name>

# View current vs recommended resources
kubectl get vpa <vpa-name> -o yaml

# Monitor pod resource usage
kubectl top pods --sort-by=cpu

# Watch for OOMKilled events
kubectl get events --field-selector reason=OOMKilled

VPA status signs of limits:

status:
  recommendation:
    containerRecommendations:
    - containerName: app
      lowerBound:
        cpu: 500m
        memory: 1Gi
      target:
        cpu: 2
        memory: 4Gi
      upperBound:
        cpu: 4
        memory: 8Gi

When the target approaches your instance limits, vertical scaling is exhausted.

Next Steps After Vertical Scaling Hits Limits

Once scaling up hits hardware limits, becomes too risky, or stops improving performance, the next step is to change the architecture, not buy a bigger server. These are the common paths teams can take after vertical scaling fails:

Use Horizontal Scaling Architecture

You can distribute the workload across multiple servers instead of upgrading one. The implementation steps include:

Load Balancer Setup:

# Install HAProxy on Ubuntu
sudo apt install haproxy

# Basic configuration /etc/haproxy/haproxy.cfg
cat > /etc/haproxy/haproxy.cfg << 'EOF'
global
    daemon
    maxconn 4096

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend web_frontend
    bind *:80
    default_backend web_backend

backend web_backend
    balance roundrobin
    server web1 10.0.1.10:80 check
    server web2 10.0.1.11:80 check
    server web3 10.0.1.12:80 check
EOF

# Restart HAProxy
sudo systemctl restart haproxy

Modify applications to avoid local state:

# Bad: Local file storage
def save_upload(file):
    with open(f"/tmp/{file.name}", "wb") as f:
        f.write(file.data)

# Good: Object storage
def save_upload(file):
    s3_client.upload_fileobj(
        file.data, 
        "my-bucket", 
        f"uploads/{file.name}"
    )

Database Read Replicas:

-- MySQL read replica configuration
CHANGE MASTER TO
MASTER_HOST='master.example.com',
MASTER_USER='replica',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=107;

START SLAVE;

Session Management: Use external session stores:

# Redis for session storage
sudo apt install redis-server

# Configure PHP to use Redis for sessions
echo "session.save_handler = redis" >> /etc/php/8.1/apache2/php.ini
echo "session.save_path = tcp://127.0.0.1:6379" >> /etc/php/8.1/apache2/php.ini

You can learn more about horizontal scaling by checking this guide on Horizontal Scaling Strategies.

Use Hybrid Scaling Strategy

If vertical scaling fails, you can use vertical and horizontal scaling for optimal efficiency.

Vertical Optimization:

# Right-size existing instances
# Use VPA recommendations to optimize resource requests
kubectl apply -f - << 'EOF'
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: app-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: app
  updatePolicy:
    updateMode: "Auto"
EOF

Horizontal Expansion:

# Horizontal Pod Autoscaler
kubectl apply -f - << 'EOF'
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
EOF

Here is a suggestion for teams that usually want to move from vertical scaling to horizontal scaling as the user base grows:

User BaseScaling ApproachTimeline
< 100K usersVertical only1-3 months
100K-1M usersHybrid (Vertical → Horizontal)3-6 months
> 1M usersHorizontal mandatory6-12 months

To learn more about Hybrid architecture, check this guide on building Hybrid hosting architecture.

Use Microservices Decomposition

Microservices decomposition means turning one big app into smaller services that can run and scale on their own. This helps after vertical scaling fails, because you can scale only the busy parts instead of upgrading one huge server.

Decomposition Strategy:

# Example: Extract user service from monolith
# 1. Create new service repository
mkdir user-service && cd user-service

# 2. Initialize with

FAQs

How do I know if my server is about to fail from scaling limits?

Watch for sustained high CPU, memory pressure, slow disk I/O, or increasing error rates. These are the main warning signs that vertical scaling fails.

Can I switch from vertical to horizontal scaling without rewriting my app?

If your app stores data locally or keeps session state in memory, you’ll need to fix that. But with a load balancer and a shared database, you can often scale horizontally with minimal code changes.

Do microservices always solve vertical scaling issues?

No. Microservices add complexity. Use them only if different parts of your app have very different load patterns. For many small apps, horizontal scaling on a single codebase is simpler and enough.

Conclusion

Vertical scaling is quick and simple, so teams start with it, but it will eventually hit limits, including hardware limits, higher costs, downtime risk, and hidden design issues. If you monitor CPU, memory, disk I/O, and app behavior early, you can plan before things break.

When vertical scaling fails, the fix isn’t a bigger server; it’s changing the architecture. Most teams move to horizontal scaling, a hybrid method, or microservices based on growth.

We hope you enjoy this guide on vertical scaling fails. Subscribe to our X and Facebook channels to get the latest articles.

For further reading:

Horizontal vs Vertical Scaling in Dedicated Servers.

Post Your Comment

PerLod delivers high-performance hosting with real-time support and unmatched reliability.

Contact us

Payment methods

payment gateway
Perlod Logo
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.