Python Celery vs arq: выбираем очередь задач для асинхронного бэкенда
Фоновые задачи — неотъемлемая часть любого бэкенда: отправка 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 остаётся стандартом.