Table Of Contents
- Introduction
- Understanding Laravel Octane's Architecture
- Technique 1: Worker Count Optimization
- Technique 2: Memory Management and Leak Prevention
- Technique 3: Connection Pooling Configuration
- Technique 4: Optimizing for Different Server Types
- Technique 5: Advanced Garbage Collection Tuning
- Technique 6: Optimizing for Specific Workloads
- Technique 7: Custom Worker Initialization
- Technique 8: Request Lifecycle Hooks
- Technique 9: Memory Profiling and Leak Detection
- Technique 10: Optimizing for Containerized Environments
- Technique 11: Advanced Logging Configuration
- Technique 12: Optimizing for Serverless Environments
- Technique 13: Advanced Health Checks
- Technique 14: Performance Benchmarking
- Technique 15: Production Deployment Strategies
- Octane Configuration Checklist
- FAQ Section
- Conclusion
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
toswoole_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
andgc_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!
Add Comment
No comments yet. Be the first to comment!