Как масштабировать Laravel приложение на VPS когда начинает тормозить (2015)
Коротко: Первый шаг — переключить сессии и кэш с файлов на Redis (SESSION_DRIVER=redis, CACHE_DRIVER=redis). Второй — вынести email/уведомления в очередь (QUEUE_DRIVER=redis). Третий — настроить PHP-FPM: pm = dynamic, pm.max_children = RAM / 30MB. Это решает 80% проблем производительности без смены архитектуры.
Диагностика: что именно тормозит
# PHP-FPM: сколько воркеров работает сейчас
sudo service php5-fpm status
# или
ps aux | grep php-fpm | wc -l
# Если все воркеры заняты — запросы встают в очередь
# Смотрим slow log:
tail -f /var/log/php5-fpm.log.slow
# Покажет запросы дольше request_slowlog_timeout секунд
# MySQL: что тормозит
mysql -u root -p -e "SHOW FULL PROCESSLIST;"
# Много queries в состоянии 'Locked' или 'Sorting result' = проблемы с индексами
Шаг 1: PHP-FPM настройка
# /etc/php5/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
listen = /var/run/php5-fpm.sock # Unix socket быстрее TCP для локального nginx
# Dynamic: воркеры создаются по необходимости
pm = dynamic
# Расчёт: RAM / средний размер PHP процесса
# 4 GB RAM, 35 MB на процесс = ~114 max
# Оставить 20% для OS и MySQL
pm.max_children = 90
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
# Перезапускать воркер после N запросов (борьба с утечками памяти в PHP 5.x)
pm.max_requests = 500
# Логировать медленные запросы
request_slowlog_timeout = 2s
slowlog = /var/log/php5-fpm.slow.log
# Ограничение по времени выполнения
request_terminate_timeout = 30s
sudo service php5-fpm restart
Шаг 2: Redis для сессий и кэша
sudo apt-get install redis-server
sudo apt-get install php5-redis # PHP расширение
# Или через Predis (чистый PHP, без расширения):
composer require predis/predis
# .env
CACHE_DRIVER=redis
SESSION_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
// config/database.php — Redis конфигурация
'redis' => [
'cluster' => false,
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
],
После переключения — файловые блокировки сессий исчезают. На 100 одновременных пользователей это снимает заметный стресс с файловой системы.
Шаг 3: Очереди для тяжёлых операций
<?php
// app/Jobs/SendOrderConfirmation.php — Job для Laravel 5
// Создать: php artisan make:job SendOrderConfirmation
namespace App\Jobs;
use App\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Bus\SelfHandling;
use App\Models\Order;
class SendOrderConfirmation extends Job implements SelfHandling {
use InteractsWithQueue, SerializesModels;
protected Order $order;
public function __construct(Order $order) {
$this->order = $order;
}
public function handle(\App\Services\Mailer $mailer): void {
$mailer->sendOrderConfirmation($this->order);
// Тяжёлые операции здесь: генерация PDF, отправка SMS, webhook
}
}
// В контроллере: вместо отправки письма прямо здесь — ставим в очередь
public function checkout(Request $request): JsonResponse {
$order = Order::create($request->validated());
// Было (блокирует ответ на 2-5 секунд):
// Mail::send('emails.order', compact('order'), fn($m) => ...);
// Стало (возвращает ответ мгновенно):
dispatch(new SendOrderConfirmation($order));
return response()->json(['order_id' => $order->id], 201);
}
Шаг 4: Supervisor для Queue Workers
# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/myapp/artisan queue:work redis --sleep=3 --tries=3
autostart=true
autorestart=true
user=www-data
numprocs=4 ; 4 параллельных воркера
redirect_stderr=true
stdout_logfile=/var/log/laravel-worker.log
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status
# Вывод:
# laravel-worker:laravel-worker_00 RUNNING pid 12345, uptime 0:00:10
# laravel-worker:laravel-worker_01 RUNNING pid 12346, uptime 0:00:10
# laravel-worker:laravel-worker_02 RUNNING pid 12347, uptime 0:00:10
# laravel-worker:laravel-worker_03 RUNNING pid 12348, uptime 0:00:10
Шаг 5: Laravel Caching
# Кэшировать конфиги, роуты, views для production
php artisan config:cache
php artisan route:cache
php artisan view:cache # Laravel 5.x: view:cache добавлен позже, используйте optimize
# Единая команда оптимизации (Laravel 5):
php artisan optimize # Кэширует config + автозагрузчик Composer
// Кэшировать дорогие запросы в коде
// До: каждый запрос — запрос к БД
$categories = Category::active()->get();
// После: первый запрос → БД, следующие 60 минут → Redis
$categories = Cache::remember('categories.active', 60, function() {
return Category::active()->get();
});
Результаты после оптимизации
На сервере 2 CPU / 4 GB RAM с 300 одновременными пользователями:
| Метрика | До | После |
|---|---|---|
| Avg response time | 1.8 с | 280 мс |
| PHP-FPM 502 errors | 15/мин | 0 |
| Потребление RAM | 3.7 GB | 2.1 GB |
| Redis hit rate | — | 87% |
| Email latency | 4 с (синхронно) | 0.1 с (async) |
Ни строчки кода приложения не изменено — только конфигурация инфраструктуры.