Кратко:
Если приложению нужно отправить письмо, обработать изображение или выполнить запрос к внешнему API, такие операции обычно выносят в очередь задач (task queue). Благодаря этому сервер быстро отвечает пользователю, а сама задача выполняется в фоновом режиме.
Годами дефолтным выбором для этого в Python был Celery. Он проектировался до широкого распространения asyncio, и основная модель по-прежнему синхронная — async в Celery 5+ поддерживается частично.
TaskIQ — более молодой распределённый менеджер задач, изначально ориентированный на async/await. Он поддерживает и синхронные, и асинхронные задачи. В статье разберём, как устроен TaskIQ, чем он отличается от Celery на практике, что показали наши бенчмарки на TaskIQ 0.12.x, и когда переход имеет смысл.
TaskIQ — это современный Python task-queue, ориентированный на async/await. Он изначально заточен под asyncio и под фреймворки вроде FastAPI, поддерживает несколько брокеров сообщений (Redis, RabbitMQ, NATS, ZeroMQ) и в целом настраивается проще, чем Celery.
Основные возможности:
Схема выполнения задачи выглядит так:
Приложение → Broker → Worker → Result Backend

Приложение отправляет задачу через kicker (.kiq() — сокращение для .kicker().kiq(...)). Kicker позволяет менять broker, labels, task_id и timeout на лету. Перед отправкой обязателен await broker.startup() — без этого поведение не определено. Результат — через TaskiqTask.wait_result().
Далее задачу принимает Broker — посредник между приложением и воркером. Он помещает сообщение в очередь. Это можно сравнить с почтальоном, который доставляет письмо в почтовый ящик.
Worker отслеживает очередь, забирает новые задачи и выполняет связанный с ними код.
Если необходимо сохранить результат выполнения, он отправляется в Result Backend. Его настраивают отдельно от брокера. Чаще всего для этой роли используют Redis — он быстро настраивается, хорошо интегрируется с TaskIQ и обеспечивает быстрый доступ к результатам.
|
Тип задачи |
Примеры |
|
IO-bound |
Запросы к API, отправка email, работа с базой данных |
|
Network |
Интеграции с внешними сервисами, web scraping, загрузка файлов |
|
Memory-heavy |
Анализ больших объемов данных, обработка логов, генерация отчетов |
|
CPU-bound |
Машинное обучение, обработка изображений, сложные вычисления |
TaskIQ лучше всего подходит для IO-bound и сетевых задач. Благодаря асинхронной архитектуре воркер не простаивает во время ожидания ответа от API, базы данных или другого внешнего сервиса. Пока одна задача ожидает завершения операции ввода-вывода, event loop может переключиться на выполнение других задач, что позволяет эффективнее использовать ресурсы.
Memory-heavy задачи TaskIQ не «лечит» сам по себе: async не снижает потребление RAM. Такие задачи имеет смысл выносить в отдельные воркеры или process pool и контролировать лимиты памяти на уровне инфраструктуры.
С CPU-bound задачами ситуация иная. Вычисления упираются в GIL и могут блокировать event loop. Для sync CPU-задач используйте --use-process-pool; для async — отдельные воркеры/очереди, чтобы тяжёлые задачи не мешали IO-bound.
TaskIQ предоставляет все основные механизмы для построения системы фоновых задач: работу с разными брокерами сообщений, декларативное описание задач, управление воркерами и запуск задач по расписанию.
TaskIQ не привязывается к одному брокеру сообщений. Можно выбрать подходящий вариант в зависимости от требований проекта: скорости, надежности, архитектуры и инфраструктуры.
|
Брокер |
Пакет |
Особенности |
|
Redis |
taskiq-redis |
Redis Streams / ListQueue; часто как result backend |
|
NATS |
taskiq-nats |
Легковесный брокер с высокой скоростью работы |
|
RabbitMQ |
taskiq-aio-pika |
Надежная AMQP-архитектура, но требует более сложной настройки |
|
ZeroMQ |
встроенный |
P2P-модель и низкие задержки |
|
PostgreSQL |
taskiq-postgresql |
third-party |
|
Kafka |
taskiq-aio-kafka |
community, не в core docs |
|
SQS |
taskiq-aio-sqs |
third-party, AWS |
Настройка брокера в TaskIQ выполняется декларативно: разработчик импортирует нужный брокер и описывает его конфигурацию. После этого к нему можно подключить Result Backend для хранения результатов, изменить формат сообщений или добавить middleware для расширения логики обработки задач.
В TaskIQ задачи описываются декоратором @broker.task — подход похож на Celery. Декоратор регистрирует sync или async функцию как задачу. Sync выполняется в thread pool (IO) или process pool (CPU, флаг --use-process-pool).
```python
from taskiq_redis import ListQueueBroker, RedisAsyncResultBackend
broker = ListQueueBroker(url="redis://localhost:6379/0").with_result_backend(
RedisAsyncResultBackend(redis_url="redis://localhost:6379/1")
)
@broker.task
async def send_notification(user_id: int) -> None:
...
# await send_notification.kiq(user_id)
# CLI: taskiq worker myapp.broker:broker myapp.tasks --workers 2 --max-async-tasks 10
```При объявлении задачи можно указать дополнительные параметры:
После запуска задачи разработчик может дождаться результата выполнения, проверить текущий статус задачи и получить информацию о времени выполнения других метаданных.
Также TaskIQ поддерживает механизм shared broker. Он позволяет собрать задачи из разных частей проекта и использовать единый брокер без жёсткой привязки каждой задачи к конкретному экземпляру конфигурации.
Worker — это компонент, который получает задачи из очереди и выполняет их. TaskIQ предоставляет несколько CLI-параметров для управления его поведением.
|
Параметр |
Назначение |
|
|
Количество запущенных воркеров |
|
|
Максимальное количество задач, которые один воркер может выполнять одновременно |
|
|
Автоматический поиск задач в проекте |
|
|
Перезапуск воркера при изменении кода во время разработки |
|
|
Sync CPU-bound задачи в ProcessPoolExecutor |
|
|
Когда подтверждать задачу (when_saved по умолчанию) |
По умолчанию --fs-discover ищет задачи в файле tasks.py. Если задачи организованы внутри отдельных пакетов, шаблон поиска можно изменить вручную.
Параметр --reload особенно полезен в локальной разработке: при изменении кода перезапускается только нужный воркер, что ускоряет проверку изменений.
TaskIQ включает TaskiqScheduler: cron (LabelScheduleSource), interval schedules (schedule_by_interval) и dynamic scheduling через ListRedisScheduleSource. Запуск: taskiq scheduler module:scheduler.
Для работы планировщика необходимо указать:
Основные параметры Scheduler в таблице ниже.
|
Параметр |
Назначение |
|
|
Запуск планировщика задач |
|
|
Частота проверки расписания (по умолчанию 60 секунд) |
|
|
Пропуск первого запуска задачи после старта |
Важно: scheduler не выполняет задачи — только ставит их в очередь через broker. Worker выполняет. Запускайте один экземпляр scheduler; несколько инстансов могут продублировать задачи. Для timezone — поле cron_offset в расписании.
TaskIQ интегрируется с FastAPI через пакет taskiq-fastapi. Вызов taskiq_fastapi.init(broker, "myapp.main:app") подключает брокер к приложению.
Брокер нужно явно стартовать и останавливать. Предпочтительный способ — lifespan FastAPI:
@asynccontextmanager
async def lifespan(app: FastAPI):
await broker.startup()
yield
await broker.shutdown()
app = FastAPI(lifespan=lifespan)Помимо базового выполнения фоновых задач, TaskIQ предоставляет инструменты для построения более надежных и масштабируемых систем.
DI через TaskiqDepends() — по аналогии с FastAPI. Зависимости можно переиспользовать между HTTP-handlers и задачами (с учётом ограничений: Request в задаче — mock, не тот же объект, что в handler).
Например, таким способом можно подключать настройки приложения, клиентов внешних API или сервисы для работы с базой данных.
TaskiqState — shared state воркера: инициализируется в @broker.on_event(WORKER_STARTUP) (connection pools, clients). Из задачи доступен через Context (TaskiqDepends()).
Context также даёт requeue() и reject() — вернуть задачу в очередь или отбросить без повторного выполнения. Manual ack — через await context.ack() (требует AckableMessage у broker).
Smart Retry — middleware (SmartRetryMiddleware), подключается явно к брокеру. Автоматически возвращает неуспешные задачи в очередь с настраиваемым backoff и jitter. Не включён по умолчанию.
В TaskIQ можно настроить:
Использование jitter помогает избежать ситуации, когда после массового сбоя большое количество задач одновременно повторяет запросы и создает дополнительную нагрузку на систему.
Для простых сценариев есть SimpleRetryMiddleware — фиксированное число повторов без backoff. SmartRetryMiddleware — для production: jitter, exponential backoff, кастомный schedule source.
Pipeline позволяет создавать цепочки связанных задач, где результат одной операции передается в следующую.
Например:
Загрузка файла → обработка данных → сохранение результата → отправка уведомления
Дополнительно поддерживаются операции:
TaskIQ поддерживает Prometheus (taskiq[metrics]) и OpenTelemetry (с 0.12+). Через middleware отслеживаются:
Для проверки логики задач TaskIQ предоставляет in-memory broker. Он позволяет запускать задачи без подключения Redis, RabbitMQ или другого внешнего брокера. Это удобно для юнит-тестов: разработчик может проверить работу бизнес-логики, обработку ошибок и сценарии выполнения задач без дополнительной инфраструктуры.
Для тестов — InMemoryBroker: тот же интерфейс, без сети. Типичный паттерн: подмена broker по ENVIRONMENT=pytest. Задачу можно вызвать как обычную async-функцию или через .kiq() + wait_result(). Для fire-and-forget — await_inplace=True или broker.wait_all(). С FastAPI — taskiq_fastapi.populate_dependency_context().
Чтобы TaskIQ работал эффективно в production-среде, важно правильно организовать задачи, настроить обработку ошибок и контролировать состояние системы.
Разделяйте задачи по типу нагрузки — отдельный брокер для CPU-bound, отдельный для IO-bound (cpu_tasks.py, io_tasks.py).
Оборачивайте задачи в try-except и подключайте SmartRetryMiddleware. Для Redis используйте taskiq-redis (Streams/ListQueue) и настраивайте --ack-type (when_saved, when_executed, manual). Надёжность зависит от брокера и ack-политики, а не только от «Redis vs RabbitMQ».
Используйте async/await везде, где это возможно, а не только в отдельных задачах.
Настройте мониторинг — Prometheus или OpenTelemetry плюс структурированные логи.
Вызывайте await broker.startup() в клиенте и broker.shutdown() при остановке.
Настройте timeouts для долгих задач: @broker.task(timeout=30) или .kicker().with_labels(timeout=30).kiq().
Для sync CPU-bound — --use-process-pool; для sync IO — thread pool (default).
--max-prefetch имеет смысл только с брокерами, поддерживающими ack.
Установите uvloop — TaskIQ подхватит его автоматически, если пакет есть.

Несмотря на удобную архитектуру и современный подход, TaskIQ пока не является полностью зрелой заменой более старым системам фоновых задач. Перед использованием в крупных production-проектах стоит учитывать несколько особенностей.
TaskIQ активно развивается (документация на taskiq-python.github.io заметно выросла), но community и production-историй всё ещё меньше, чем у Celery (~2.2k vs ~28k stars на GitHub).
Для небольших и средних проектов это обычно не становится проблемой, но при построении критически важных систем стоит заранее оценить зрелость экосистемы.
У встроенного планировщика TaskIQ есть ограничение: он не хранит информацию о предыдущих запусках между перезапусками.
Например, если Scheduler был остановлен и запущен снова в течение короткого промежутка времени, уже выполненная задача может быть поставлена в очередь повторно.
Параметр --skip-first-run помогает избежать первого автоматического запуска после старта, но не проверяет, выполнялась ли задача ранее.
В системах, где важно гарантировать однократное выполнение запланированных задач, это необходимо учитывать. Например, в Celery есть Database Scheduler, который сохраняет состояние расписания в базе данных. В TaskIQ подобного встроенного механизма пока нет.
TaskIQ Scheduler рассчитан на работу в одном экземпляре. Запустить несколько планировщиков одновременно для повышения отказоустойчивости нельзя без дополнительной архитектуры вокруг него.
Для проектов с высокими требованиями к надёжности это означает необходимость самостоятельно продумывать механизм защиты от дублирования задач и контроля состояния планировщика.
Celery — один из самых популярных инструментов для выполнения фоновых задач в Python. Проект существует более 15 лет, имеет большое сообщество, множество интеграций и активно используется в production-системах, особенно в проектах на Django.
Главное преимущество Celery — зрелость. За годы развития вокруг него сформировалась большая экосистема: готовые решения для мониторинга, планирования задач, обработки ошибок и интеграции с различными брокерами сообщений.
TaskIQ появился позже и изначально создавался с учетом современных асинхронных подходов Python. Его основная идея — нативная работа с async/await и удобная интеграция с асинхронными фреймворками.
Основные отличия TaskIQ и Celery привёл в таблице ниже.
|
Критерий |
Celery |
TaskIQ |
|
Async/await |
Поддерживается частично, чаще используется синхронная модель |
Нативная поддержка асинхронного выполнения |
|
Интеграция с FastAPI |
Требует дополнительной настройки |
Есть готовая интеграция |
|
Dependency Injection |
Нет встроенного механизма |
Поддерживается через зависимости задач |
|
Retry-механизмы |
Базовые повторы, возможна настройка backoff |
Smart Retry с backoff и jitter |
|
Поддерживаемые брокеры |
Redis, RabbitMQ, Amazon SQS и другие |
Redis, RabbitMQ, NATS, ZeroMQ |
|
Экосистема |
Большое количество решений и документации |
Более молодая экосистема |
Мы прогнали сравнение на внутреннем стенде. Это не официальный benchmark — результат зависит от железа, брокера и настроек воркеров.
|
Тип нагрузки |
Преимущество TaskIQ в тестах |
|
IO-bound задачи |
около 32% |
|
CPU-bound задачи |
около 17% |
|
Смешанная нагрузка |
около 40% |
|
Высокая нагрузка (6000 задач) |
около 47% |
Наибольшая разница наблюдается в IO-bound сценариях: запросах к API, работе с внешними сервисами и другими операциями ожидания. Это связано с async-first архитектурой TaskIQ — пока одна задача ожидает ответа, воркер может выполнять другие операции.
Для CPU-bound задач преимущество меньше, поскольку такие операции ограничиваются особенностями Python и GIL (Global Interpreter Lock). Асинхронность не ускоряет сами вычисления, поэтому прирост зависит от конкретной архитектуры выполнения.
При этом результаты бенчмарков нельзя считать универсальными: итоговая производительность зависит от брокера, количества воркеров, настроек очередей и характера задач.
Отдельно стоит учитывать влияние дополнительных компонентов. Например, при использовании TaskIQ Admin производительность может снижаться из-за дополнительной нагрузки от мониторинга и middleware. Поэтому перед внедрением в production рекомендуется проводить собственные нагрузочные тесты.
Выбор между инструментами зависит не только от производительности, но и от архитектуры проекта.
|
Критерий |
Celery |
TaskIQ |
|
Async/await |
Ограниченная поддержка |
Нативная поддержка |
|
FastAPI |
Требует дополнительной настройки |
Простая интеграция |
|
Django |
Лучший выбор благодаря зрелой экосистеме |
Возможен, но не является основным сценарием |
|
Dependency Injection |
Нет встроенного механизма |
Поддерживается |
|
Retry |
Базовые механизмы повторов |
Smart Retry с backoff и jitter |
|
Экосистема |
Большое количество готовых решений |
Экосистема активно развивается |
|
Production-кейсы |
Очень много |
Меньше |
Если задачи уже вынесены в отдельные функции, технический переход умеренный, но это не «замена декоратора»: нужно сменить .delay() → .kiq(), настроить broker startup/shutdown, Celery Beat → TaskIQ Scheduler, retry через middleware. Миграция оправдана не всегда.
Django-проекты чаще всего остаются на Celery, так как он имеет глубокую интеграцию с этим фреймворком и большое количество готовых решений. Flask-приложения тоже часто используют такой инструмент.
Переход на TaskIQ имеет смысл, если текущая архитектура действительно ограничивает развитие проекта. Например:
Главный аргумент в пользу TaskIQ — современный асинхронный подход и удобная работа с типизированным Python-кодом. Но для команды, которая уже хорошо работает с Celery, этого может быть недостаточно для полноценного перехода.
Во многих проектах используется один и тот же брокер — например Redis или RabbitMQ. Поэтому широкий выбор брокеров TaskIQ не всегда становится решающим преимуществом.
Concurrency в Celery — это количество процессов. Workers в TaskIQ — асинхронные обработчики, которые могут делить между собой один event loop. Прямого соответствия между ними нет.
Если задачи оформлены как отдельные функции — перенос сводится к замене декоратора.
Формально да — TaskIQ Admin, но на практике он заметно просаживал производительность воркеров в тестах, поэтому надёжнее использовать Prometheus с дашбордами в Grafana.
Нет — у TaskIQ свои процессы и отдельный event loop, не пересекающийся с event loop FastAPI. Данные между ними передаются по ID, а не целыми моделями — так же, как это устроено и в Celery.
100 тыс.+ пользователей и 3000 часов разработки — Flutter MVP за 3 месяца
Тысячи скачиваний и шорт-лист Рейтинга Рунета — экосистема доставки еды на Flutter для Пхукета