Here's what's happening in our production environment:
Server specs:
- Linode VPS: 8 cores, 8GB RAM, 2.6GHz
- Nginx + PHP-FPM stack
- Custom PHP framework (unknown, potentially inefficient)
- 6 PHP-FPM processes consuming 70-100% CPU each
- Current pool config:
pm = dynamic
pm.max_children = 10
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
First, let's implement some quick fixes while we investigate the root cause:
# Updated www.conf with CPU limiting
pm = dynamic
pm.max_children = 8 # Don't max out all cores
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 4
pm.process_idle_timeout = 10s
pm.max_requests = 500 # Prevent memory leaks
We've also implemented Memcached for session storage:
session.save_handler = memcached
session.save_path = "127.0.0.1:11211"
To identify problematic scripts:
# Install and run PHP-FPM process inspector
sudo apt-get install php-xhprof
sudo service php-fpm restart
# Sample profiling code at application entry point
if (extension_loaded('xhprof')) {
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
register_shutdown_function(function() {
$data = xhprof_disable();
file_put_contents('/tmp/xhprof.log', json_encode($data));
});
}
Implementing process prioritization with cgroups:
# Create cgroup for PHP-FPM
sudo cgcreate -g cpu:/phpfpm
echo 50000 > /sys/fs/cgroup/cpu/phpfpm/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/phpfpm/cpu.cfs_period_us
# Apply to PHP-FPM
sudo cgclassify -g cpu:phpfpm $(pgrep php-fpm)
For custom frameworks, consider these patterns:
// Replace heavy reflection with direct calls
// Before:
$method = new ReflectionMethod($object, 'methodName');
$method->invoke($object, $args);
// After:
$object->methodName($args);
// Optimize session usage
session_write_close(); // Release lock early
// Continue processing without session blocking
Prevent PHP-FPM from being overloaded:
location ~ \.php$ {
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_connect_timeout 60;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_busy_buffers_size 64k;
fastcgi_temp_file_write_size 64k;
fastcgi_max_temp_file_size 0;
fastcgi_intercept_errors off;
}
Set up real-time monitoring:
# Install and configure php-fpm-exporter
wget https://github.com/hipages/php-fpm_exporter/releases/download/v1.0.0/php-fpm_exporter_1.0.0_linux_amd64
./php-fpm_exporter --phpfpm.scrape-uri tcp://127.0.0.1:9000/status
# Sample Prometheus alert rule
- alert: HighPHPCPU
expr: rate(phpfpm_process_cpu_seconds_total[1m]) > 0.7
for: 5m
labels:
severity: critical
annotations:
summary: "High CPU usage in PHP-FPM process"
description: "PHP-FPM process {{ $labels.pid }} is using {{ $value }} CPU seconds per second"
When dealing with custom PHP frameworks on Nginx/PHP-FPM setups, we often encounter situations where individual PHP-FPM processes consume 70-100% CPU. This creates resource contention and prevents proper scaling, even on relatively powerful VPS instances (8-core, 8GB RAM in this case).
First, let's examine the current pool configuration:
pm = dynamic
pm.max_children = 10
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
While these settings appear reasonable for the available memory (only 472MB used out of 8GB), the CPU becomes the bottleneck. Here's how to identify the culprits:
Real-time Process Monitoring
# Install required tools
sudo apt-get install htop strace
# Monitor PHP-FPM processes
sudo strace -p $(pgrep -f "php-fpm: pool www") -c
1. Process Isolation
Separate frontend and backend operations into different PHP-FPM pools:
; /etc/php/7.4/fpm/pool.d/frontend.conf
[frontend]
listen = /run/php/php7.4-fpm-frontend.sock
pm = dynamic
pm.max_children = 8
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 4
; /etc/php/7.4/fpm/pool.d/backend.conf
[backend]
listen = /run/php/php7.4-fpm-backend.sock
pm = dynamic
pm.max_children = 4
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 2
2. CPU Throttling with cgroups
Instead of cpulimit, use cgroups for more precise control:
# Create cgroup
sudo cgcreate -g cpu:/phpfpm
# Set CPU limit (50% in this example)
echo 50000 > /sys/fs/cgroup/cpu/phpfpm/cpu.cfs_quota_us
# Apply to PHP-FPM
sudo cgclassify -g cpu:phpfpm $(pgrep php-fpm)
3. Session Handling Optimization
Implementing Memcached for sessions was a good move. Here's how to configure it properly:
; php.ini configuration
session.save_handler = memcached
session.save_path = "SERVER_IP:11211"
; Additional protection
memcached.sess_lock_wait = 150000
memcached.sess_prefix = "sess_"
OPcache Configuration
Even with custom frameworks, OPcache can dramatically reduce CPU usage:
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
Query Optimization
Implement basic query logging to identify bottlenecks:
# MySQL slow query log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
For CPU-intensive operations, implement a queue system:
// Example using Redis queue
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// Instead of processing immediately
$redis->rPush('process_queue', json_encode($data));
// Worker script (run via supervisor)
while ($job = $redis->blPop('process_queue', 0)) {
processJob(json_decode($job[1], true));
}
Implement proper monitoring to validate changes:
# Install Prometheus exporter
wget https://github.com/hipages/php-fpm_exporter/releases/download/v2.0.0/php-fpm_exporter_2.0.0_linux_amd64
# Configure to monitor both pools
./php-fpm_exporter --phpfpm.scrape-uri=unix:///run/php/php7.4-fpm-frontend.sock;/status \
--phpfpm.scrape-uri=unix:///run/php/php7.4-fpm-backend.sock;/status
The key is gradual implementation of these measures while monitoring performance. Start with pool separation and OPcache, then move to more advanced solutions like queueing if needed.