La mayoría de los equipos no eligen una cola de tareas en Python — la heredan. Alguien arrancó el proyecto hace tres años, Celery era el nombre obvio, y ahora cada decisión de “¿deberíamos mandar esto a segundo plano?” recae por defecto en @app.task sin que nadie sopese realmente las alternativas. Eso no es necesariamente incorrecto, pero tampoco es una decisión — es inercia. Y la inercia es una mala razón para operar un message broker, un result backend y un proceso supervisor para un trabajo que RQ podría resolver con una fracción de las piezas móviles.
La versión honesta de esta comparación no es “Celery está inflado” ni “RQ es un juguete” — ambas posturas son incorrectas con demasiada frecuencia como para ser útiles. Celery, Dramatiq, RQ y arq resuelven el mismo problema básico — sacar trabajo del camino del request y ejecutarlo en otro lugar, de forma fiable — pero difieren claramente en el soporte de brokers, en cómo tratan async def, en cuánta superficie operativa exigen y en qué pasa cuando un worker muere a mitad de una tarea. Esas diferencias importan más que los números de benchmark, porque determinan qué se rompe a las 2 de la madrugada y qué tan difícil es depurarlo.
Aprenderás:
- Qué hace realmente una cola de tareas por dentro — broker, backend y worker, y por qué esa separación importa
- Una comparación entre Celery y Dramatiq en brokers, serialización y diseño de reintentos
- Dónde se sitúa cada una de las cuatro colas en soporte nativo de
asyncio - El peso operativo real de ejecutar cada una en producción — no solo
pip install - Cómo maneja cada sistema los crashes de workers, la redelivery, y la entrega at-least-once frente a at-most-once
- Una matriz de decisión para elegir sin una semana de investigación
- En qué consiste realmente migrar entre colas una vez que superas tu primera elección
Tabla de Contenidos
- Lo Básico
- Los Cuatro Contendientes de un Vistazo
- Soporte de Brokers
- Soporte Async: Dónde Celery se Queda Corto
- Peso Operativo
- Semántica de Fallos
- Matriz de Decisión
- Notas sobre Migración
- Hacia Dónde Ir Desde Aquí
Lo Básico
Una cola de tareas tiene tres piezas móviles, sin importar qué librería elijas, y confundirlas es la fuente de la mayoría de los errores tempranos.
El broker es el transporte de mensajes — normalmente Redis o RabbitMQ (o SQS, si estás en AWS). Mantiene los mensajes de tarea serializados hasta que un worker los recoge. El result backend es un almacén separado — a menudo también Redis, pero no obligatorio — que guarda valores de retorno y estado de la tarea si necesitas consultarla más tarde. Y el worker es el proceso que saca mensajes del broker y ejecuta tu función. Los productores (tu aplicación web) y los workers son procesos separados, normalmente en máquinas distintas, que se comunican únicamente a través del broker.
La tarea en sí es solo una referencia a una función más argumentos serializados. Cuando llamas a my_task.delay(user_id=42), nada se ejecuta de forma síncrona — se serializa un mensaje (JSON por defecto en la mayoría de estas librerías) y se empuja al broker. Un worker en algún lugar, posiblemente en otro host, lo deserializa, busca la función correspondiente y la llama. Este desacoplamiento es todo el punto: tu request web devuelve una respuesta en 50ms en lugar de bloquearse en una exportación de PDF de 30 segundos.
Donde las cuatro librerías realmente divergen es en todo lo que viene después de esa forma básica — con qué brokers hablan, si entienden async def, cuántos procesos extra tienes que vigilar, y qué garantías ofrecen cuando un worker muere por OOM a mitad de una tarea.
Los Cuatro Contendientes de un Vistazo
| Celery | Dramatiq | RQ | arq | |
|---|---|---|---|---|
| Brokers | Redis, RabbitMQ, SQS, más | Redis, RabbitMQ | Solo Redis | Solo Redis |
| Async nativo | No (requiere workaround) | No | No | Sí, async def de primera clase |
| Result backend | Opcional, varios backends | Opcional (Redis) | Integrado vía Redis | Integrado vía Redis |
| Superficie de configuración | Grande | Pequeña-mediana | Pequeña | Pequeña |
| Retry/backoff | Integrado, muy configurable | Integrado, basado en decoradores | Manual o vía rq-scheduler | Integrado |
| Caso de uso típico | Pipelines grandes, poliglotas, complejas | Apps más simples que quieren el poder de Celery | Apps pequeñas-medianas, setup rápido | Apps async-nativas de FastAPI/asyncio |
Celery es el incumbente por una razón — lleva desde 2009 endurecido en producción, y su ecosistema de plugins (Flower para monitoreo, Beat para scheduling, Canvas para composición de workflows) no tiene un equivalente real. Dramatiq es lo que muchos equipos eligen cuando quieren las garantías de fiabilidad de Celery sin su superficie de configuración. RQ cambia funcionalidad por simplicidad — es la cola que puedes explicarle a alguien nuevo en cinco minutos. arq es el bicho raro: construido específicamente para código base asyncio donde envolver cada tarea en run_in_executor se siente como pelear contra el framework.
Si estás mapeando funcionalidades en una comparación más amplia de colas de tareas en Python, la bifurcación en el camino casi siempre es la misma: ¿necesitas el ecosistema de Celery, o estás pagando un impuesto por funcionalidades que nunca vas a tocar?
Soporte de Brokers
Este es el primer filtro, porque es el menos negociable. RQ y arq son exclusivamente Redis, punto — nada de RabbitMQ, nada de SQS. Es una decisión deliberada de simplicidad: menos abstracciones de broker que mantener, pero también significa que si ya estás ejecutando RabbitMQ para otros servicios, terminas añadiendo Redis solo para la cola de tareas.
Celery soporta Redis, RabbitMQ y Amazon SQS de fábrica, más transportes de la comunidad para unos cuantos más. Dramatiq soporta oficialmente RabbitMQ y Redis, con RabbitMQ como su broker históricamente preferido porque encaja de forma más directa con el modelo nativo de acknowledgment de AMQP.
La implicación práctica: si necesitas RabbitMQ específicamente — por su flexibilidad de routing, sus primitivas de dead-lettering más maduras, o porque ya es tu bus de mensajes para tráfico que no es de tareas —, Celery y Dramatiq son tus únicas dos opciones. Si de todos modos solo usas Redis (algo común; Redis suele estar ya en el stack para caching), las cuatro están sobre la mesa y la decisión se desplaza a los demás ejes.
# Celery: broker is just a URL
from celery import Celery
app = Celery("tasks", broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1")
@app.task
def send_email(to: str, subject: str) -> None:
...
# Dramatiq: broker is a module-level object
import dramatiq
from dramatiq.brokers.redis import RedisBroker
dramatiq.set_broker(RedisBroker(host="localhost"))
@dramatiq.actor
def send_email(to: str, subject: str) -> None:
...
Soporte Async: Dónde Celery se Queda Corto
Aquí es donde arrancan de verdad la mayoría de los argumentos a favor de alternativas a Celery. Celery es una década anterior al asyncio moderno, y su modelo de workers es fundamentalmente síncrono — ejecuta tareas en un pool de procesos o hilos del sistema operativo (prefork, threads, eventlet, gevent), no en un event loop. Puedes llamar código async def desde dentro de una tarea de Celery, pero tienes que tender el puente tú mismo con asyncio.run(), y ese costo de puente — levantar un event loop nuevo por cada invocación de tarea — añade overhead real si tus tareas hacen muchas llamadas de I/O awaited pequeñas en lugar de una grande.
Dramatiq y RQ tienen la misma limitación: ambos ejecutan tareas en procesos/hilos worker sin soporte de primera clase para async def integrado en el camino de ejecución del actor/job.
arq es la excepción, y es la razón misma de su existencia. Está construido nativamente sobre asyncio — el worker en sí es una corrutina, y tus funciones de tarea son async def por diseño:
# arq: tasks are coroutines, no bridging needed
from arq import create_pool
from arq.connections import RedisSettings
async def send_email(ctx, to: str, subject: str) -> None:
async with httpx.AsyncClient() as client:
await client.post("https://api.mailer.example/send", json={"to": to})
class WorkerSettings:
functions = [send_email]
redis_settings = RedisSettings(host="localhost")
Si tu aplicación ya es asyncio-nativa — FastAPI, aiohttp, la mayoría de los stacks web async modernos —, arq elimina toda una categoría de bugs de “por qué esto está bloqueando el event loop”, porque no hay frontera sync/async con la que tropezar. Si tu app es Django o Flask corriendo de forma síncrona, esta ventaja prácticamente desaparece; el modelo síncrono de workers de Celery encaja perfectamente con el modelo síncrono de tu app.
Peso Operativo
“Peso operativo” significa: cuántos procesos ejecutas, cuánto tienes que configurar para lograr un comportamiento de nivel producción, y cuánto necesitas entender para depurarlo a las 3 de la madrugada.
Celery es, con diferencia, el más pesado. Un despliegue de Celery en producción típicamente implica: un broker (Redis o RabbitMQ), opcionalmente un result backend separado, un proceso celery worker (a menudo varios, ajustados según el tipo de pool de concurrencia), celery beat si necesitas tareas programadas, y a menudo Flower para monitoreo. La superficie de configuración es enorme — routing de tareas, prioridades de cola, acks_late, multiplicadores de prefetch, connection pooling, elección de serializador — todo ello poderoso, y casi todo con una trampa incorporada si no lees la documentación antes.
Dramatiq recorta esto deliberadamente. No hay un proceso beat separado (recurrirías a APScheduler o a un enqueue disparado por cron), no hay result backend a menos que lo actives explícitamente, y su sistema de middleware reemplaza la mayor parte de lo que Celery hace con señales y flags de configuración. Es la opción de “80% de la fiabilidad de Celery, 30% de la configuración”.
RQ es la más ligera de las cuatro para operar de verdad. Una instancia de Redis, un comando rq worker, listo. Su dashboard (rq-dashboard) es opcional y trivial de añadir. El costo son menos garantías incorporadas — reintentos nativos solo desde hace relativamente poco en su historia, ningún sistema de routing/prioridad de primera clase más allá de colas con nombre.
arq se sitúa operativamente cerca de RQ — una única dependencia de Redis, un único proceso worker iniciado con arq worker.WorkerSettings —, pero con reintentos y scheduling tipo cron integrados mediante decoradores cron(), así que tampoco necesita un proceso beat separado.
Si eres un equipo pequeño, el eje del peso operativo suele dominar sobre cualquier otra consideración. Una cola que puedas explicarte por completo a ti mismo durante un incidente le gana a una cola más “correcta” cuyos modos de fallo requieren leer el código fuente para entenderlos.
Monitoreo y Observabilidad
El peso operativo también se nota en cómo te enteras de que algo va mal. Flower de Celery te da de fábrica un dashboard web de tareas activas, estado de workers y throughput histórico — genuinamente útil, pero es otro proceso que desplegar, asegurar detrás de auth y mantener corriendo. Dramatiq se apoya en cambio en su sistema de middleware: una middleware de Prometheus viene incluida con la librería y expone métricas que puedes recolectar con tu stack de monitoreo existente en lugar de un dashboard a medida. El rq-dashboard de RQ es una app Flask ligera y opcional que lee el estado de los jobs directamente desde Redis — más cerca de una herramienta de depuración que de una plataforma completa de observabilidad. arq no trae ningún dashboard; se espera que inspecciones el estado del job programáticamente (Job.status()) o que construyas tu propia vista sobre las claves de Redis que mantiene, lo cual encaja con su filosofía de huella mínima, pero significa que estás por tu cuenta para cualquier cosa más allá de health checks básicos.
Ninguna de estas opciones está “resuelta” como sí lo está, por ejemplo, el tracing de requests HTTP — cuenta con escribir al menos algo de código pegamento para llevar los fallos de tareas al sistema de alertas que tu equipo ya usa, sin importar qué cola elijas.
Semántica de Fallos
Este es el eje que la gente subestima hasta que le cuesta datos. Todas estas librerías por defecto usan entrega at-least-once, no exactly-once — un worker puede fallar después de terminar una tarea pero antes de confirmarla, y el broker volverá a entregar esa tarea a otro worker. Si tus tareas no están escritas para tolerar ejecutarse dos veces, esto acabará mordiéndote sin importar qué librería elijas; consulta cómo hacer idempotentes las tareas de Celery para el patrón que resuelve esto.
Celery confirma las tareas antes de ejecutarlas por defecto (acks_early), lo cual en realidad es una garantía más débil — un fallo a mitad de la tarea significa que el mensaje desaparece y nunca se reintenta. Poner task_acks_late = True invierte esto para confirmar solo tras completarse con éxito, cambiando “nunca perder una tarea” por “quizá ejecutar una tarea dos veces”. El sistema de reintentos de Celery es configurable por separado, por tarea, vía autoretry_for, retry_backoff y max_retries.
Dramatiq confirma tarde por defecto — los mensajes no se eliminan del broker hasta que el actor retorna con éxito —, lo cual es el default más seguro de fábrica, aunque significa que el riesgo de ejecución doble está presente desde el primer día a menos que tus tareas sean idempotentes.
RQ requiere configuración explícita para los reintentos (Retry(max=3, interval=[10, 30, 60]), pasado al momento del enqueue) y no tiene acknowledgment tardío en el mismo sentido — el job de un worker caído puede detectarse vía su mecanismo de monitoreo/heartbeat y volver a encolarse, pero esto es más grueso que el modelo de ack por mensaje de Celery o Dramatiq.
arq rastrea los jobs en curso y los vuelve a encolar si un worker desaparece más allá de su timeout de job, con conteo de reintentos y backoff configurables por función.
Ninguna de las cuatro te da semántica exactly-once — esa garantía esencialmente no existe para ejecución distribuida de tareas sin coordinación transaccional entre el broker y tus efectos secundarios, algo que ninguna de estas herramientas intenta. Diseña tus tareas alrededor de at-least-once desde el principio en lugar de tratar la ejecución duplicada como un caso extremo raro; no es rara, solo es lo bastante infrecuente como para olvidarla entre incidentes.
Matriz de Decisión
Una heurística aproximada, en orden de cuán a menudo aplica realmente:
- Necesitas RabbitMQ, SQS o composición compleja de workflows (chains, chords, groups) → Celery. Nada más en esta lista se acerca a Canvas para construir pipelines de varios pasos con semántica de fan-out/fan-in.
- Quieres fiabilidad al estilo Celery con una huella menor y estás sobre Redis o RabbitMQ → Dramatiq. Buen término medio para equipos a los que la superficie de configuración de Celery realmente les resulta dolorosa pero que aun así quieren reintentos y middleware de nivel producción.
- Quieres el setup más pequeño posible y Redis ya está en tu stack → RQ. Mejor opción para apps pequeñas-medianas donde “una instancia de Redis, un comando de worker” es toda la historia que quieres contar.
- Tu app es
asyncio-nativa (FastAPI, aiohttp) y tus tareas son corrutinas ligadas a I/O → arq. Evita por completo el impuesto de puentear sync/async; mal encaje si tu app es síncrona de punta a punta.
El tamaño del equipo y la infraestructura existente suelen decidir esto más rápido que cualquier comparación de funcionalidades. Un equipo de cinco personas ejecutando Django sobre una única instancia de Redis gana casi nada del soporte de RabbitMQ de Celery o de los workflows de Canvas — gana configuración que nunca usará y una superficie de depuración que no necesitaba invitar.
Notas sobre Migración
Moverse entre estas librerías más adelante es más mecánico de lo que la gente espera, porque la forma subyacente — función más argumentos serializados más un broker — es la misma en todas partes. El trabajo real tiene tres partes: reescribir las definiciones de tareas (la sintaxis de decoradores difiere, pero los cuerpos de las funciones rara vez necesitan cambiar), reimplementar cualquier funcionalidad específica de Celery de la que dependieras (los workflows de Canvas no tienen un equivalente directo en RQ o arq — tendrías que construir la lógica de encadenamiento a mano), y — la parte que realmente causa incidentes — auditar cada tarea en busca de idempotencia antes de migrar, porque un cambio de broker es exactamente el tipo de cambio que causa doble entrega durante la ventana de corte.
El patrón más seguro es una ejecución en paralelo: levantar la cola nueva junto a la antigua, escribir en ambas (“dual-write”) los tipos de tarea nuevos mientras las tareas en curso se van drenando del broker antiguo, y hacer el corte cola por cola en lugar de todo de una vez. Migrar directamente los mensajes ya encolados pero aún no ejecutados de un broker en vivo (de Celery a RQ, por ejemplo) no está soportado por ninguna de estas librerías — dejas que la cola antigua se vacíe de forma natural y empiezas de cero en la nueva, lo que significa elegir una ventana de bajo tráfico en lugar de esperar un corte sin downtime.
Hacia Dónde Ir Desde Aquí
Elegir la cola es el 20% fácil de este problema — el otro 80% es lograr que las tareas se comporten correctamente una vez que corren en producción bajo condiciones reales de fallo. Tres lecturas complementarias que vale la pena hacer sin importar con qué librería te quedes:
- Cómo hacer idempotentes las tareas de Celery — dado que todas las colas aquí entregan por defecto at-least-once, este es el patrón que evita que una tarea reentregada le cobre dos veces a un cliente o envíe un email duplicado.
- Reintentos, backoff y dead-letter queues en Celery — cómo distinguir un fallo transitorio de uno permanente, y adónde deben ir las tareas una vez que han fallado de verdad para siempre.
- Celery + Redis: trampas de las que nadie te avisa — redelivery por visibility timeout, acaparamiento de prefetch, y el ajuste
maxmemory-policyque devora silenciosamente tareas encoladas.
Para Cerrar
No hay una respuesta universalmente correcta enterrada en un benchmark de Celery contra Dramatiq, y nunca la iba a haber — la elección correcta depende de con qué broker ya estás comprometido, de si tu app es síncrona o async-nativa, y de cuánta superficie operativa quiere realmente poseer tu equipo. Celery se gana su complejidad cuando necesitas RabbitMQ, SQS o composición de workflows genuina. Dramatiq se gana su lugar cuando quieres la mayor parte de esa fiabilidad sin la mayor parte de esa configuración. RQ gana cuando la simplicidad es la funcionalidad. arq gana cuando tu app ya está construida sobre asyncio y puentear ejecución síncrona de tareas hacia ella sería la complejidad real que intentas evitar.
Elige según lo que ya estás ejecutando y cómo necesitan comportarse realmente tus tareas ante un fallo — no según qué nombre aparece primero en un resultado de búsqueda.

