Introduction
OPcache stores precompiled PHP bytecode in shared memory to avoid parsing and compiling scripts on every request. When opcache.memory_consumption is too small for the number of files in the project, the cache fills up. OPcache then begins purging old entries, forcing re-compilation on subsequent requests. This causes sudden performance degradation, increased CPU usage, and higher response times.
Symptoms
- Response times suddenly increase after deployment or traffic spike
- CPU usage spikes as PHP recompiles scripts on every request
- OPcache status shows:
Cached scripts: 3999/4000(near limit) OPcache Warning: Insufficient shared memoryin error log- Performance degrades gradually, then drops sharply
opcache_get_status()showsmemory_wastedincreasing
```php // Check OPcache status $status = opcache_get_status(); echo "Memory: {$status['memory_usage']['used_memory']}/{$status['memory_usage']['memory_consumption']} bytes\n"; echo "Cached scripts: {$status['opcache_statistics']['num_cached_scripts']}\n"; echo "Hits: {$status['opcache_statistics']['hits']}\n"; echo "Misses: {$status['opcache_statistics']['misses']}\n"; echo "Hit rate: {$status['opcache_statistics']['opcache_hit_rate']}%\n";
// Output showing problem: // Memory: 134217728/134217728 bytes (FULL!) // Cached scripts: 3999/4000 // Hit rate: 45.2% (should be > 99%) ```
Common Causes
opcache.memory_consumptiontoo small for project size- Large framework (Laravel, Symfony) with many files
- Deployments without OPcache reset
opcache.max_accelerated_filestoo low- Memory fragmentation from frequent file changes
Step-by-Step Fix
- 1.Calculate required OPcache memory:
- 2.```bash
- 3.# Count PHP files in project
- 4.find /var/www/html -name "*.php" | wc -l
- 5.# Output: 4523 files
# Estimate memory needed (average ~200KB per compiled file) # 4523 files * 200KB = ~900MB (but OPcache deduplicates) # Start with 256MB and monitor ```
- 1.Tune OPcache settings:
- 2.```ini
- 3.# /etc/php/8.2/fpm/conf.d/10-opcache.ini
# Increase shared memory (default: 128MB) opcache.memory_consumption=256
# Increase max files (default: 10000) # Use the next power of 2 above your file count opcache.max_accelerated_files=16000
# Enable file timestamp validation (disable in production) opcache.validate_timestamps=0
# How often to check for changes (seconds, when validate_timestamps=1) opcache.revalidate_freq=60
# Save comments for debugging (disable in production for memory savings) opcache.save_comments=0
# Enable fast shutdown opcache.fast_shutdown=1
# After changing, restart PHP-FPM # sudo systemctl restart php8.2-fpm ```
- 1.Reset OPcache after deployment:
- 2.```bash
- 3.#!/bin/bash
- 4.# deploy.sh - at the end of deployment
# Clear OPcache to load new code php -r 'if (function_exists("opcache_reset")) { opcache_reset(); echo "OPcache cleared\n"; }'
# Or via PHP-FPM (graceful reload) sudo systemctl reload php8.2-fpm
# Verify php -r 'print_r(opcache_get_status()["memory_usage"]);' ```
- 1.Monitor OPcache in production:
- 2.```php
- 3.// opcache-monitor.php (restrict access!)
- 4.if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1') {
- 5.http_response_code(403);
- 6.exit;
- 7.}
$status = opcache_get_status(); $mem = $status['memory_usage']; $stats = $status['opcache_statistics'];
echo "Memory: " . round($mem['used_memory'] / 1024 / 1024, 1) . "MB / " . round($mem['memory_consumption'] / 1024 / 1024, 1) . "MB\n"; echo "Hit rate: " . round($stats['opcache_hit_rate'], 2) . "%\n"; echo "Cached scripts: {$stats['num_cached_scripts']}\n"; echo "Free memory: " . round($mem['free_memory'] / 1024 / 1024, 1) . "MB\n";
// Alert if hit rate drops below 99% if ($stats['opcache_hit_rate'] < 99) { error_log("OPcache hit rate low: {$stats['opcache_hit_rate']}%"); } ```
Prevention
- Set
opcache.memory_consumption=256(or higher) for large applications - Set
opcache.validate_timestamps=0in production for best performance - Reset OPcache after every deployment
- Monitor hit rate - should be > 99% in production
- Use
opcache.file_cacheas fallback when shared memory is full - In Docker, mount OPcache as tmpfs for better performance:
- ```yaml
- # docker-compose.yml
- volumes:
- - type: tmpfs
- target: /tmp/opcache
- tmpfs:
- size: 256000000 # 256MB
`