О насБлогКонтакты
Backend5 сентября 2015 г. 4 мин 23

Как масштабировать Laravel приложение на VPS когда начинает тормозить (2015)

AunimedaAunimeda
📋 Содержание

Как масштабировать 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)

Ни строчки кода приложения не изменено — только конфигурация инфраструктуры.

Читайте также

Первый REST API для мобильного приложения: как мы учились проектировать API в 2012 годуaunimeda
Backend

Первый REST API для мобильного приложения: как мы учились проектировать API в 2012 году

В 2012 году REST был уже известной концепцией, но «хороший REST API» — понятием размытым. Мы набили классические шишки: неверная структура URL, отсутствие версионирования, небезопасная аутентификация. Вот что из этого вышло и как мы исправляли.

WebSocket-сервер на 100,000 соединений: архитектура и реальные подводные камниaunimeda
Бэкенд

WebSocket-сервер на 100,000 соединений: архитектура и реальные подводные камни

Как масштабировать WebSocket от 100 до 100k+ соединений: uWebSockets.js vs ws, Redis Pub/Sub между инстансами, heartbeat и reconnect логика, memory leak на неочищенных обработчиках.

Как перейти с монолита на микросервисы на PHP: реальный опыт 2016 годаaunimeda
Архитектура

Как перейти с монолита на микросервисы на PHP: реальный опыт 2016 года

В 2016 году микросервисы стали модным словом. Мы разделили монолитный Laravel-проект на три сервиса: auth, catalog, orders. Не всё прошло гладко. Вот что работало, что не работало, и когда микросервисы помогают, а когда это преждевременная оптимизация.

Нужна IT-разработка для вашего бизнеса?

Разрабатываем сайты, мобильные приложения и AI-решения для бизнеса в России. Бесплатная консультация.

Получить консультацию Все статьи