Содержание

Очередь задач нужна, чтобы всё медленное и ненадёжное — отправка писем, генерация отчётов, обработка файлов, вызовы внешних API — выполнялось в фоне, а пользователь получал ответ сразу. Веб-запрос ставит задачу в очередь за миллисекунды, а отдельные процессы-обработчики выполняют её, повторяют при сбое и не дают одной медленной операции положить весь сайт.

Разберём, как устроены очереди, что в них выносить и как не потерять задачи.

Зачем веб-приложению очередь задач?

Очередь задач нужна веб-приложению, чтобы отделить быстрый ответ пользователю от долгой или ненадёжной работы. Пока всё выполняется внутри запроса, любая задержка внешнего сервиса превращается в задержку для пользователя, а любой сбой — в ошибку на экране.

Что обычно выносят в очередь:

  • отправку писем, SMS и push-уведомлений;
  • обработку загруженных файлов — изображений, видео, документов;
  • генерацию отчётов и выгрузок;
  • вызовы внешних API: платёжных систем, CRM, служб доставки;
  • обращения к AI-моделям, которые отвечают секундами;
  • пересчёт агрегатов и индексов для поиска.

Правило простое: если операция не нужна для ответа пользователю прямо сейчас, её место в очереди.

Как устроена очередь задач?

Очередь задач состоит из трёх частей: приложение ставит задачу, брокер хранит её, а процессы-обработчики (воркеры) забирают и выполняют. Между постановкой и выполнением задача живёт в хранилище и переживает перезапуск приложения.

Часть Что делает Типичные варианты
Продюсер Кладёт задачу с данными в очередь Код приложения: контроллер, событие, планировщик
Брокер Хранит задачи до выполнения Redis, база данных, RabbitMQ, облачные очереди
Воркер Забирает задачу и выполняет её Отдельные процессы, масштабируемые по нагрузке
Хранилище неудачных задач Сохраняет задачи, которые не удалось выполнить Таблица failed jobs, отдельная очередь
Мониторинг Показывает длину очередей и ошибки Панели фреймворка, метрики и алерты

В большинстве фреймворков очереди есть из коробки: Laravel Queues, BullMQ для Node.js, Celery для Python. Для старта обычно хватает Redis или даже самой базы данных в роли брокера.

Монитор с программным кодом Фото: Unsplash

Как написать надёжную фоновую задачу?

Надёжная фоновая задача должна быть идемпотентной, ограниченной по времени и повторяемой: её можно безопасно выполнить дважды, она не зависает навсегда и при временном сбое повторяется с паузой. Большинство проблем с очередями возникает из-за нарушения одного из этих правил.

Пример задачи в Laravel, которая отправляет заказ в CRM:

final class SendOrderToCrm implements ShouldQueue
{
    use Queueable;

    public int $tries = 5;
    public int $timeout = 30;

    public function __construct(public int $orderId) {}

    /** Пауза между попытками растёт: 10 с, 1 мин, 5 мин… */
    public function backoff(): array
    {
        return [10, 60, 300, 900];
    }

    public function handle(CrmClient $crm): void
    {
        $order = Order::findOrFail($this->orderId);

        // Идемпотентность: повторный запуск не создаст дубль в CRM
        if ($order->crm_id !== null) {
            return;
        }

        $order->update(['crm_id' => $crm->createDeal($order)]);
    }
}

// В контроллере — мгновенно, пользователь не ждёт CRM
SendOrderToCrm::dispatch($order->id);

Правила, которые экономят нервы

  1. Передавайте в задачу идентификатор, а не весь объект: к моменту выполнения данные могут измениться.
  2. Ставьте задачу в очередь после коммита транзакции, иначе воркер может не найти ещё не сохранённую запись.
  3. Разделяйте очереди по приоритету: письма о сбросе пароля не должны ждать за тысячей отчётов.
  4. Ограничивайте время выполнения и число попыток — бесконечные повторы забивают очередь.

Как не потерять задачи и заметить сбой?

Задачи не теряются, если брокер хранит их надёжно, неудачные попытки сохраняются отдельно, а длина очереди и число ошибок отслеживаются с оповещениями. Самая опасная ситуация — тихая: воркеры упали, задачи копятся, а заметили это только по жалобам клиентов.

Что настроить:

  • автоматический перезапуск воркеров через менеджер процессов или оркестратор;
  • перезапуск воркеров при каждом деплое, чтобы они подхватили новый код;
  • хранилище неудачных задач и регулярный разбор причин;
  • алерт на длину очереди и на время ожидания задачи;
  • алерт на рост числа ошибок в конкретном типе задач.

Когда нужен отдельный брокер сообщений?

Отдельный брокер вроде RabbitMQ или Kafka нужен, когда очередь становится средством обмена между несколькими сервисами или объём событий измеряется тысячами в секунду. Для фоновых задач внутри одного приложения чаще хватает Redis или базы данных.

Признаки, что пора смотреть на брокер:

  • задачи ставит одно приложение, а выполняет другое, написанное на другом стеке;
  • одно событие должны получить несколько независимых потребителей;
  • нужна гарантированная доставка с подтверждениями и сложной маршрутизацией;
  • нужно хранить поток событий и перечитывать его заново.

Это решение того же рода, что и выбор между монолитом и сервисами, — подробнее в статье монолит или микросервисы. Для вызовов AI-моделей очередь особенно полезна; об этом — в статье как встроить LLM в продукт.

Коротко

  • В очередь выносят всё, что не нужно для ответа пользователю прямо сейчас.
  • Очередь — это продюсер, брокер, воркеры, хранилище неудачных задач и мониторинг.
  • Задача должна быть идемпотентной, ограниченной по времени и повторяемой с паузой.
  • Ставьте задачу после коммита, разделяйте очереди по приоритету и следите за их длиной.
  • Отдельный брокер нужен для обмена между сервисами и больших потоков событий.

Частые вопросы

  • Можно ли использовать базу данных вместо Redis для очередей?

    Да, для небольшой нагрузки это нормальный вариант: не нужна дополнительная инфраструктура, а задачи хранятся надёжно. При росте числа задач Redis или специализированный брокер работают быстрее и меньше нагружают базу.

  • Сколько воркеров нужно запускать?

    Столько, чтобы очередь не росла в пиковые часы. Начинают с нескольких процессов и масштабируют по длине очереди и времени ожидания задач; тяжёлым задачам лучше выделить отдельные воркеры.

  • Чем очередь отличается от планировщика задач (cron)?

    Планировщик запускает задачи по расписанию, а очередь — по событию, когда задачу поставили. Часто они работают вместе: планировщик раз в час ставит в очередь задачи на пересчёт, а воркеры их выполняют.