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 memory in error log
  • Performance degrades gradually, then drops sharply
  • opcache_get_status() shows memory_wasted increasing

```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_consumption too small for project size
  • Large framework (Laravel, Symfony) with many files
  • Deployments without OPcache reset
  • opcache.max_accelerated_files too low
  • Memory fragmentation from frequent file changes

Step-by-Step Fix

  1. 1.Calculate required OPcache memory:
  2. 2.```bash
  3. 3.# Count PHP files in project
  4. 4.find /var/www/html -name "*.php" | wc -l
  5. 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. 1.Tune OPcache settings:
  2. 2.```ini
  3. 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. 1.Reset OPcache after deployment:
  2. 2.```bash
  3. 3.#!/bin/bash
  4. 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. 1.Monitor OPcache in production:
  2. 2.```php
  3. 3.// opcache-monitor.php (restrict access!)
  4. 4.if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1') {
  5. 5.http_response_code(403);
  6. 6.exit;
  7. 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=0 in production for best performance
  • Reset OPcache after every deployment
  • Monitor hit rate - should be > 99% in production
  • Use opcache.file_cache as 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
  • `