Laravel Octane Production: Những bài học từ 6 tháng vận hành
Kinh nghiệm thực tế deploy và maintain Laravel Octane trong production: memory leaks, monitoring, scaling và những cạm bẫy cần tránh.
6 tháng trước, tôi migrate API của công ty từ PHP-FPM sang Laravel Octane.
Performance tăng 4 lần. Response time giảm từ 450ms xuống 120ms. Mọi thứ hoàn hảo… cho đến tuần thứ 2.
Lúc 3 giờ sáng, tôi nhận được alert: “API down. Memory usage 100%”.
Đó là bài học đầu tiên về Octane production. Đây là những gì tôi học được sau 6 tháng vận hành.
Lesson 1: Memory Leak là kẻ thù số 1
Vấn đề
Sau 2-3 ngày chạy, memory usage của workers tăng từ 50MB lên 800MB. Server crash.
Root Cause
Bug 1: Static cache trong Service
class ProductService
{
private static $categoryCache = [];
public function getProductsByCategory($categoryId)
{
if (!isset(self::$categoryCache[$categoryId])) {
self::$categoryCache[$categoryId] = Product::where('category_id', $categoryId)->get();
}
return self::$categoryCache[$categoryId];
}
}
Vấn đề: Cache không bao giờ clear. Sau 10,000 requests, cache chứa hàng ngàn categories.
Fix:
class ProductService
{
public function getProductsByCategory($categoryId)
{
// Dùng Laravel Cache với TTL
return Cache::remember("products.category.{$categoryId}", 3600, function () use ($categoryId) {
return Product::where('category_id', $categoryId)->get();
});
}
}
Bug 2: Event Listeners tích lũy
// AppServiceProvider
public function boot()
{
Event::listen('order.created', function ($order) {
// Process order
});
}
Vấn đề: Mỗi lần worker restart (nhưng không exit process), listener được register lại. Sau 100 lần, có 100 listeners cho cùng event.
Fix:
public function boot()
{
// Chỉ register một lần
if (!app()->bound('order.listener.registered')) {
Event::listen('order.created', OrderCreatedListener::class);
app()->instance('order.listener.registered', true);
}
}
Hoặc dùng Event Listener classes thay vì closures.
Solution: Auto-restart workers
// config/octane.php
return [
'swoole' => [
'options' => [
'max_request' => 5000, // Restart sau 5000 requests
'max_conn' => 1000,
],
],
];
Trade-off: Performance giảm nhẹ (restart overhead), nhưng stability tăng đáng kể.
Lesson 2: Monitoring là bắt buộc
Setup monitoring cho Octane
1. Memory monitoring
// app/Console/Commands/MonitorOctane.php
class MonitorOctane extends Command
{
public function handle()
{
$workers = $this->getWorkerStats();
foreach ($workers as $worker) {
if ($worker['memory_mb'] > 500) {
// Alert: Worker memory too high
Log::critical("Octane worker {$worker['pid']} using {$worker['memory_mb']}MB");
// Auto-restart worker
$this->restartWorker($worker['pid']);
}
}
}
private function getWorkerStats()
{
$output = shell_exec('ps aux | grep "octane:start"');
// Parse output và return stats
}
}
Schedule command mỗi phút:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
$schedule->command('octane:monitor')->everyMinute();
}
2. Request metrics
// app/Http/Middleware/OctaneMetrics.php
class OctaneMetrics
{
public function handle($request, $next)
{
$start = microtime(true);
$startMemory = memory_get_usage();
$response = $next($request);
$duration = (microtime(true) - $start) * 1000;
$memoryUsed = (memory_get_usage() - $startMemory) / 1024 / 1024;
Log::info('Request metrics', [
'url' => $request->url(),
'duration_ms' => $duration,
'memory_mb' => $memoryUsed,
'worker_pid' => getmypid(),
]);
return $response;
}
}
3. Grafana Dashboard
Export metrics sang Prometheus:
Route::get('/metrics', function () {
$workers = app('octane')->workers();
return response([
'octane_workers_total' => count($workers),
'octane_memory_usage_bytes' => memory_get_usage(),
'octane_requests_total' => Cache::get('octane.requests.total', 0),
])->header('Content-Type', 'text/plain');
});
Lesson 3: Database Connection Pool
Vấn đề
Sau khi scale lên 4 servers, database báo: “Too many connections”.
Root Cause
Mỗi Octane worker giữ 1 persistent connection.
- 4 servers × 8 workers/server = 32 connections
- MySQL max_connections = 50
- Chỉ còn 18 connections cho background jobs, admin panel, etc.
Solution 1: Tăng max_connections
-- MySQL
SET GLOBAL max_connections = 200;
Trade-off: Tốn nhiều RAM hơn.
Solution 2: Connection pooling
// config/database.php
'mysql' => [
'driver' => 'mysql',
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
'wait_timeout' => 3.0,
],
],
Lưu ý: Chỉ Swoole hỗ trợ connection pooling. RoadRunner không có.
Solution 3: Giảm số workers
// config/octane.php
'swoole' => [
'options' => [
'worker_num' => 4, // Giảm từ 8 xuống 4
],
],
Trade-off: Throughput giảm, nhưng ổn định hơn.
Lesson 4: Session & Cache phải dùng Redis
Vấn đề
User login ở server A, request tiếp theo đến server B → Chưa login.
Root Cause
Session driver = file. Mỗi server có file system riêng.
Solution
SESSION_DRIVER=redis
CACHE_DRIVER=redis
REDIS_HOST=redis-cluster.example.com
Bonus: Redis cũng nhanh hơn file driver.
Lesson 5: Graceful Shutdown
Vấn đề
Deploy code mới → Octane restart → Requests đang xử lý bị drop → Users thấy 502 error.
Solution: Zero-downtime deployment
Bước 1: Reload thay vì restart
# Bad: Hard restart
php artisan octane:restart
# Good: Graceful reload
php artisan octane:reload
reload đợi workers xử lý xong requests hiện tại trước khi restart.
Bước 2: Blue-Green Deployment
# Deploy code mới lên server "green"
# Chuyển load balancer từ "blue" sang "green"
# Đợi "blue" xử lý xong requests
# Shutdown "blue"
Bước 3: Health check
Route::get('/health', function () {
return response()->json([
'status' => 'ok',
'octane' => true,
'memory_mb' => memory_get_usage(true) / 1024 / 1024,
]);
});
Load balancer chỉ route traffic đến servers có health check pass.
Lesson 6: File Uploads cần special handling
Vấn đề
User upload file lên server A. Request tiếp theo (download file) đến server B → File not found.
Solution: Shared storage
Option 1: S3
// config/filesystems.php
'default' => 's3',
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
],
Option 2: NFS/EFS
Mount shared volume trên tất cả servers:
# /etc/fstab
nfs-server:/exports/uploads /var/www/storage/app/public nfs defaults 0 0
Lesson 7: Logging cần centralized
Vấn đề
Bug xảy ra → Phải SSH vào 4 servers để đọc log → Mất thời gian.
Solution: Centralized logging
Option 1: Laravel Log to Database
// config/logging.php
'channels' => [
'database' => [
'driver' => 'custom',
'via' => App\Logging\DatabaseLogger::class,
],
],
Option 2: ELK Stack (Elasticsearch, Logstash, Kibana)
// config/logging.php
'channels' => [
'logstash' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\SocketHandler::class,
'handler_with' => [
'connectionString' => 'tcp://logstash:5000',
],
],
],
Option 3: Cloud logging (Papertrail, Logtail)
'channels' => [
'papertrail' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\SyslogUdpHandler::class,
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
],
],
],
Production Checklist
Trước khi deploy Octane production:
- Test memory leaks với load test (10,000+ requests)
- Set
max_requestđể auto-restart workers - Monitor memory usage của workers
- Dùng Redis cho session và cache
- Setup connection pooling cho database
- Implement health check endpoint
- Centralized logging
- Shared storage cho file uploads
- Graceful shutdown trong deployment
- Rollback plan (có thể quay lại PHP-FPM nhanh chóng)
Kết luận
Laravel Octane trong production không phải “cài xong là xong”.
Những điều tôi ước mình biết trước:
- Memory leak sẽ xảy ra, cần monitoring chặt chẽ
- Database connections cần quản lý cẩn thận
- Session/Cache phải dùng Redis
- File uploads cần shared storage
- Logging phải centralized
Có đáng không?
Sau 6 tháng, câu trả lời của tôi là: Có.
- API response time giảm 70%
- Server cost giảm 40% (cần ít servers hơn)
- User experience tốt hơn đáng kể
Nhưng bạn phải sẵn sàng đầu tư thời gian vào monitoring và maintenance.
Lời khuyên cuối:
- Bắt đầu với staging environment
- Monitor kỹ 2-4 tuần trước khi scale
- Luôn có rollback plan
- Document mọi issue và fix
Octane không phải cho mọi project. Nhưng nếu project của bạn cần performance, và team sẵn sàng học, nó là một game changer.
Cách tốt nhất để detect memory leak trong Laravel Octane production?
Thử Thách Kiến Thức Lịch Sử?
Khám phá hàng trăm câu hỏi trắc nghiệm lịch sử thú vị tại HistoQuiz. Vừa học vừa chơi, nâng cao kiến thức ngay hôm nay!