Basic OPcache and query tuning get you far. Production systems at scale need deeper analysis of the runtime, process model, and data layer. This page covers advanced techniques beyond introductory optimization.

Request Lifecycle and Hot Paths

Every HTTP request in PHP-FPM follows:

  Nginx → FastCGI → PHP-FPM worker → Bootstrap → Autoload → App logic → Response
  

The bootstrap cost (framework kernel, DI container, route compilation) dominates short requests. Measure with:

  // At the very top of public/index.php
define('LARAVEL_START', microtime(true));
// Framework reports bootstrap time in debug toolbar
  

Tools: Blackfire, Tideways, SPX, Laravel Telescope, Symfony Web Profiler.

JIT Compiler Tuning (PHP 8+)

JIT compiles hot bytecode to machine code. Configure in php.ini:

  opcache.enable=1
opcache.jit_buffer_size=128M
opcache.jit=1255
  

JIT mode 1255 = tracing JIT, optimize all functions. Useful for CPU-heavy loops; often marginal for typical CRUD apps dominated by I/O.

Workload JIT benefit
Numerical computation, parsers High
Typical web CRUD + MySQL Low to moderate
Serialization-heavy APIs Moderate

Benchmark your app — JIT increases memory usage and can hurt I/O-bound workloads.

PHP-FPM Pool Sizing

Worker exhaustion causes 502 errors under load. Tune www.conf:

  pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
  

Estimate max_children:

  max_children ≈ (Available RAM for PHP) / (Average request memory)
  

Example: 4 GB for PHP, 80 MB per request → ~50 workers. Monitor with pm.status_path and tools like php-fpm_exporter.

Slow Request Logging

  request_slowlog_timeout = 2s
slowlog = /var/log/php-fpm/slow.log
  

Correlate slow logs with APM traces to find N+1 queries or blocking external calls.

Memory Profiling

  $before = memory_get_usage(true);
$users = User::with('posts.comments')->get(); // eager load
$after = memory_get_usage(true);
error_log('Memory delta: ' . ($after - $before));
  

For deep analysis:

  • Xdebug memory profiling (dev only — significant overhead)
  • meminfo extension — heap snapshots
  • PHPUnit @group memory tests with memory_get_peak_usage()

Generators for Large Datasets

  function readLargeCsv(string $path): Generator {
    $handle = fopen($path, 'r');
    while (($row = fgetcsv($handle)) !== false) {
        yield $row;
    }
    fclose($handle);
}

foreach (readLargeCsv('export.csv') as $row) {
    processRow($row); // constant memory regardless of file size
}
  

Database Layer at Scale

Connection Pooling

PHP-FPM workers each hold DB connections. At 50 workers × 3 services = 150 connections. Use:

  • ProxySQL or PgBouncer (for PostgreSQL)
  • RDS Proxy on AWS
  • Persistent connections cautiously (PDO::ATTR_PERSISTENT) — can exhaust server slots

Read Replicas

  // Laravel: configure read/write connections in config/database.php
'mysql' => [
    'read'  => ['host' => ['replica1', 'replica2']],
    'write' => ['host' => ['primary']],
    'sticky' => true,
],
  

Route read-heavy reporting queries to replicas; writes stay on primary.

Query Plan Analysis

  EXPLAIN ANALYZE SELECT u.id, COUNT(o.id)
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id;
  

Watch for sequential scans, filesort, and temporary tables. Add composite indexes matching WHERE + JOIN + ORDER BY columns.

Multi-Layer Caching Architecture

  Browser → CDN → Reverse proxy (Varnish) → App cache (Redis) → OPcache → Database
  

Cache Stampede Prevention

  function getCachedUsers(Redis $redis, callable $loader): array {
    $key = 'users:active';
    $data = $redis->get($key);
    if ($data !== false) {
        return json_decode($data, true);
    }

    $lock = $redis->set('lock:' . $key, '1', ['NX', 'EX' => 10]);
    if ($lock) {
        $users = $loader();
        $redis->setex($key, 300, json_encode($users));
        $redis->del('lock:' . $key);
        return $users;
    }

    usleep(100_000);
    return getCachedUsers($redis, $loader);
}
  

Use probabilistic early expiration or Symfony’s CacheInterface stampede protection.

Async and Queues

PHP is synchronous per request — offload slow work:

  // Laravel queue job
class SendWelcomeEmail implements ShouldQueue {
    public function __construct(public User $user) {}
    public function handle(): void {
        Mail::to($this->user)->send(new WelcomeMail());
    }
}

SendWelcomeEmail::dispatch($user);
  

Run workers with Supervisor:

  [program:laravel-worker]
command=php /var/www/artisan queue:work redis --sleep=3 --tries=3
numprocs=4
autostart=true
autorestart=true
  

For high-throughput ingestion, consider RoadRunner or FrankenPHP with early response patterns.

Horizontal Scaling

                      Load Balancer
                   /      |      \
              App-1     App-2     App-3
                   \      |      /
                    Redis (sessions/cache)
                         MySQL primary
                         MySQL replica(s)
  

Requirements for stateless PHP nodes:

  • Session storage in Redis, not local files
  • Shared or object storage for uploads (S3)
  • Centralized logging (ELK, Loki)
  • Deploy with zero-downtime (blue/green or rolling)

Observability Stack

Signal Tools
Metrics Prometheus + php-fpm_exporter, Grafana
Traces OpenTelemetry PHP SDK, Jaeger
Logs Monolog → structured JSON → Loki/ELK
Errors Sentry, Bugsnag
  // Structured logging with Monolog
$logger->info('order.created', [
    'order_id' => $order->id,
    'user_id'  => $order->user_id,
    'duration_ms' => $elapsed,
]);
  

Set SLIs: p95 latency, error rate, queue depth. Alert on saturation before users notice.

Production Checklist

  • composer install --no-dev --optimize-autoloader
  • php artisan config:cache / route:cache (Laravel)
  • OPcache validate_timestamps=0 with deploy-time reset
  • Realpath cache tuned for many files
  • Preload critical classes (opcache.preload)
  • Load test with k6 or Locust before launch
  • Document rollback procedure

Performance is a continuous discipline: profile, fix the largest bottleneck, measure again, and automate monitoring so regressions surface immediately.