The nuclear option. Keep Laravel alive in memory.
Octane is not for everyone. It eliminates 90% of Laravel's bootstrapping overhead, but introduces new challenges: memory leaks become permanent, global state becomes dangerous, and you can no longer rely on "fresh" application state per request. Only use Octane if you understand these trade-offs.
Octane makes Laravel 5-10x faster for most operations by eliminating the bootstrap overhead. A 50ms Laravel request becomes 5-10ms. A 200ms request becomes 20-30ms.
# Install Laravel Octane
composer require laravel/octane
# Install Swoole (recommended for performance)
composer require swoole/php-ext
sudo pecl install swoole
# OR install RoadRunner (easier)
php artisan octane:install --server=roadrunner
# Start Octane
php artisan octane:start --server=swoole --port=8000
# Run with multiple workers
php artisan octane:start --server=swoole --workers=4 --task-workers=2
| Operation | PHP-FPM | Octane (Swoole) | Improvement |
|---|---|---|---|
| Simple Hello World | 15-25ms | 1-2ms | 10-15x faster |
| Eloquent SELECT (100 rows) | 30-50ms | 5-10ms | 4-6x faster |
| Complex page with views | 80-150ms | 15-30ms | 4-5x faster |
| API endpoint (JSON) | 20-40ms | 3-8ms | 4-6x faster |
| Requests per second (RPS) | 50-200 RPS | 500-2000 RPS | 5-10x more capacity |
A typical Laravel e-commerce site:
Code that works perfectly in PHP-FPM can leak memory catastrophically in Octane. Test your application thoroughly with Octane before deploying to production. Monitor memory usage closely.
// Static property accumulates data
class UserService
{
private static array $processed = [];
public function process(User $user)
{
self::$processed[] = $user->id; // GROWS FOREVER
}
}
// Singleton accumulating data
$this->app->singleton(Analytics::class, function () {
return new Analytics(); // Events accumulate!
});
// Global variables
$GLOBALS['counter'] = ($GLOBALS['counter'] ?? 0) + 1;
// Use request-scoped data
class UserService
{
public function process(User $user)
{
$processed = []; // Fresh per request
$processed[] = $user->id;
}
}
// Use cache with TTL (not memory accumulation)
Cache::remember('counter', 60, function () {
return Counter::increment();
});
// Avoid globals entirely
return [
// Server type (swoole or roadrunner)
'server' => env('OCTANE_SERVER', 'swoole'),
// Number of workers (CPU cores * 1-2)
'workers' => env('OCTANE_WORKERS', 4),
// Number of task workers (for async tasks)
'task_workers' => env('OCTANE_TASK_WORKERS', 2),
// Max request size (bytes)
'max_request_size' => env('OCTANE_MAX_REQUEST_SIZE', 10 * 1024 * 1024),
// Listen on all interfaces
'host' => env('OCTANE_HOST', '0.0.0.0'),
// Port (use 80 with reverse proxy)
'port' => env('OCTANE_PORT', 8000),
// Reload workers after N requests (like pm.max_requests)
'max_requests' => env('OCTANE_MAX_REQUESTS', 1000),
// Watch for file changes (disable in production)
'watch' => env('OCTANE_WATCH', false),
// Swoole specific
'swoole' => [
'options' => [
'enable_coroutine' => false, // Disable for Laravel
'open_http2_protocol' => true,
'http_compression' => true,
],
],
// RoadRunner specific
'roadrunner' => [
'options' => [
'http' => [
'middleware' => ['static', 'gzip'],
],
],
],
];
# /etc/supervisor/conf.d/octane.conf
[program:octane]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan octane:start --server=swoole --workers=4 --task-workers=2 --max-requests=1000
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=forge
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/storage/logs/octane.log
stopwaitsecs=3600
[program:octane-watcher]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan octane:reload
autostart=true
autorestart=true
user=forge
numprocs=1
# /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com;
# Proxy all requests to Octane
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
# Static files still served by Nginx
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ {
root /var/www/example.com/public;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# Check Octane status
php artisan octane:status
# Output example:
# ┌─────────────────────────────────────────┐
# │ Port: 8000 │
# │ Server: Swoole │
# │ Workers: 4 │
# │ Task Workers: 2 │
# │ State: Running │
# │ Requests: 125,342 │
# └─────────────────────────────────────────┘
# Memory usage per worker
php artisan octane:status --verbose
# Reload workers (without downtime)
php artisan octane:reload
# Stop Octane
php artisan octane:stop
# Start Octane with custom memory limit
php artisan octane:start --memory-limit=512M
Monitor these metrics in production:
| Aspect | PHP-FPM | Octane (Swoole/RoadRunner) |
|---|---|---|
| Bootstrap per request | Yes (30-50ms) | No (once) |
| Response time (typical) | 50-200ms | 5-30ms |
| Requests per second | 50-200 | 500-2000 |
| Memory leak risk | Low (processes restart) | High (persistent) |
| Setup complexity | Low (standard) | Medium |
| Code compatibility | 100% | 95% (some patterns unsafe) |
Topic 22: SplFixedArray — The PHP array lie. Why normal arrays are actually hash maps. How SplFixedArray can save 70-80% memory for known-size arrays.