0

OPcache internals — shared memory, file invalidation

Expert5 min read·eng-09-004
interviewperformance

Concept

OPcache (Opcode Cache) stores the compiled opcode of PHP scripts in shared memory, eliminating the parse → compile step on subsequent requests. Without OPcache: every request parses every PHP file from disk. With OPcache: compiled opcodes are shared across all PHP-FPM worker processes.

The compilation pipeline (from eng-09-001):

  1. Source PHP → Lexer → Tokens
  2. Tokens → Parser → AST
  3. AST → Compiler → Opcodes (zend_op_array)
  4. OPcache stores step 3's output in shared memory.
  5. Next request: skip steps 1-3, execute opcodes directly.

Shared memory segment: OPcache allocates a block of memory at PHP-FPM startup (controlled by opcache.memory_consumption, default: 128MB). All workers read from this block — no per-process copy.

Script identification: Each script is keyed by its real path. OPcache stores the compiled opcodes, the file's mtime (modification time), and a checksum.

Invalidation: OPcache checks opcache.validate_timestamps (default: on) and opcache.revalidate_freq (default: 2 seconds). If the file's mtime changed, the cached entry is discarded and the file is recompiled. In production: disable validate_timestamps for maximum performance — files never change in production anyway. Invalidate manually after deployments.

Production settings:

ini
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0   ; disable in production
opcache.revalidate_freq=0
opcache.save_comments=0         ; strip phpdoc in production

Deployment invalidation: opcache_reset() clears all cached scripts (requires a PHP request — can't call from CLI directly). Laravel Octane restarts the server. Most deployments restart PHP-FPM: systemctl reload php8.3-fpm.

OPcache + preloading (PHP 7.4+): opcache.preload — load a set of files into shared memory at PHP-FPM startup. Files are pre-compiled AND the opcode is already in memory before any request arrives. Laravel's php artisan optimize generates a preload file.

Code Example

ini
; /etc/php/8.3/fpm/conf.d/10-opcache.ini

; Development settings
opcache.enable=1
opcache.memory_consumption=128     ; MB
opcache.max_accelerated_files=10000
opcache.validate_timestamps=1      ; check for file changes (dev)
opcache.revalidate_freq=0          ; check on EVERY request (dev only)
opcache.enable_cli=0               ; usually off for CLI

; Production settings
; opcache.validate_timestamps=0    ; NEVER check timestamps → no disk I/O
; opcache.revalidate_freq=0        ; irrelevant when validate_timestamps=0
; opcache.memory_consumption=256
; opcache.max_accelerated_files=30000
; opcache.save_comments=0          ; strip DocBlocks (saves memory)

; Preloading (PHP 7.4+)
; opcache.preload=/var/www/app/bootstrap/cache/preload.php
; opcache.preload_user=www-data

; Introspection — what's cached?
php
<?php
// Check OPcache status
$status = opcache_get_status();
echo $status['memory_usage']['used_memory'];      // bytes used
echo $status['memory_usage']['free_memory'];      // bytes free
echo $status['opcache_statistics']['num_cached_scripts']; // cached files
echo $status['opcache_statistics']['hits'];        // cache hits
echo $status['opcache_statistics']['misses'];      // cache misses

// OPcache hit ratio — should be > 99% in production
$stats   = $status['opcache_statistics'];
$hitRate = $stats['hits'] / ($stats['hits'] + $stats['misses']) * 100;
echo number_format($hitRate, 2) . '% hit rate';

// Invalidate a single file (useful after deployment)
opcache_invalidate('/var/www/app/public/index.php', force: true);

// Clear all cached scripts (called from a web request, not CLI)
opcache_reset();

// Check if a specific file is cached
opcache_is_script_cached('/var/www/app/vendor/laravel/framework/src/Illuminate/Foundation/Application.php');

// Laravel — artisan optimize pre-compiles config, routes, views, and generates preload.php
// php artisan optimize          → generates bootstrap/cache/*.php
// php artisan optimize:clear    → removes cached files
// After deployment: reload php-fpm to pick up new preload.php
// sudo systemctl reload php8.3-fpm