Optimizing Apache Performance: Solving High CPU Usage with Low Memory Consumption in LAMP Stack


4 views

The top output shows Apache processes consuming 90-100% CPU while memory usage remains relatively low (only 1.9% for MySQL and under 0.5% for most Apache processes). This indicates we're dealing with a CPU-bound workload rather than memory constraints.

First, let's optimize the MPM (Multi-Processing Module) settings. Since you're using prefork (visible in your config), we'll focus there:



StartServers       8
MinSpareServers    5
MaxSpareServers   20
ServerLimit       128    # Reduced from 256
MaxClients        128    # Reduced from 256
MaxRequestsPerChild 1000 # Reduced from 4000

The key changes:

  • Reduced ServerLimit and MaxClients to prevent overloading
  • Lowered MaxRequestsPerChild to allow more frequent process recycling

Since this is a LAMP stack, PHP is likely the real CPU hog. Add these to your php.ini:


opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60

Enable mod_deflate to reduce CPU load from serving uncompressed content:



AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript
DeflateCompressionLevel 6

Your current KeepAlive settings may be keeping connections open too long:


KeepAlive On
MaxKeepAliveRequests 50   # Reduced from 100
KeepAliveTimeout 3        # Reduced from 5

Implement these monitoring commands to verify the changes:


# View real-time Apache connections
watch -n 1 "echo -n 'Apache Processes: '; ps -C httpd --no-headers | wc -l"

# CPU usage by Apache
top -b -n 1 | grep httpd | awk '{print $9}' | sort -n | tail -n 20

# Memory usage
free -m && ps -eo pmem,pcpu,vsize,pid,cmd | grep httpd | sort -k 1 -nr | head -10

For CPU-bound workloads, consider switching to event MPM if possible:



StartServers            2
ServerLimit            64
ThreadLimit            64
ThreadsPerChild        32
MaxRequestWorkers     2048
MaxConnectionsPerChild 1000

Remember to install the event module and disable prefork first:


yum remove mod_php
yum install mod_php72w php72w-opcache

From your server metrics, we can observe several critical patterns:

  • Extremely high CPU utilization (90-100%)
  • Apache processes dominating CPU usage
  • Low memory consumption relative to available resources
  • MySQL showing moderate CPU usage spikes
# Sample sar output showing CPU usage pattern
sar -u 1 5
Linux 3.10.0-1160.45.1.el7.x86_64 (web01)     06/15/2023     _x86_64_    (8 CPU)

12:00:01 AM     CPU     %user     %nice   %system   %iowait    %steal     %idle
12:00:02 AM     all     93.38      0.00      6.62      0.00      0.00      0.00
12:00:03 AM     all     94.12      0.00      5.88      0.00      0.00      0.00

Based on your current prefork configuration, here are recommended changes:

<IfModule prefork.c>
StartServers       16
MinSpareServers    10
MaxSpareServers    30
ServerLimit        200
MaxClients         200
MaxRequestsPerChild  1000
</IfModule>

Key modifications and reasoning:

  • Reduced MaxRequestsPerChild to 1000 to prevent memory leaks from accumulating
  • Adjusted ServerLimit/MaxClients to better match your CPU core count
  • Increased spare server counts to handle traffic spikes more efficiently

For CPU-bound scenarios, worker MPM often performs better:

<IfModule worker.c>
StartServers         4
MaxClients         400
MinSpareThreads     50
MaxSpareThreads    150 
ThreadsPerChild     50
MaxRequestsPerChild  10000
</IfModule>

To enable worker MPM:

# Stop Apache
service httpd stop

# Switch to worker MPM
yum remove httpd-tools
yum install httpd httpd-tools --enablerepo=centosplus

# Verify MPM
httpd -V | grep -i mpm

Since this is a LAMP stack, PHP configuration significantly impacts CPU usage:

; Recommended php.ini modifications
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1

realpath_cache_size=256k
realpath_cache_ttl=3600

; For CPU-bound systems
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20

Even though MySQL isn't the primary bottleneck, optimizing it will help:

# my.cnf optimizations
[mysqld]
innodb_buffer_pool_size = 2G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
query_cache_type = 0
table_open_cache = 4000
thread_cache_size = 50
# Network stack tuning
echo 'net.ipv4.tcp_tw_reuse = 1' >> /etc/sysctl.conf
echo 'net.ipv4.tcp_fin_timeout = 15' >> /etc/sysctl.conf
echo 'net.core.somaxconn = 65535' >> /etc/sysctl.conf
sysctl -p

# File descriptor limits
echo 'www-data soft nofile 65535' >> /etc/security/limits.conf
echo 'www-data hard nofile 65535' >> /etc/security/limits.conf

Implement these monitoring commands to verify improvements:

# Apache status monitoring
apachectl fullstatus

# Real-time process monitoring
watch -n 1 "ps -eo pid,user,pcpu,pmem,cmd --sort=-pcpu | head -20"

# Detailed CPU profiling
perf top -p $(pgrep -d, httpd)