lenec ru

← все посты

Python Celery vs arq: выбираем очередь задач для асинхронного бэкенда

11K

Фоновые задачи — неотъемлемая часть любого бэкенда: отправка email, обработка изображений, генерация отчётов. В Python-экосистеме два основных подхода: проверенный временем Celery и минималистичный arq на asyncio. Разбираем архитектуру, сравниваем производительность и определяем, когда какой инструмент уместен.

Celery — архитектура и компоненты

Celery — зрелый фреймворк (с 2009 года) для распределённых очередей задач. Архитектура:

  • Broker — транспорт сообщений (Redis, RabbitMQ, Amazon SQS).
  • Worker — процесс, выполняющий задачи. Pool: prefork (multiprocessing), eventlet, gevent.
  • Beat — планировщик периодических задач (cron-like).
  • Result backend — хранение результатов (Redis, PostgreSQL, MongoDB).
# celery_app.py
from celery import Celery

app = Celery('myapp', broker='redis://localhost:6379/0',
             backend='redis://localhost:6379/1')

@app.task(bind=True, max_retries=3, default_retry_delay=60)
def send_email(self, user_id, template):
    try:
        result = email_service.send(user_id, template)
        return {'status': 'sent', 'message_id': result.id}
    except ConnectionError as exc:
        raise self.retry(exc=exc)

# Вызов
send_email.delay(user_id=42, template='welcome')
send_email.apply_async(args=[42, 'welcome'], countdown=300)  # через 5 мин

Запуск воркера и beat:

celery -A celery_app worker --pool=prefork --concurrency=4
celery -A celery_app beat --scheduler=redbeat.RedBeatScheduler

arq — asyncio + Redis

arq — легковесная библиотека от автора pydantic. Только Redis как брокер, только asyncio, минимум абстракций:

# tasks.py
import asyncio
from arq import create_pool
from arq.connections import RedisSettings

async def send_email(ctx, user_id: int, template: str):
    """ctx — словарь с redis-пулом и другими зависимостями."""
    result = await email_service.async_send(user_id, template)
    return {'status': 'sent', 'message_id': result.id}

class WorkerSettings:
    functions = [send_email]
    redis_settings = RedisSettings(host='localhost', port=6379)
    max_jobs = 50
    job_timeout = 300
    retry_jobs = True
    max_tries = 3

# Постановка задачи
async def enqueue():
    redis = await create_pool(RedisSettings())
    await redis.enqueue_job('send_email', user_id=42, template='welcome')
    await redis.enqueue_job('send_email', user_id=42, template='welcome',
                            _defer_by=300)  # через 5 мин

Запуск: arq tasks.WorkerSettings

Периодические задачи — через cron прямо в настройках:

from arq.cron import cron

class WorkerSettings:
    functions = [send_email]
    cron_jobs = [
        cron(cleanup_old_sessions, hour=3, minute=0),
        cron(generate_daily_report, hour=8, minute=30)
    ]

Сравнение

Критерий          | Celery              | arq
────────────────────────────────────────────────────────
Broker            | Redis, RabbitMQ,    | Только Redis
                  | SQS, Kafka          |
Async-native      | Нет (eventlet/      | Да (asyncio)
                  | gevent как workaround)|
Настройка         | ~50 строк + beat    | ~20 строк
Retry             | Встроенный, гибкий  | Встроенный, базовый
Scheduling        | Beat + crontab      | Встроенный cron
Мониторинг        | Flower, Prometheus  | Минимальный (arq CLI)
Result backend    | Redis, DB, S3       | Redis
Routing/приоритеты| Да (queues, priority)| Нет
Canvas (chains,   | Да                  | Нет
groups, chords)   |                     |
Зависимости       | 12+ пакетов         | 3 пакета

Бенчмарки

Тест: 10 000 задач (sleep 10ms), Redis localhost, 4 воркера (Python 3.12, Linux):

Метрика              | Celery (prefork) | Celery (gevent) | arq
─────────────────────────────────────────────────────────────────
Throughput (tasks/s) | 1 200            | 3 800           | 5 400
Latency p50          | 12 ms            | 8 ms            | 4 ms
Latency p99          | 45 ms            | 22 ms           | 11 ms
RAM на воркер        | 180 MB           | 95 MB           | 45 MB
Время старта         | 2.1 s            | 1.8 s           | 0.3 s

arq выигрывает по throughput и памяти за счёт asyncio event loop — один процесс обрабатывает тысячи задач конкурентно без форков. Celery с prefork создаёт отдельный процесс на каждый слот concurrency.

Но: если задачи CPU-bound (обработка изображений, ML-инференс), prefork-пул Celery эффективнее — asyncio не параллелит CPU-работу.

Когда что выбирать

Celery — правильный выбор когда:

  • Нужен RabbitMQ или SQS как брокер (требования инфраструктуры).
  • Сложные workflow: цепочки задач (chain), группы (group), аккорды (chord).
  • CPU-bound задачи — prefork-пул реально параллелит работу.
  • Нужен продвинутый мониторинг (Flower) и routing по очередям.
  • Большая команда — Celery знают все, документация обширная.

arq — правильный выбор когда:

  • Проект уже на asyncio (FastAPI, aiohttp, Starlette).
  • Задачи I/O-bound: HTTP-запросы, работа с БД, отправка уведомлений.
  • Важна минимальная latency и потребление памяти.
  • Redis уже в стеке, дополнительный брокер не нужен.
  • Маленькая команда, простые workflow без canvas.

Для async-first бэкенда на FastAPI с Redis arq — естественный выбор: меньше кода, быстрее старт, ниже overhead. Для enterprise-систем с RabbitMQ, сложной маршрутизацией и CPU-задачами Celery остаётся стандартом.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.