Laravel Octane — turbo PHP z RoadRunner i Swoole
Opublikowano: 10 kwietnia 2026 · Kategoria: Hosting
Laravel Octane to oficjalny pakiet pierwszej linii od Laravela, który rozwiązuje fundamentalne wąskie gardło PHP: każdy request uruchamia PHP od zera, ładuje Composera, bootstrapuje frameworka i tworzy service container. Octane ładuje aplikację raz i obsługuje tysiące requestów bez restartu. Wynik: 5-10x więcej requestów na sekundę. Artykuł pokazuje instalację z RoadRunner (prościej) i Swoole (wydajniej), zarządzanie shared state i konfigurację produkcyjną z Nginx.
Instalacja Laravel Octane z RoadRunner
# Wymagania: Laravel 9+, PHP 8.1+, VPS (hosting wspoldzielony NIE dziala)
# Instalacja pakietu
composer require laravel/octane
# Inicjalizacja — wybierz RoadRunner lub Swoole
php artisan octane:install
# Wybierz: roadrunner
# Octane pobiera binary RoadRunner automatycznie
# Sprawdz czy binary jest gotowe:
ls -la rr # powinno byc: ./rr (executable)
# Konfiguracja w config/octane.php jest generowana automatycznie
# Kluczowe ustawienia:
# 'server' => env('OCTANE_SERVER', 'roadrunner'),
# 'workers' => env('OCTANE_WORKERS', null), # null = auto (liczba CPU)
# 'max_requests' => env('OCTANE_MAX_REQUESTS', 500), # restart po N requestach
# Start w trybie dev
php artisan octane:start --watch # --watch: restart po zmianie plikow
# Sprawdz potencjalne problemy shared state
php artisan octane:check Instalacja z Swoole (alternatywna, wyższa wydajność)
# Wymagania: PHP 8.1+ z rozszerzeniem swoole # Ubuntu/Debian — instalacja Swoole przez PECL sudo apt install php8.2-dev php-pear sudo pecl install swoole # Lub przez repo ondrej/php (latwiej) sudo add-apt-repository ppa:ondrej/php sudo apt install php8.2-swoole # Weryfikacja php -m | grep swoole php -r "phpinfo();" | grep -i swoole # Konfiguracja w php.ini (dodaj jesli brakuje) # extension=swoole # swoole.use_shortname = Off # Wymagane przez Laravel # Instalacja Octane z Swoole php artisan octane:install # Wybierz: swoole # Opcje Swoole w config/octane.php: # 'swoole' => [ # 'options' => [ # 'max_request' => 500, # 'dispatch_mode' => 2, # round-robin # 'reactor_num' => 2, # I/O reactor threads # 'worker_num' => 4, # workery PHP # 'task_worker_num' => 2, # task workers (dla queue) # ], # ],
Shared State — najważniejszy problem Octane
PHP-FPM resetuje cały stan po każdym requeście. Octane tego nie robi — worker żyje przez setki requestów. Singleton zarejestrowany w AppServiceProvider może "wyciec" między requestami. Laravel Octane automatycznie resetuje kilka singletons (Auth, Session, Cookie), ale własny kod może mieć problemy.
// PROBLEM: singleton przechowujacy stan per-request
class UserContext {
private ?User $user = null;
// Singleton — ta instancja zyje przez caly czas zycia workera
public function setUser(User $user): void {
$this->user = $user;
}
public function getUser(): ?User {
return $this->user; // WYCIEK! Poprzedni user bedzie widoczny w nastepnym requeście
}
}
// ROZWIAZANIE 1: Nie uzywa singletonu — tworz nowa instancje per request
// W AppServiceProvider:
$this->app->bind(UserContext::class); // bind zamiast singleton
// ROZWIAZANIE 2: Uzyj hooka terminating w Octane
use Laravel\Octane\Facades\Octane;
Octane::tick('clear-user-context', function () {
app(UserContext::class)->clear();
})->seconds(0); // po kazdym requeście
// ROZWIAZANIE 3: Reset w ServiceProvider
// W AppServiceProvider::boot():
$this->callAfterResolving('user-context', function ($context, $app) {
if ($app->runningInOctane()) {
Octane::onRequestTerminated(fn() => $context->clear());
}
});
// BEZPIECZNE WZORCE w Octane:
// ✓ Fasady (facade proxy do kontenera — OK)
// ✓ Eloquent modele (nowe instancje per query)
// ✓ request() helper (Octane podmienia per request)
// ✓ auth() / Auth:: (resetowane przez Octane)
// ✗ Klasy z statycznymi wlasciwosciami (static $cache = [])
// ✗ Wlasne singletons z mutowalnym stanem Warm-up routines — przyspieszenie startu
// Warm-up w config/octane.php — wstepne ladowanie klas do pamieci
'warm' => [
...Octane::defaultServicesToWarm(),
// Dodaj wlasne klasy które sa uzywane przy kazdym requeście
App\Services\PricingService::class,
App\Repositories\ProductRepository::class,
App\Http\Middleware\TrustProxies::class,
],
// Wlasny warm-up — np. cache danych ze swiata rzeczywistego
'listeners' => [
WorkerStarting::class => [
EnsureUploadDirectoriesExist::class,
// Wlasny listener
App\Listeners\WarmApplicationCache::class,
],
],
// App\Listeners\WarmApplicationCache:
class WarmApplicationCache {
public function handle(WorkerStarting $event): void {
// Wstepne zaladowanie najczesciej uzywanych konfiguracji do cache
Cache::remember('site_settings', 3600, fn() => Setting::all());
}
} Systemd + Nginx — konfiguracja produkcyjna
# /etc/systemd/system/laravel-octane.service
[Unit]
Description=Laravel Octane
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/php artisan octane:start \
--server=roadrunner \
--host=127.0.0.1 \
--port=8000 \
--workers=4 \
--max-requests=500
ExecReload=/bin/kill -HUP $MAINPID
KillMode=mixed
TimeoutStopSec=30
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
# Nginx — reverse proxy do Octane
# /etc/nginx/sites-available/laravel-octane
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/myapp/public;
index index.php;
# Pliki statyczne bezposrednio przez Nginx
location ~* \.(css|js|gif|ico|jpg|png|svg|woff2|woff|ttf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# Octane obsluguje wszystko inne
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_read_timeout 60s;
}
}
# Wlaczenie i restart
sudo systemctl daemon-reload && sudo systemctl enable --now laravel-octane Benchmarki vs PHP-FPM
| Środowisko | Req/s (avg) | Latency P95 | RAM per worker |
|---|---|---|---|
| PHP-FPM (klasyczny) | ~800 req/s | ~45 ms | ~30 MB |
| Octane RoadRunner (4 workers) | ~5 500 req/s | ~8 ms | ~80 MB |
| Octane Swoole (4 workers) | ~6 200 req/s | ~7 ms | ~90 MB |
Wyniki orientacyjne dla prostego endpointu JSON. Realne aplikacje z bazą danych i złożoną logiką zyskują mniej — ale nadal 3-5x. Większe zużycie RAM to cena za trzymanie aplikacji w pamięci.