Очереди задач — одна из тех вещей, о которых разработчики часто вспоминают слишком поздно: когда сайт уже тормозит, письма не доходят, а пользователи ждут по 30 секунд ответа от сервера. Я прошёл этот путь на десятках проектов и сейчас расскажу, как правильно настроить очереди в 2026 году — на реальных примерах с Laravel, Bitrix и WordPress.
Что такое очереди задач и зачем они вообще нужны
Грубо говоря, очередь задач — это механизм, который позволяет откладывать тяжёлые или долгие операции на потом, не заставляя пользователя ждать. Нажал кнопку "Отправить заявку" — получил мгновенный ответ. А отправка письма, запись в CRM, генерация PDF — всё это уходит в очередь и обрабатывается в фоне.
На практике без очередей я регулярно вижу одну и ту же картину: интернет-магазин на 500 товаров работает нормально, потом подключают рассылку на 10 000 подписчиков — и всё, сайт падает под нагрузкой. Или другой сценарий: форма обратной связи "зависает" на 15 секунд, потому что пытается прямо в момент отправки дёрнуть три внешних API, записать лог, отправить уведомление в Telegram и создать задачу в CRM. Это классическая причина медленной работы сайта, которую легко устранить.
Типичные задачи, которые однозначно стоит выносить в очередь: отправка email и SMS, интеграции с внешними сервисами, генерация отчётов и экспорт данных, ресайз изображений, синхронизация с 1С, пересчёт цен и остатков в каталоге, отправка push-уведомлений. По моему опыту, если операция занимает больше 500 мс — она уже кандидат в очередь.
Инструменты: Redis, RabbitMQ, Beanstalkd — что выбрать
Самый популярный бэкенд для очередей сегодня — Redis. Я использую его на большинстве проектов: он быстрый, простой в настройке, хорошо интегрируется с Laravel, и заодно можно использовать для кеширования сессий. Если интересно, как его настраивать в связке с кешем — почитай мою статью про настройку Redis и Memcached для сайта.
RabbitMQ — это уже серьёзный инструмент для highload-проектов. Поддерживает сложные маршруты сообщений, приоритеты, dead letter queues, кластеризацию. Я ставил его на проекте с 200+ тысячами заказов в день — там Redis уже не справлялся с надёжностью доставки. Но для среднестатистического сайта — избыточно, честно говоря.
Beanstalkd — лёгкий и быстрый, но функционал ограничен. Database queue (через MySQL) — самый простой вариант для старта, нулевые требования к инфраструктуре, но при большой нагрузке таблица очереди превращается в узкое место. На проектах с PHP 8.1–8.3 я обычно стартую с Redis, и только если появляются специфические требования к маршрутизации — перехожу на RabbitMQ.
SQS от Amazon — хороший вариант, если проект уже живёт в AWS-инфраструктуре. Управляемый сервис, не надо думать про отказоустойчивость. Но добавляет latency (обычно 50–200 мс на запрос к API), что для некоторых сценариев критично.
Настройка очередей в Laravel: от нуля до продакшена
Laravel — это моя основная среда для серьёзных проектов, и там с очередями всё сделано максимально удобно. Конфиг в .env:
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
Создаём Job-класс:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Order;
use App\Mail\OrderConfirmation;
use Illuminate\Support\Facades\Mail;
class SendOrderConfirmation implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
// Количество попыток при ошибке
public int $tries = 3;
// Таймаут выполнения в секундах
public int $timeout = 60;
// Задержка между повторными попытками (секунды)
public int $backoff = 30;
public function __construct(
private readonly Order $order
) {}
public function handle(): void
{
Mail::to($this->order->email)
->send(new OrderConfirmation($this->order));
// Логируем успешную отправку
$this->order->update(['confirmation_sent_at' => now()]);
}
public function failed(\Throwable $exception): void
{
// Уведомляем команду об ошибке
\Log::error('Order confirmation failed', [
'order_id' => $this->order->id,
'error' => $exception->getMessage(),
]);
}
}
Диспатчим задачу из контроллера:
// Немедленно в очередь
SendOrderConfirmation::dispatch($order);
// С задержкой 5 минут
SendOrderConfirmation::dispatch($order)->delay(now()->addMinutes(5));
// В конкретную очередь с приоритетом
SendOrderConfirmation::dispatch($order)->onQueue('high-priority');
Важный момент, который я часто вижу упущенным: настройка разных очередей с разными приоритетами. Не надо смешивать отправку транзакционных писем и генерацию месячного отчёта в одну очередь. У меня стандартная схема: очередь critical для платёжных уведомлений, default для обычных задач, low для тяжёлых фоновых операций.
Supervisor: запускаем воркеры правильно
Без Supervisor воркеры очередей — это боль. Упал процесс — очередь встала, никто не знает. Supervisor автоматически перезапускает воркеры и следит за их количеством.
Конфиг /etc/supervisor/conf.d/laravel-worker.conf:
[program:laravel-worker-default]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/mysite.ru/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 --queue=critical,default,low
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/log/supervisor/laravel-worker.log
stopwaitsecs=3600
[program:laravel-worker-heavy]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/mysite.ru/artisan queue:work redis --sleep=5 --tries=2 --max-time=7200 --queue=low --memory=256
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/supervisor/laravel-worker-heavy.log
Параметр --max-time=3600 — это важная деталь. Воркер сам завершается через час и Supervisor его перезапускает. Это предотвращает утечки памяти при долгой работе. На PHP 8.2 и 8.3 с этим получше, чем на 8.0, но всё равно лучше перестраховаться.
После изменения конфига:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker-default:*
sudo supervisorctl status
php artisan queue:restart — это корректно завершит текущие воркеры после обработки активных задач и Supervisor поднимет новые с обновлённым кодом. Без этого воркеры продолжат работать со старым кодом, и ты будешь долго искать, почему изменения не применяются.Очереди в WordPress: WP Cron vs ActionScheduler
WordPress — отдельная история. Штатный WP-Cron — это, честно говоря, не настоящий планировщик. Он срабатывает только при посещении сайта, что создаёт целый ряд проблем. Подробнее про cron-задачи я писал в статье про настройку cron jobs в 2026 году — там же про правильную замену WP-Cron на системный cron.
Для серьёзных очередей в WordPress я использую Action Scheduler — библиотека, которая входит в WooCommerce и доступна как отдельный пакет. Она хранит задачи в базе данных, поддерживает повторные попытки, логирует выполнение, умеет работать с batch-операциями. Это реально хороший инструмент.
<?php
// Регистрируем обработчик
add_action('my_plugin_send_notification', 'handle_send_notification');
function handle_send_notification(int $user_id, string $message): void {
$user = get_user_by('id', $user_id);
if (!$user) return;
wp_mail(
$user->user_email,
'Уведомление',
$message
);
}
// Добавляем задачу в очередь (выполнится через 5 секунд)
as_enqueue_async_action(
'my_plugin_send_notification',
['user_id' => 123, 'message' => 'Ваш заказ подтверждён'],
'notifications' // группа
);
// Отложенная задача (через 10 минут)
as_schedule_single_action(
time() + 600,
'my_plugin_send_notification',
['user_id' => 123, 'message' => 'Напоминание']
);
// Повторяющаяся задача (каждый час)
as_schedule_recurring_action(
time(),
HOUR_IN_SECONDS,
'my_plugin_sync_stock',
[]
);
У одного клиента был интернет-магазин на WooCommerce с кастомной синхронизацией с 1С. Каждая синхронизация обрабатывала до 5000 позиций и занимала 3–4 минуты. Раньше это запускалось через обычный AJAX-запрос — и сервер регулярно падал с 502. Перевели на Action Scheduler с batch по 100 товаров — каждый batch отдельная задача, выполняется за 3–5 секунд. Проблема исчезла полностью, а правильное кеширование добавили следующим шагом.
Очереди задач в Битрикс
В Битриксе нет встроенной системы очередей в классическом понимании, но есть несколько инструментов, которые можно использовать. Первый — агенты Битрикс. Это задачи, которые выполняются при обращении к сайту (аналог WP-Cron). Для продакшена это слабовато.
Второй вариант — модуль bitrix.xmpp с очередями событий, но это для корпоративного портала. Для сайтов я обычно комбинирую системный cron + кастомные обработчики через события Битрикс + Redis как брокер. Выглядит примерно так:
<?php
// Файл: /local/php_interface/init.php
// Подписываемся на событие создания заказа
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'sale',
'OnSaleOrderSaved',
function(\Bitrix\Main\Event $event) {
$order = $event->getParameter('ENTITY');
if ($order->isNew()) {
// Пушим в Redis вместо синхронной обработки
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$redis->rPush('order_notifications', json_encode([
'order_id' => $order->getId(),
'timestamp' => time(),
]));
}
}
);
<?php
// Файл: /local/cron/process_order_notifications.php
// Запускается системным cron каждую минуту
require_once($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$processed = 0;
$maxPerRun = 50; // Обрабатываем не больше 50 задач за раз
while ($processed < $maxPerRun) {
$item = $redis->lPop('order_notifications');
if (!$item) break;
$data = json_decode($item, true);
try {
// Здесь логика: отправка письма, синхронизация с CRM
\Bitrix\Main\Mail\Event::sendImmediate([
'EVENT_NAME' => 'ORDER_CONFIRMATION',
'LID' => SITE_ID,
'C_FIELDS' => ['ORDER_ID' => $data['order_id']],
]);
$processed++;
} catch (\Exception $e) {
// Возвращаем в очередь при ошибке
$redis->rPush('order_notifications_failed', $item);
\Bitrix\Main\Diag\Debug::writeToFile(
$e->getMessage(),
'Queue Error',
'/local/logs/queue.log'
);
}
}
Это не идеальное решение, но рабочее. Для серьёзных highload-проектов на Битриксе я рекомендую смотреть в сторону правильной архитектуры с кешированием и выносить тяжёлые операции на отдельный микросервис на Laravel или Node.js.
Мониторинг очередей: как не проспать проблему
Настроить очереди — полдела. Важно знать, что они работают. У меня был неприятный случай: Redis на одном проекте упал в 3 ночи, воркеры перестали работать, очередь накопила 40 000 задач. Утром клиент обнаружил, что за ночь не ушло ни одного письма. Это был урок.
Теперь я всегда настраиваю мониторинг очередей. В Laravel есть Horizon — это must-have для любого продакшен-проекта с Redis. Устанавливается просто:
composer require laravel/horizon
php artisan horizon:install
php artisan migrate
Horizon даёт дашборд с метриками: throughput, время выполнения задач, failed jobs, размер очереди. Можно настроить алерты через config/horizon.php. Supervisor для Horizon:
[program:laravel-horizon]
process_name=%(program_name)s
command=php /var/www/mysite.ru/artisan horizon
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/horizon.log
stopwaitsecs=3600
Для мониторинга размера очереди я добавляю проверку в систему мониторинга — у меня это обычно связка с настройкой мониторинга через Zabbix или UptimeRobot. Простой bash-скрипт для проверки:
#!/bin/bash
# Проверяем размер очереди Redis
QUEUE_SIZE=$(redis-cli llen queues:default)
THRESHOLD=1000
if [ "$QUEUE_SIZE" -gt "$THRESHOLD" ]; then
echo "ALERT: Queue size is $QUEUE_SIZE (threshold: $THRESHOLD)"
# Отправляем уведомление в Telegram
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d "chat_id=${CHAT_ID}" \
-d "text=⚠️ Очередь задач перегружена: $QUEUE_SIZE задач в ожидании"
fi
Оптимизация и масштабирование под нагрузку
Когда базовая настройка работает, приходит время оптимизации. Первое, на что я смотрю — количество воркеров. Слишком мало — очередь растёт. Слишком много — жрут память и создают конкуренцию за ресурсы базы данных. На PHP 8.2 воркер Laravel потребляет около 30–50 МБ RAM в базовом состоянии. На сервере с 4 ГБ RAM я обычно держу 8–12 воркеров с учётом других процессов.
Второй момент — chunk-обработка тяжёлых задач. Если нужно обработать 50 000 записей, не надо пихать всё в одну задачу. Разбивай на chunks по 100–500 записей, каждый chunk — отдельная задача. Так очередь не блокируется одной тяжёлой операцией, и можно параллельно обрабатывать другие задачи.
<?php
// Диспатчим batch-задачи
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
$users = User::where('newsletter', true)->pluck('id');
$chunks = $users->chunk(100);
$batch = Bus::batch(
$chunks->map(fn($chunk) => new SendNewsletterBatch($chunk))
)->then(function (Batch $batch) {
\Log::info('Newsletter sent successfully', [
'total' => $batch->totalJobs,
]);
})->catch(function (Batch $batch, \Throwable $e) {
\Log::error('Newsletter batch failed', ['error' => $e->getMessage()]);
})->onQueue('newsletter')
->dispatch();
// Получаем ID батча для отслеживания
$batchId = $batch->id;
Третье — rate limiting для задач, которые дёргают внешние API. Если у тебя 1000 задач, каждая из которых обращается к API с лимитом 100 запросов в минуту — без rate limiting ты получишь шквал ошибок 429. В Laravel есть встроенный механизм:
<?php
// В Job-классе
public function middleware(): array
{
return [
// Максимум 60 задач в минуту для этого типа
new \Illuminate\Queue\Middleware\RateLimited('external-api'),
];
}
// В ServiceProvider регистрируем лимит
\Illuminate\Support\Facades\RateLimiter::for('external-api', function ($job) {
return \Illuminate\Cache\RateLimiting\Limit::perMinute(60);
});
Четвёртое — правильная обработка failed jobs. Не просто логировать, а анализировать паттерны. Если одна и та же задача падает раз в сутки — это не случайность, это баг или проблема с внешним сервисом. Я регулярно проверяю таблицу failed_jobs и настраиваю алерты на всплески ошибок.
Базы данных и очереди: типичные грабли
Отдельная тема — взаимодействие очередей с базой данных. Одна из самых частых проблем: воркер держит транзакцию открытой слишком долго, блокирует таблицы и тормозит весь сайт. Это особенно актуально для MySQL 5.7 с движком InnoDB — там блокировки строк могут превращаться в deadlock-кошмар.
На MySQL 8.0 с правильными индексами ситуация лучше, но принцип остаётся: задачи в очереди должны работать с базой максимально быстро и точечно. Никаких SELECT * без LIMIT в воркерах. Никаких долгих транзакций. Если нужна оптимизация запросов — делай её до того, как эти запросы попадут в очередь.
Ещё один момент: если используешь database-драйвер для очередей (не Redis, а MySQL), при высокой нагрузке таблица jobs превращается в горячую точку. Сотни воркеров одновременно делают SELECT + UPDATE + DELETE. Это создаёт серьёзную нагрузку. Мой совет — для продакшена с более чем 100 задачами в минуту переходи на Redis. Database-драйвер хорош только для разработки или очень низкой нагрузки.
И не забывай про очистку: таблица failed_jobs и логи Horizon со временем растут. Я добавляю в cron еженедельную очистку старых записей:
# В crontab
0 2 * * 0 cd /var/www/mysite.ru && php artisan queue:flush --before="$(date -d '30 days ago' '+%Y-%m-%d')"
0 3 * * 0 cd /var/www/mysite.ru && php artisan horizon:clear
Безопасность очередей: что важно не упустить
Про безопасность очередей говорят редко, но зря. Если Redis доступен без пароля и без привязки к localhost — это огромная дыра. Я видел серверы, где Redis слушал на 0.0.0.0:6379 без аутентификации. Это значит, что любой желающий может читать и писать в очередь, включая добавление вредоносных задач.
Минимальный конфиг безопасного Redis (/etc/redis/redis.conf):
bind 127.0.0.1
port 6379
requirepass ВашСложныйПарольМинимум32Символа
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command CONFIG "CONFIG_DISABLED"
maxmemory 512mb
maxmemory-policy allkeys-lru
Также важно: данные, которые попадают в очередь, сериализуются. В Laravel это делается через SerializesModels — Eloquent-модели сериализуются по ID и восстанавливаются в воркере. Но если ты сериализуешь сырые данные от пользователя — всегда валидируй их перед обработкой в воркере, а не только на входе. Воркер — это отдельный процесс, он не знает о HTTP-контексте и middleware валидации.
Практические рекомендации: с чего начать прямо сейчас
Если у тебя ещё нет очередей на проекте, вот мой приоритетный список действий. Первое — определи, какие операции у тебя сейчас выполняются синхронно в запросе пользователя и занимают больше 500 мс. Обычно это отправка email, внешние API-вызовы, генерация отчётов. Именно с них и начинай.
Второе — выбери стек. Для Laravel + Redis это 2 часа работы от нуля до работающей очереди. Для WordPress — установи Action Scheduler и начни с одной задачи. Не пытайся сразу выстроить идеальную архитектуру. Начни с малого, убедись что работает, потом расширяй.
Третье — сразу настрой мониторинг. Без него очереди создают ложное чувство безопасности. Ты думаешь, что всё работает, а на деле воркеры упали три дня назад. Laravel Horizon + Telegram-алерты на failed jobs — минимальный необходимый набор.
Четвёртое — тестируй сценарии отказов. Что будет, если Redis упадёт? Если внешний API вернёт 503? Если воркер убьёт OOM killer? Правильно настроенные retry-механизмы, dead letter queues и алерты — это не паранойя, это профессионализм. Можешь заодно почитать про настройку отказоустойчивости сайта в целом.
По моему опыту, внедрение очередей на среднестатистическом интернет-магазине даёт снижение среднего времени ответа сервера с 2–3 секунд до 200–400 мс на операциях с внешними интеграциями. PageSpeed Insights после этого обычно прыгает на 15–25 баллов только за счёт улучшения TTFB. Это одно из самых высокоэффективных вложений в производительность сайта, которое я знаю.
Если нужна помощь с настройкой очередей на вашем проекте — могу помочь в рамках поддержки Битрикс, поддержки WordPress или доработки сайта под ваши задачи.
Хотите ускорить обработку задач на вашем сайте?
Свяжитесь с нами, и мы настроим эффективную систему очередей задач, которая повысит производительность вашего проекта.