Урок 04

Драйверы очередей

Что такое драйвер

Драйвер — где хранится очередь и как обрабатывается.

В config/queue.php:

'default' => env('QUEUE_CONNECTION', 'database'),

Sync

Для dev / тестов:

QUEUE_CONNECTION=sync

Что делает: выполняет Job сразу, синхронно.

Плюсы:

  • Просто — ничего ставить не надо.
  • Отладка — сразу видно результат.

Минусы:

  • Блокирует HTTP.
  • Нет retry.
  • Нет параллельности.

Когда: локальная разработка, тесты.

Database

Очередь в БД:

QUEUE_CONNECTION=database

Миграция:

php artisan queue:table
php artisan queue:failed-table
php artisan migrate

Что создаёт:

  • jobs — таблица задач.
  • job_batches — группы.
  • failed_jobs — провалы.

Как работает:

  1. dispatch вставляет строку в jobs.
  2. Worker читает и удаляет.
  3. При ошибке → failed_jobs.

Плюсы:

  • Просто — не нужен Redis.
  • Надёжно — БД с бэкапами.
  • Видно — можно смотреть SQL.

Минусы:

  • Медленно на больших объёмах.
  • Блокировки при конкурентных воркерах.
  • Нагрузка на БД.

Когда: маленькие проекты, нет Redis.

Redis

Рекомендуемый:

QUEUE_CONNECTION=redis

Что нужно:

composer require predis/predis
# или ext-redis

config/database.php:

'redis' => [
    'client' => 'phpredis',   // или predis
    'default' => [
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'port' => env('REDIS_PORT', 6379),
        'password' => env('REDIS_PASSWORD'),
        'database' => 0,
    ],
],

Как работает:

  1. Job — в список Redis.
  2. Worker забирает.
  3. BRPOPLPUSH — атомарно.

Плюсы:

  • Скорость — миллисекунды.
  • Horizon — UI и метрики.
  • Масштабирование — миллионы задач.
  • Отложенные — через sorted sets.

Минусы:

  • Нужен Redis.
  • RAM — если задач много.
  • Не персистится по умолчанию (можно настроить).

Когда: продакшен, средние и крупные проекты.

Laravel Horizon

Дашборд для Redis-очередей.

composer require laravel/horizon
php artisan horizon:install

Запуск:

php artisan horizon

Что даёт:

  • Метрики — сколько задач, throughput.
  • Failed jobs — retry из UI.
  • Мониторинг — воркеры, очереди.
  • Auto-balance — распределение.

URL: /horizon.

В проде: supervisor + horizon.

Amazon SQS

Managed от AWS:

QUEUE_CONNECTION=sqs
AWS_ACCESS_KEY_ID=xxx
AWS_SECRET_ACCESS_KEY=xxx
AWS_DEFAULT_REGION=us-east-1
SQS_PREFIX=https://sqs.us-east-1.amazonaws.com/your-account-id
SQS_QUEUE=default

Плюсы:

  • Managed — нет своей инфры.
  • Надёжно — AWS SLA.
  • Бесплатно — миллион запросов в месяц.

Минусы:

  • Латентность — 10-100 мс.
  • Vendor lock-in.

Когда: AWS-инфраструктура.

Beanstalkd

Лёгкий брокер:

sudo apt install beanstalkd
QUEUE_CONNECTION=beanstalkd
BEANSTALKD_QUEUE_HOST=localhost

Плюсы:

  • Простой.
  • Быстрый.

Минусы:

  • Не персистентный (по умолчанию).
  • Меньше инструментов.

Когда: редко — Redis или database лучше.

Сравнение

ДрайверСкоростьНадёжностьПростота
syncМгновенноНет retry⭐⭐⭐⭐⭐
databaseМедленноВысокая⭐⭐⭐⭐⭐
redisБыстроСредняя⭐⭐⭐⭐
sqsСреднеВысокая⭐⭐⭐
beanstalkdБыстроСредняя⭐⭐

Приоритеты в конфиге

Несколько соединений:

'connections' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => env('REDIS_QUEUE', 'default'),
    ],
    'redis-high' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => 'high',
    ],
],

Dispatch:

ProcessPodcast::dispatch($podcast)->onConnection('redis-high');

Конфиг queues

Внутри connection:

'redis' => [
    'driver' => 'redis',
    'queue' => 'default',
    'retry_after' => 90,   // сек до повтора
    'block_for' => null,   // ждать задачу
    'after_commit' => true, // после commit транзакции
],

Что значит:

  • retry_after — если worker умер, через 90 сек задача вернётся.
  • block_for — worker ждёт задачу, а не спит.
  • after_commit — Job уйдёт только после commit БД-транзакции.

after_commit

Проблема:

DB::transaction(function () {
    $user = User::create([...]);
    SendWelcomeEmail::dispatch($user);   // Job может выполниться до commit
});
// Job обработается, но юзера ещё нет в БД

Решение:

// config/queue.php
'after_commit' => true,

Что даёт: Job уйдёт только после успешного commit.

Фейки

В тестах:

use Illuminate\Support\Facades\Queue;

public function test_send_email_dispatched(): void
{
    Queue::fake();

    $this->post('/register', [...]);

    Queue::assertPushed(SendWelcomeEmail::class);
    Queue::assertPushed(SendWelcomeEmail::class, function ($job) {
        return $job->user->email === 'test@example.com';
    });
    Queue::assertNotPushed(SomeOtherJob::class);
}

Что даёт: тесты без реальных задач.

Supervisor

Автозапуск воркеров:

/etc/supervisor/conf.d/laravel-worker.conf:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
user=www-data
autostart=true
autorestart=true
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/app/storage/logs/worker.log
stopwaitsecs=3600

Что даёт: 4 воркера, автозапуск.

Перезапуск после деплоя:

sudo supervisorctl restart laravel-worker:*
php artisan queue:restart

Масштабирование

Больше воркеров:

numprocs=10

Несколько серверов:

  • Все воркеры смотрят в одну очередь.
  • Redis / SQS шарит состояние.

Что даёт: горизонтальное масштабирование.

Практика: переключение драйверов

1. Dev:

QUEUE_CONNECTION=sync

2. Prod с database:

QUEUE_CONNECTION=database

3. Prod с Redis:

QUEUE_CONNECTION=redis

4. Тесты:

Queue::fake();

Частые ошибки

1. sync в проде:

Все задачи выполняются в HTTP-запросе

2. Database-очередь без миграции:

Base table or view not found: jobs

Решение: php artisan queue:table && migrate.

3. Redis без клиента:

Class 'Redis' not found

Решение: composer require predis/predis.

4. retry_after меньше timeout:

Одна задача выполняется дважды

Правило: retry_after > timeout.

5. Забыли after_commit:

Job не видит модель, которая ещё не в БД

Итог

  • sync — для dev.
  • database — просто и надёжно.
  • redis — для прода.
  • sqs — если AWS.
  • Horizon — UI и метрики.
  • after_commit — для транзакций.
  • Supervisor — автозапуск.
  • retry_after > timeout — правило.

Дальше — Laravel Horizon.