Navigation

Laravel

Laravel Octane: 15 Advanced Configuration Techniques for Maximum Performance

Master Laravel Octane configuration tuning with 15 advanced techniques. Boost Laravel performance by 20x with Swoole/RoadRunner optimization strategies.

Table Of Contents

Introduction

Laravel Octane revolutionizes PHP application performance by serving your Laravel app using high-performance application servers like Swoole and RoadRunner. While the basic installation provides significant speed improvements, true performance mastery comes from strategic configuration tuning. In this comprehensive guide, you'll discover advanced Octane configuration techniques that squeeze every ounce of performance from your Laravel applications. Whether you're handling thousands of requests per second or optimizing for resource-constrained environments, these battle-tested configuration strategies will transform your application's performance profile. We'll dive deep into memory management, worker optimization, and advanced tuning parameters that most developers never explore.

Understanding Laravel Octane's Architecture

Before diving into configuration, it's crucial to understand how Octane fundamentally changes Laravel's request lifecycle:

  • Traditional PHP-FPM: Loads the entire framework for each request
  • Octane (Swoole/RoadRunner): Framework loads once, then handles multiple requests in memory

This architectural shift introduces new optimization vectors but also new challenges:

  • Memory management becomes critical (no automatic cleanup between requests)
  • Configuration must account for long-running processes
  • Traditional debugging approaches need adaptation

Key Performance Metrics to Monitor:

  • Memory usage per worker
  • Requests per second (RPS)
  • Error rates under load
  • CPU utilization patterns
  • Cold vs warm request performance

Technique 1: Worker Count Optimization

The most impactful configuration parameter is worker count, but it's not as simple as "more workers = better performance."

// config/octane.php
'options' => [
    'worker_num' => max(4, swoole_cpu_num() * 2),
],

Strategic Worker Allocation:

  • For CPU-bound applications: swoole_cpu_num() + 1
  • For I/O-bound applications: swoole_cpu_num() * 2 to swoole_cpu_num() * 4
  • Always reserve 1 core for system processes
  • Monitor with top -H -p $(pgrep -f "swoole")

Real-World Example: On a 16-core server handling mostly database requests:

'worker_num' => 28, // 16 * 1.75

Technique 2: Memory Management and Leak Prevention

Memory leaks are the silent killer of long-running Octane applications.

// config/octane.php
'options' => [
    'max_request' => 5000,
    'max_wait_time' => 60,
],

Advanced Memory Management:

  • Set max_request based on memory growth patterns (start with 1,000-5,000)
  • Implement custom memory monitoring:
// bootstrap/app.php
register_shutdown_function(function () {
    if (memory_get_usage() > 100 * 1024 * 1024) {
        \Log::warning('High memory usage: '.memory_get_usage());
    }
});
  • Use octane:reload strategically during low-traffic periods
  • Profile memory with php artisan octane:profile-memory

Technique 3: Connection Pooling Configuration

Database and Redis connections require special handling in Octane's persistent environment.

// config/database.php
'mysql' => [
    'driver' => 'mysql',
    'url' => env('DATABASE_URL'),
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '3306'),
    'database' => env('DB_DATABASE', 'forge'),
    'username' => env('DB_USERNAME', 'forge'),
    'password' => env('DB_PASSWORD', ''),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
    'prefix_indexes' => true,
    'strict' => true,
    'engine' => null,
    'options' => extension_loaded('pdo_mysql') ? array_filter([
        PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
        PDO::ATTR_PERSISTENT => true, // Critical for Octane
    ]) : [],
],

Connection Pooling Best Practices:

  • Enable persistent connections (PDO::ATTR_PERSISTENT => true)
  • Configure proper wait_timeout in MySQL (28800 seconds)
  • Implement custom connection reaper:
// app/Providers/AppServiceProvider.php
public function boot()
{
    if (app()->isProduction() && app()->runningInOctane()) {
        DB::reconnect();
        
        Octane::tick('reconnect-database', function () {
            DB::reconnect();
        })->every(300); // Every 5 minutes
    }
}

Technique 4: Optimizing for Different Server Types

Swoole and RoadRunner require different optimization strategies.

Swoole-Specific Tuning:

'options' => [
    'package_max_length' => 20 * 1024 * 1024, // 20MB
    'buffer_output_size' => 32 * 1024 * 1024,  // 32MB
    'socket_buffer_size' => 128 * 1024 * 1024, // 128MB
    'enable_reuse_port' => true,
    'reload_async' => true,
    'max_wait_time' => 60,
],

RoadRunner-Specific Tuning (in .rr.yaml):

http:
  address: 0.0.0.0:80
  max_request_size: 20 # MB
  uploads:
    forbid: [".php", ".exe", ".bat"]
  workers:
    command: "php octane:roadrunner"
    relay: "unix://var/run/roadrunner.sock"
    pool:
      num_workers: 28
      max_jobs: 5000
      allocate_timeout: 60s
      destroy_timeout: 60s

Technique 5: Advanced Garbage Collection Tuning

PHP's garbage collector behaves differently in long-running processes.

// config/octane.php
'init' => [
    function () {
        gc_enable();
        ini_set('zend.enable_gc', 1);
        gc_mem_caches(10); // Increase from default 2
    },
],

Garbage Collection Strategies:

  • Force GC collection every 100 requests:
Octane::tick('gc-collect', function () {
    gc_collect_cycles();
})->every(100);
  • Monitor memory before/after GC:
Log::info('Memory before GC: '.memory_get_usage());
gc_collect_cycles();
Log::info('Memory after GC: '.memory_get_usage());
  • Adjust gc_divisor and gc_threshold for your workload

Technique 6: Optimizing for Specific Workloads

Different application types require different tuning approaches.

API-First Applications:

'options' => [
    'worker_num' => swoole_cpu_num() * 3,
    'max_request' => 3000,
    'package_max_length' => 10 * 1024 * 1024,
],

Web Applications with Heavy Views:

'options' => [
    'worker_num' => swoole_cpu_num() * 2,
    'max_request' => 1500,
    'buffer_output_size' => 64 * 1024 * 1024,
],

Real-Time Applications (WebSockets):

'options' => [
    'worker_num' => swoole_cpu_num(),
    'task_worker_num' => swoole_cpu_num() * 2,
    'max_connection' => 100000,
    'heartbeat_check_interval' => 30,
    'heartbeat_idle_time' => 60,
],

Technique 7: Custom Worker Initialization

Optimize framework initialization for your specific application.

// config/octane.php
'init' => [
    function () {
        // Pre-warm cache
        Cache::rememberForever('config:all', function () {
            return config()->all();
        });
        
        // Pre-load frequently used models
        User::with('roles')->limit(10)->get();
        
        // Reset singleton instances that shouldn't persist
        app()->forgetInstance('redis');
    },
],

Advanced Initialization Patterns:

  • Pre-warm route cache
  • Initialize database connections
  • Warm up frequently used services
  • Reset problematic singleton instances

Technique 8: Request Lifecycle Hooks

Leverage Octane's event system for fine-grained control.

// app/Providers/AppServiceProvider.php
public function boot()
{
    Octane::callBeforeServingApplication(function (Application $app, Request $request) {
        // Reset problematic singletons
        $app->forgetInstance('session');
        
        // Custom request preparation
        if ($request->hasSession()) {
            $request->setLaravelSession($app->make('session.store'));
        }
    });

    Octane::callAfterResolvingResponse(function (Response $response, Request $request) {
        // Add custom headers
        $response->headers->set('X-Octane-Worker', posix_getpid());
        
        // Log performance metrics
        $duration = round(microtime(true) - LARAVEL_START, 4);
        Log::debug("Request completed in {$duration}s");
    });
}

Technique 9: Memory Profiling and Leak Detection

Proactively identify memory issues before they impact production.

# Profile memory usage
php artisan octane:profile-memory --interval=10 --duration=300

Interpreting Results:

  • Steady memory growth indicates a leak
  • Spikes followed by GC collection are normal
  • Memory plateaus suggest efficient usage

Advanced Profiling:

  • Use meminfo extension for detailed analysis
  • Implement custom memory tracking:
// app/Console/Commands/MemoryMonitor.php
public function handle()
{
    Octane::tick('memory-monitor', function () {
        $usage = memory_get_usage(true);
        $peak = memory_get_peak_usage(true);
        
        Log::info("Memory: {$usage} | Peak: {$peak}");
        
        if ($usage > 150 * 1024 * 1024) {
            Log::warning("High memory usage detected");
        }
    })->every(60);
}

Technique 10: Optimizing for Containerized Environments

Docker and Kubernetes require special considerations.

Dockerfile Optimizations:

# Use production-optimized PHP image
FROM php:8.2-fpm-alpine

# Install Swoole with production settings
RUN pecl install swoole-5.1.0 && \
    docker-php-ext-enable swoole && \
    echo "swoole.use_shortname='Off'" >> /usr/local/etc/php/conf.d/swoole.ini

# Set proper memory limits
ENV PHP_MEMORY_LIMIT=256M

Kubernetes Configuration:

resources:
  requests:
    memory: "256Mi"
    cpu: "250m"
  limits:
    memory: "512Mi"
    cpu: "1000m"
livenessProbe:
  exec:
    command:
      - curl
      - -s
      - http://localhost/health
  initialDelaySeconds: 10
  periodSeconds: 5

Technique 11: Advanced Logging Configuration

Logging in Octane requires special handling to prevent bottlenecks.

// config/logging.php
'channels' => [
    'octane' => [
        'driver' => 'monolog',
        'handler' => StreamHandler::class,
        'with' => [
            'stream' => 'php://stderr',
            'level' => 'debug',
        ],
        'processors' => [
            // Add worker ID to logs
            function ($record) {
                $record['extra']['worker'] = posix_getpid();
                return $record;
            },
        ],
    ],
],

Production Logging Strategies:

  • Use asynchronous logging channels
  • Implement log rotation specific to Octane
  • Filter out unnecessary debug information
  • Add worker ID to all log entries for tracing

Technique 12: Optimizing for Serverless Environments

AWS Lambda and similar platforms have unique constraints.

// config/octane.php
'options' => [
    'max_request' => 1, // Critical for serverless
    'worker_num' => 1,
],

Serverless-Specific Optimizations:

  • Minimize cold start time with pre-warmed containers
  • Use Lambda extensions for Swoole
  • Implement proper shutdown handling
  • Optimize package size by removing unnecessary dependencies

Technique 13: Advanced Health Checks

Custom health checks for Octane-specific issues.

// routes/web.php
Route::get('/health', function () {
    $status = [
        'status' => 'ok',
        'timestamp' => now()->toIso8601String(),
        'memory_usage' => memory_get_usage(),
        'memory_limit' => ini_get('memory_limit'),
        'worker_id' => app(Runner::class)->workerId() ?? 'n/a',
        'pending_requests' => app(Runner::class)->pendingRequests() ?? 'n/a',
        'database' => DB::connection()->getPdo() ? 'connected' : 'disconnected',
    ];

    // Check for memory pressure
    if (memory_get_usage() > 0.8 * returnBytes(ini_get('memory_limit'))) {
        $status['status'] = 'warning';
        $status['memory_warning'] = 'High memory usage';
    }

    return response()->json($status, $status['status'] === 'ok' ? 200 : 503);
});

Technique 14: Performance Benchmarking

Measure the impact of your tuning efforts.

Benchmarking Script:

# Install wrk
brew install wrk

# Basic benchmark
wrk -t12 -c400 -d30s http://localhost:8000

# With authentication
wrk -t12 -c400 -d30s -s auth.lua http://localhost:8000

auth.lua:

init = function(args)
    local cjson = require 'cjson'
    wrk.method = "POST"
    wrk.body = '{"email":"user@example.com","password":"password"}'
    wrk.headers["Content-Type"] = "application/json"
end

response = function(status, headers, body)
    if status == 200 then
        local data = cjson.decode(body)
        wrk.headers["Authorization"] = "Bearer " .. data.token
    end
end

Key Metrics to Track:

  • Requests per second (RPS)
  • Latency distribution (p50, p90, p99)
  • Error rates
  • Memory usage under load
  • CPU utilization patterns

Technique 15: Production Deployment Strategies

Implement zero-downtime deployments with Octane.

Blue-Green Deployment Script:

#!/bin/bash
# deploy.sh

# Start new instance on different port
php artisan octane:start --port=8001 --server=swoole &

# Wait for warm-up
sleep 10

# Test new instance
if ! curl -s http://localhost:8001/health | grep -q '"status":"ok"'; then
    echo "New instance failed health check"
    kill %1
    exit 1
fi

# Switch Nginx to new instance
sed -i '' 's/8000/8001/g' /etc/nginx/sites-available/laravel
nginx -s reload

# Gracefully stop old instance
php artisan octane:stop --port=8000

echo "Deployment successful"

Octane Configuration Checklist

Before deploying to production:

  • Set appropriate worker_num based on CPU cores
  • Configure max_request to prevent memory leaks
  • Implement custom health checks
  • Set up proper logging channels
  • Configure connection pooling for databases
  • Implement memory monitoring
  • Test under realistic load conditions
  • Configure proper shutdown handling
  • Set up deployment pipeline with zero-downtime

FAQ Section

How much performance improvement can I expect from Laravel Octane?

Most applications see 5-20x performance improvements in requests per second and reduced latency. API applications typically see the highest gains (15-20x), while view-heavy applications see 5-10x improvements. The exact improvement depends on your specific application architecture and workload patterns.

Should I use Swoole or RoadRunner for Laravel Octane?

Swoole generally offers better raw performance (10-15% faster) but has more complex installation. RoadRunner is easier to set up and has better Windows support. For most production environments, Swoole is preferred for maximum performance, while RoadRunner works well for development and simpler deployments.

How do I handle Laravel's singleton services in Octane?

Many Laravel services are designed as singletons that persist between requests. For services that should reset per-request (like sessions), implement custom reset logic in Octane's request lifecycle hooks. Use app()->forgetInstance() to clear problematic singletons before each request.

What's the optimal max_request setting for Laravel Octane?

Start with 1,000-5,000 requests per worker and adjust based on memory profiling. Monitor memory usage with php artisan octane:profile-memory and set max_request to trigger restarts before memory usage reaches 70-80% of your configured limit. For memory-intensive applications, you may need values as low as 500.

Conclusion

Mastering Laravel Octane configuration transforms your application from merely "using Octane" to truly leveraging its full performance potential. These 15 advanced techniques address the real-world challenges that emerge when moving beyond basic installation. Remember that optimal configuration is highly specific to your application's architecture and workload patterns—there's no universal "best" configuration.

The journey to peak Octane performance requires continuous monitoring, iterative tuning, and careful measurement of changes. Start with worker count and memory management, then progressively implement more advanced techniques as you understand your application's specific needs.

Ready to supercharge your Laravel application? Implement one tuning technique this week and measure the impact. Share your Octane optimization journey in the comments below, and subscribe for more Laravel performance guides!

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Laravel