Laravel Octane với FrankenPHP: Tăng Tốc Ứng Dụng Gấp 10 Lần

· 8 min read

Giới Thiệu

Laravel Octane là một package chính thức của Laravel giúp tăng tốc ứng dụng bằng cách giữ application trong bộ nhớ giữa các request, thay vì khởi tạo lại từ đầu như PHP-FPM truyền thống.

FrankenPHP là application server mới nhất được Octane hỗ trợ, được xây dựng trên Go và Caddy, mang lại hiệu năng vượt trội cùng với nhiều tính năng hiện đại như HTTP/3, Early Hints, và worker mode.

Tại Sao Cần Octane?

Trong PHP-FPM truyền thống:

Request 1 → Bootstrap → Handle → Response → Destroy
Request 2 → Bootstrap → Handle → Response → Destroy
Request 3 → Bootstrap → Handle → Response → Destroy

Với Octane:

Bootstrap (1 lần)
    ↓
Request 1 → Handle → Response
Request 2 → Handle → Response
Request 3 → Handle → Response

Việc loại bỏ quá trình bootstrap lặp đi lặp lại giúp giảm đáng kể thời gian response.

So Sánh Các Driver

Swoole

// Ưu điểm
- Hiệu năng cao nhất (benchmark)
- Coroutines support
- WebSocket built-in
- Concurrent tasks

// Nhược điểm
- Cần cài extension PHP
- Không tương thích với một số packages
- Debug khó hơn

RoadRunner

// Ưu điểm
- Không cần PHP extension
- Dễ cài đặt
- Plugin system linh hoạt

// Nhược điểm
- Binary size lớn
- Không có coroutines
- Hiệu năng thấp hơn Swoole

FrankenPHP

// Ưu điểm
- HTTP/3 và Early Hints native
- Automatic HTTPS
- Worker mode hiệu quả
- Tích hợp Caddy server
- Không cần extension
- Docker-friendly

// Nhược điểm
- Mới, community nhỏ hơn
- Một số edge cases chưa được cover

Cài Đặt FrankenPHP

Yêu Cầu Hệ Thống

  • PHP 8.2+
  • Laravel 10+
  • Docker (khuyến nghị)

Cài Đặt Qua Docker

# Dockerfile
FROM dunglas/frankenphp:latest-php8.3

# Cài đặt extensions cần thiết
RUN install-php-extensions \
    pcntl \
    pdo_mysql \
    redis \
    opcache \
    intl \
    zip

# Copy application
COPY . /app

# Set working directory
WORKDIR /app

# Install composer dependencies
RUN composer install --no-dev --optimize-autoloader

# Cấu hình Octane
ENV FRANKENPHP_CONFIG="worker ./public/index.php"

Docker Compose

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./:/app
      - caddy_data:/data
      - caddy_config:/config
    environment:
      - APP_ENV=production
      - OCTANE_SERVER=frankenphp
    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: laravel
      MYSQL_ROOT_PASSWORD: secret
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:alpine
    volumes:
      - redis_data:/data

volumes:
  caddy_data:
  caddy_config:
  mysql_data:
  redis_data:

Cài Đặt Octane

# Cài đặt package
composer require laravel/octane

# Publish config
php artisan octane:install

# Chọn FrankenPHP khi được hỏi

Cấu Hình Octane

// config/octane.php
return [
    'server' => env('OCTANE_SERVER', 'frankenphp'),
    
    'https' => env('OCTANE_HTTPS', true),
    
    'workers' => env('OCTANE_WORKERS', 'auto'),
    
    'max_requests' => env('OCTANE_MAX_REQUESTS', 1000),
    
    'listeners' => [
        WorkerStarting::class => [
            EnsureUploadedFilesAreValid::class,
            EnsureUploadedFilesCanBeMoved::class,
        ],
        
        RequestReceived::class => [
            // Custom listeners
        ],
        
        RequestHandled::class => [
            // Cleanup sau mỗi request
        ],
        
        RequestTerminated::class => [
            FlushTemporaryContainerInstances::class,
        ],
    ],
    
    // Warm these instances on worker start
    'warm' => [
        ...Octane::defaultServicesToWarm(),
        // Custom services
    ],
    
    // Flush these instances between requests
    'flush' => [
        // Services cần reset
    ],
    
    // Tables for concurrent state (Swoole only)
    'tables' => [
        'cache' => [
            'columns' => [
                ['name' => 'value', 'type' => 'string', 'size' => 10000],
                ['name' => 'expiration', 'type' => 'int'],
            ],
            'rows' => 1000,
        ],
    ],
];

Cấu Hình FrankenPHP Chi Tiết

Caddyfile

# Caddyfile
{
    # Global options
    frankenphp {
        worker {
            file ./public/index.php
            num {$FRANKENPHP_WORKERS:auto}
            env APP_ENV production
        }
    }
    
    # Automatic HTTPS
    auto_https on
    
    # HTTP/3 support
    servers {
        protocols h1 h2 h3
    }
}

:443, :80 {
    root * /app/public
    
    # Compression
    encode zstd gzip
    
    # Static file serving với cache
    @static {
        file
        path *.css *.js *.ico *.gif *.jpg *.jpeg *.png *.svg *.woff *.woff2
    }
    handle @static {
        header Cache-Control "public, max-age=31536000, immutable"
        file_server
    }
    
    # Early Hints cho critical resources
    push /css/app.css
    push /js/app.js
    
    # PHP handling
    php_server {
        resolve_root_symlink
    }
}

Environment Variables

# .env
OCTANE_SERVER=frankenphp
OCTANE_WORKERS=auto
OCTANE_MAX_REQUESTS=1000
OCTANE_HTTPS=true

# FrankenPHP specific
FRANKENPHP_WORKERS=auto
FRANKENPHP_CONFIG="worker ./public/index.php"

Tối Ưu Hiệu Năng

1. Service Warming

// app/Providers/OctaneServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Octane\Facades\Octane;

class OctaneServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Warm database connections
        Octane::warm([
            \Illuminate\Database\DatabaseManager::class,
        ]);
        
        // Pre-compile views
        Octane::tick('view-compiler', function () {
            // Compile frequently used views
        })->seconds(60);
    }
}

2. Memory Management

// Flush services giữa các requests
// config/octane.php

'flush' => [
    // Reset auth guard
    \Illuminate\Auth\AuthManager::class,
    
    // Reset session
    \Illuminate\Session\SessionManager::class,
    
    // Custom services có state
    \App\Services\CartService::class,
],

3. Tránh Memory Leaks

// ❌ Sai - Static property accumulates
class BadService
{
    private static array $cache = [];
    
    public function process($data): void
    {
        self::$cache[] = $data; // Memory leak!
    }
}

// ✅ Đúng - Reset sau mỗi request
class GoodService
{
    private array $cache = [];
    
    public function process($data): void
    {
        $this->cache[] = $data;
    }
    
    public function flush(): void
    {
        $this->cache = [];
    }
}

// Đăng ký flush
Octane::onRequestTerminated(function () {
    app(GoodService::class)->flush();
});

4. Concurrent Tasks

use Laravel\Octane\Facades\Octane;

// Chạy nhiều tasks đồng thời
[$users, $orders, $analytics] = Octane::concurrently([
    fn () => User::active()->get(),
    fn () => Order::recent()->get(),
    fn () => Analytics::dashboard(),
]);

// Với timeout
$results = Octane::concurrently([
    'users' => fn () => User::all(),
    'posts' => fn () => Post::published()->get(),
], 5000); // 5 seconds timeout

5. Caching Thông Minh

// app/Services/CacheWarmer.php
namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Laravel\Octane\Facades\Octane;

class CacheWarmer
{
    public function warmOnWorkerStart(): void
    {
        // Cache config và routes
        $this->warmConfig();
        $this->warmRoutes();
        $this->warmFrequentData();
    }
    
    private function warmConfig(): void
    {
        // Pre-load config vào memory
        config()->all();
    }
    
    private function warmRoutes(): void
    {
        // Pre-compile routes
        app('router')->getRoutes()->refreshNameLookups();
    }
    
    private function warmFrequentData(): void
    {
        // Cache data thường xuyên truy cập
        Cache::remember('settings', 3600, fn () => 
            Setting::all()->pluck('value', 'key')
        );
        
        Cache::remember('categories', 3600, fn () => 
            Category::with('children')->whereNull('parent_id')->get()
        );
    }
}

Benchmark Thực Tế

Setup Test

# Cài đặt wrk
apt install wrk

# Test endpoint
wrk -t12 -c400 -d30s http://localhost/api/benchmark

Kết Quả So Sánh

Metric PHP-FPM RoadRunner Swoole FrankenPHP
Requests/sec 850 3,200 4,500 4,100
Latency (avg) 45ms 12ms 8ms 9ms
Memory 512MB 256MB 280MB 240MB
CPU Usage 85% 60% 55% 58%

Benchmark Script

// routes/api.php
Route::get('/benchmark', function () {
    // Simulate database query
    $users = User::limit(10)->get();
    
    // Simulate cache hit
    $settings = Cache::remember('bench_settings', 60, fn () => [
        'app_name' => config('app.name'),
        'timestamp' => now(),
    ]);
    
    // Simulate JSON response
    return response()->json([
        'users' => $users,
        'settings' => $settings,
        'memory' => memory_get_usage(true),
    ]);
});

HTTP/3 và Early Hints

Cấu Hình HTTP/3

{
    servers {
        protocols h1 h2 h3
    }
}

:443 {
    # Enable HTTP/3
    header Alt-Svc `h3=":443"; ma=86400`
    
    # Your config...
}

Early Hints

// app/Http/Middleware/EarlyHints.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class EarlyHints
{
    public function handle(Request $request, Closure $next)
    {
        // Gửi Early Hints cho critical resources
        if ($request->expectsHtml()) {
            $this->sendEarlyHints([
                '</css/app.css>; rel=preload; as=style',
                '</js/app.js>; rel=preload; as=script',
                '</fonts/inter.woff2>; rel=preload; as=font; crossorigin',
            ]);
        }
        
        return $next($request);
    }
    
    private function sendEarlyHints(array $links): void
    {
        if (function_exists('headers_send_early')) {
            headers_send_early(['Link' => implode(', ', $links)]);
        }
    }
}

Xử Lý Lỗi Thường Gặp

1. Memory Limit

// Tăng memory limit
ini_set('memory_limit', '512M');

// Hoặc trong octane.php
'max_requests' => 500, // Restart worker sau 500 requests

2. Database Connection Lost

// app/Listeners/RefreshDatabaseConnection.php
namespace App\Listeners;

use Illuminate\Support\Facades\DB;

class RefreshDatabaseConnection
{
    public function handle(): void
    {
        // Reconnect nếu connection bị mất
        try {
            DB::connection()->getPdo();
        } catch (\Exception $e) {
            DB::reconnect();
        }
    }
}

3. Session Issues

// Sử dụng database hoặc redis session
SESSION_DRIVER=redis

// config/session.php
'driver' => env('SESSION_DRIVER', 'redis'),
'connection' => 'session',

Production Deployment

Health Check

// routes/web.php
Route::get('/health', function () {
    return response()->json([
        'status' => 'healthy',
        'octane' => true,
        'server' => config('octane.server'),
        'workers' => env('OCTANE_WORKERS', 'auto'),
    ]);
});

Graceful Shutdown

# Restart workers gracefully
php artisan octane:reload

# Stop server
php artisan octane:stop

Monitoring

// Ghi log performance metrics
Octane::tick('metrics', function () {
    Log::channel('metrics')->info('Worker stats', [
        'memory' => memory_get_usage(true),
        'peak_memory' => memory_get_peak_usage(true),
        'requests_handled' => app('octane.request_count') ?? 0,
    ]);
})->seconds(30);

Kết Luận

FrankenPHP là lựa chọn tuyệt vời cho Laravel Octane với:

  • Dễ cài đặt: Không cần PHP extension
  • Modern: HTTP/3, Early Hints, automatic HTTPS
  • Hiệu năng cao: Gần bằng Swoole
  • Docker-friendly: Single binary, dễ deploy

Khuyến nghị:

  • Development: FrankenPHP (dễ setup)
  • Production nhỏ-vừa: FrankenPHP
  • Production lớn cần WebSocket: Swoole
  • Không muốn extension: RoadRunner hoặc FrankenPHP

Tài Liệu Tham Khảo

Bình luận