Blogs / Python Task Queue wählen: Celery, Dramatiq, RQ, arq

Python Task Queue wählen: Celery, Dramatiq, RQ, arq

Veröffentlicht
8. September 2026
Autor
Faizan Nadeem
Tags
Python Celery Task Queues Backend Development
Ein fahrerloses Transportfahrzeug trägt gestapelte Regale mit Bauteilen durch eine Fabrikhalle, im Hintergrund stehen weitere beladene Regale bereit
Foto von Homa Appliances auf Unsplash

Die meisten Teams wählen keine Python Task Queue — sie erben eine. Irgendjemand hat das Projekt vor drei Jahren aufgesetzt, Celery war der naheliegende Name, und jetzt landet jede „sollten wir das in den Hintergrund verlagern?“-Entscheidung standardmäßig bei @app.task, ohne dass jemand die Alternativen tatsächlich abwägt. Das ist nicht zwangsläufig falsch, aber es ist auch keine Entscheidung — es ist Trägheit. Und Trägheit ist ein schlechter Grund, einen Message-Broker, ein Result-Backend und einen Supervisor-Prozess für einen Job zu betreiben, den RQ mit einem Bruchteil der beweglichen Teile erledigen könnte.

Die ehrliche Version dieses Vergleichs lautet nicht „Celery ist aufgebläht“ oder „RQ ist nur Spielzeug“ — beide Einschätzungen sind zu oft falsch, um nützlich zu sein. Celery, Dramatiq, RQ und arq lösen dasselbe Grundproblem — Arbeit vom Request-Pfad nehmen und zuverlässig woanders ausführen —, aber sie unterscheiden sich stark darin, welche Broker sie unterstützen, wie sie mit async def umgehen, wie viel operative Fläche sie verlangen und was passiert, wenn ein Worker mitten in einem Task stirbt. Diese Unterschiede zählen mehr als Benchmark-Zahlen, weil sie bestimmen, was um 2 Uhr nachts kaputtgeht und wie schwer es zu debuggen ist.

Du lernst:

  • Was eine Task Queue unter der Haube wirklich tut — Broker, Backend und Worker, und warum diese Trennung wichtig ist
  • Einen Vergleich zwischen Celery und Dramatiq entlang Brokern, Serialisierung und Retry-Design
  • Wo jede der vier Queues bei nativer asyncio-Unterstützung steht
  • Den realen operativen Aufwand, jede davon in Produktion zu betreiben — nicht nur pip install
  • Wie jedes System mit Worker-Abstürzen, Redelivery und at-least-once vs. at-most-once Delivery umgeht
  • Eine Entscheidungsmatrix, die dir die Wahl ohne eine Woche Recherche erlaubt
  • Was eine Migration zwischen Queues tatsächlich bedeutet, sobald du deine erste Wahl überwachsen hast

Inhaltsverzeichnis

  1. Die Grundlagen
  2. Die vier Kandidaten im Überblick
  3. Broker-Unterstützung
  4. Async-Unterstützung: Wo Celery zu kurz kommt
  5. Operativer Aufwand
  6. Failure-Semantik
  7. Entscheidungsmatrix
  8. Hinweise zur Migration
  9. Wie es von hier weitergeht

Die Grundlagen

Eine Task Queue hat drei bewegliche Teile, egal welche Library man wählt, und sie zu verwechseln ist die Quelle der meisten Anfängerfehler.

Der Broker ist der Nachrichtentransport — meist Redis oder RabbitMQ (oder SQS, wenn man auf AWS ist). Er hält serialisierte Task-Nachrichten fest, bis ein Worker sie abholt. Das Result-Backend ist ein separater Speicher — oft ebenfalls Redis, aber nicht zwingend —, der Rückgabewerte und Task-Status hält, falls man einen Task später tatsächlich nachschlagen muss. Und der Worker ist der Prozess, der Nachrichten vom Broker holt und die Funktion ausführt. Producer (die Web-App) und Worker sind separate Prozesse, meist auf separaten Maschinen, die nur über den Broker kommunizieren.

Der Task selbst ist nur eine Funktionsreferenz plus serialisierte Argumente. Wenn man my_task.delay(user_id=42) aufruft, läuft nichts synchron ab — eine Nachricht wird serialisiert (bei den meisten dieser Libraries standardmäßig JSON) und auf den Broker gelegt. Irgendwo, möglicherweise auf einem anderen Host, deserialisiert ein Worker sie, schlägt die passende Funktion nach und ruft sie auf. Genau diese Entkopplung ist der ganze Sinn: Der Web-Request kehrt in 50ms zurück, statt für einen 30-Sekunden-PDF-Export zu blockieren.

Wo sich die vier Libraries tatsächlich unterscheiden, ist alles, was auf diese Grundform folgt — mit welchen Brokern sie sprechen, ob sie async def verstehen, wie viele zusätzliche Prozesse man beaufsichtigen muss, und welche Garantien sie geben, wenn ein Worker mitten im Task per OOM gekillt wird.

Die vier Kandidaten im Überblick

CeleryDramatiqRQarq
BrokerRedis, RabbitMQ, SQS, mehrRedis, RabbitMQNur RedisNur Redis
Natives AsyncNein (Workaround nötig)NeinNeinJa, async def erstklassig
Result-BackendOptional, mehrere BackendsOptional (Redis)Eingebaut über RedisEingebaut über Redis
KonfigurationsflächeGroßKlein-mittelKleinKlein
Retry/BackoffEingebaut, sehr konfigurierbarEingebaut, decorator-basiertManuell oder über rq-schedulerEingebaut
Typischer AnwendungsfallGroße, polyglotte, komplexe PipelinesEinfachere Apps, die Celery-artige Power wollenKleine bis mittlere Apps, schnelles SetupAsync-native FastAPI/asyncio-Apps

Celery ist aus gutem Grund der etablierte Platzhirsch — es ist seit 2009 in Produktion gehärtet, und sein Plugin-Ökosystem (Flower fürs Monitoring, Beat fürs Scheduling, Canvas für Workflow-Komposition) hat kein echtes Äquivalent. Dramatiq ist das, wonach viele Teams greifen, wenn sie Celerys Zuverlässigkeitsgarantien wollen, ohne Celerys Konfigurationsfläche mitzunehmen. RQ tauscht Features gegen Einfachheit — es ist die Queue, die man einem neuen Kollegen in fünf Minuten erklären kann. arq ist der Ausreißer: gezielt für asyncio-Codebasen gebaut, wo es sich wie Kämpfen gegen das Framework anfühlt, jeden Task in run_in_executor einzupacken.

Wenn man Features auf einen breiteren Python-Task-Queue-Vergleich abbildet, ist die Weggabelung fast immer dieselbe: Braucht man Celerys Ökosystem, oder zahlt man eine Steuer für Features, die man nie anfassen wird?

Broker-Unterstützung

Das ist der erste Filter, weil er am wenigsten verhandelbar ist. RQ und arq sind ausschließlich Redis, Punkt — kein RabbitMQ, kein SQS. Das ist ein bewusster Simplicity-Trade-off: weniger Broker-Abstraktionen zu pflegen, aber es bedeutet auch: Wenn man bereits RabbitMQ für andere Services betreibt, kommt allein für die Task Queue noch Redis dazu.

Celery unterstützt Redis, RabbitMQ und Amazon SQS out of the box, plus Community-Transporte für eine Handvoll weiterer. Dramatiq unterstützt offiziell RabbitMQ und Redis, wobei RabbitMQ historisch der bevorzugte Broker ist, weil es sich direkter auf AMQPs natives Acknowledgment-Modell abbildet.

Die praktische Konsequenz: Wer speziell RabbitMQ braucht — wegen seiner Routing-Flexibilität, seiner ausgereifteren Dead-Lettering-Primitiven oder weil es ohnehin schon der Message-Bus für Nicht-Task-Traffic ist —, hat mit Celery und Dramatiq nur zwei Optionen. Wer ohnehin nur Redis nutzt (was üblich ist; Redis ist im Stack meist schon fürs Caching vorhanden), hat alle vier zur Auswahl, und die Entscheidung verlagert sich auf die anderen Achsen.

# 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:
    ...

Async-Unterstützung: Wo Celery zu kurz kommt

Hier fangen die meisten Argumente für Celery-Alternativen eigentlich an. Celery ist ein Jahrzehnt älter als modernes asyncio, und sein Worker-Modell ist grundlegend synchron — es führt Tasks in einem Pool aus OS-Prozessen oder -Threads aus (prefork, threads, eventlet, gevent), nicht in einem Event Loop. Man kann async def-Code aus einem Celery-Task heraus aufrufen, muss die Brücke dazu aber selbst mit asyncio.run() bauen, und diese Brückenkosten — pro Task-Aufruf einen frischen Event Loop hochzufahren — erzeugen echten Overhead, wenn die Tasks viele kleine, awaited I/O-Aufrufe machen statt eines großen.

Dramatiq und RQ haben dieselbe Einschränkung: Beide führen Tasks in Worker-Prozessen/-Threads aus, ohne erstklassige async def-Unterstützung im Actor-/Job-Ausführungspfad.

arq ist die Ausnahme, und genau deshalb existiert es überhaupt. Es ist nativ auf asyncio gebaut — der Worker selbst ist eine Coroutine, und die Task-Funktionen sind per Design async def:

# 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")

Wenn die Anwendung bereits asyncio-nativ ist — FastAPI, aiohttp, die meisten modernen Async-Web-Stacks —, entfernt arq eine ganze Kategorie von „warum blockiert das den Event Loop“-Bugs, weil es keine Sync/Async-Grenze gibt, über die man stolpern kann. Läuft die Anwendung synchron unter Django oder Flask, verschwindet dieser Vorteil größtenteils; Celerys synchrones Worker-Modell passt dann problemlos zum synchronen Modell der Anwendung.

Operativer Aufwand

„Operativer Aufwand“ heißt: Wie viele Prozesse betreibt man, wie viel muss man konfigurieren, um Production-Grade-Verhalten zu bekommen, und wie viel muss man verstehen, um es um 3 Uhr nachts zu debuggen.

Celery ist mit weitem Abstand am schwersten. Ein Celery-Deployment in Produktion bedeutet typischerweise: einen Broker (Redis oder RabbitMQ), optional ein separates Result-Backend, einen celery worker-Prozess (oft mehrere, abgestimmt nach Concurrency-Pool-Typ), celery beat, falls man geplante Tasks braucht, und oft Flower fürs Monitoring. Die Konfigurationsfläche ist enorm — Task-Routing, Queue-Prioritäten, acks_late, Prefetch-Multiplikatoren, Connection-Pooling, Serializer-Wahl —, alles davon ist mächtig, und fast alles davon hat eine Falle eingebaut, wenn man nicht vorher die Docs liest.

Dramatiq kürzt das bewusst ab. Es gibt keinen separaten Beat-Prozess (man würde stattdessen zu APScheduler oder einem cron-getriggerten Enqueue greifen), kein Result-Backend, außer man aktiviert es explizit, und sein Middleware-System ersetzt das meiste von dem, was Celery über Signals und Konfigurations-Flags macht. Es ist die „80 % von Celerys Zuverlässigkeit, 30 % der Konfiguration“-Option.

RQ ist von den vieren am leichtesten tatsächlich zu betreiben. Eine Redis-Instanz, ein rq worker-Befehl, fertig. Sein Dashboard (rq-dashboard) ist optional und trivial hinzuzufügen. Der Preis dafür: weniger eingebaute Garantien — native Retries erst relativ spät in seiner Geschichte, kein erstklassiges Routing-/Prioritätssystem jenseits benannter Queues.

arq liegt operativ nah bei RQ — eine einzige Redis-Abhängigkeit, ein einziger Worker-Prozess, gestartet mit arq worker.WorkerSettings —, aber mit Retry und cron-artigem Scheduling eingebaut über cron()-Decorators, sodass auch hier kein separater Beat-Prozess nötig ist.

Für ein kleines Team dominiert die Achse „operativer Aufwand“ oft jede andere Überlegung. Eine Queue, die man sich während eines Incidents vollständig selbst erklären kann, schlägt eine „korrektere“ Queue, deren Failure-Modes man erst durch Lesen des Quellcodes versteht.

Monitoring und Observability

Operativer Aufwand zeigt sich auch darin, wie man überhaupt merkt, dass etwas schiefläuft. Celerys Flower liefert out of the box ein Web-Dashboard mit aktiven Tasks, Worker-Status und historischem Durchsatz — wirklich nützlich, aber eben ein weiterer Prozess, den man deployen, hinter Auth absichern und am Laufen halten muss. Dramatiq setzt stattdessen auf sein Middleware-System: Eine Prometheus-Middleware ist in der Library enthalten und liefert Metriken, die man mit dem bestehenden Monitoring-Stack abgreift, statt mit einem eigens gebauten Dashboard. RQs rq-dashboard ist eine schlanke, optionale Flask-App, die den Job-Status direkt aus Redis liest — näher an einem Debugging-Tool als an einer vollständigen Observability-Plattform. arq liefert überhaupt kein Dashboard mit; man soll den Job-Status programmatisch inspizieren (Job.status()) oder eine eigene Ansicht auf den Redis-Keys aufbauen, die es pflegt — passend zu seiner minimalen Philosophie, aber es bedeutet, dass man bei allem jenseits einfacher Health-Checks auf sich allein gestellt ist.

Keins davon ist „gelöst“ in dem Sinn, wie es zum Beispiel HTTP-Request-Tracing ist — plane damit, mindestens etwas Kleber-Code zu schreiben, um Task-Fehler in das Alerting-System zu bekommen, dem das eigene Team ohnehin schon vertraut, egal welche Queue man wählt.

Failure-Semantik

Das ist die Achse, die man unterschätzt, bis sie einen Daten kostet. Jede dieser Libraries liefert standardmäßig at-least-once-Delivery, nicht exactly-once — ein Worker kann abstürzen, nachdem er einen Task fertig ausgeführt hat, aber bevor er ihn bestätigt hat, und der Broker liefert diesen Task dann erneut an einen anderen Worker aus. Wenn die eigenen Tasks nicht darauf ausgelegt sind, zweimal zu laufen, wird einen das früher oder später einholen, egal welche Library man wählt; siehe Celery-Tasks idempotent machen für das Pattern, das dieses Problem behandelt.

Celery bestätigt Tasks standardmäßig vor der Ausführung (acks_early), was tatsächlich eine schwächere Garantie ist — ein Absturz mitten im Task bedeutet, dass die Nachricht weg ist und nie erneut zugestellt wird. task_acks_late = True dreht das um, sodass erst nach erfolgreichem Abschluss bestätigt wird — man tauscht „nie einen Task verlieren“ gegen „einen Task eventuell zweimal ausführen“. Celerys Retry-System ist separat pro Task über autoretry_for, retry_backoff und max_retries konfigurierbar.

Dramatiq bestätigt standardmäßig spät — Nachrichten werden erst aus dem Broker entfernt, wenn der Actor erfolgreich zurückkehrt —, was der sicherere Default out of the box ist, auch wenn das bedeutet, dass das Risiko doppelter Ausführung ab Tag eins besteht, sofern die Tasks nicht idempotent sind.

RQ verlangt explizite Konfiguration für Retries (Retry(max=3, interval=[10, 30, 60]), beim Enqueue übergeben) und hat kein spätes Acknowledgment im selben Sinn — der Job eines abgestürzten Workers kann über dessen Monitoring-/Heartbeat-Mechanismus erkannt und neu eingereiht werden, das ist aber gröbergranular als Celerys oder Dramatiqs Ack-Modell pro Nachricht.

arq trackt laufende Jobs und reiht sie neu ein, wenn ein Worker über seinen Job-Timeout hinaus verschwindet, mit pro Funktion konfigurierbarer Retry-Anzahl und Backoff.

Keine dieser vier gibt einem exactly-once-Semantik — diese Garantie existiert für verteilte Task-Ausführung im Grunde nicht ohne transaktionale Koordination zwischen Broker und den eigenen Seiteneffekten, was keines dieser Tools versucht. Plane das Task-Design von Anfang an um at-least-once herum, statt doppelte Ausführung als seltenen Randfall zu behandeln; sie ist nicht selten, sie ist nur selten genug, um zwischen Incidents wieder vergessen zu werden.

Entscheidungsmatrix

Eine grobe Heuristik, in der Reihenfolge, wie oft sie tatsächlich zutrifft:

  • Man braucht RabbitMQ, SQS oder komplexe Workflow-Komposition (Chains, Chords, Groups) → Celery. Nichts sonst auf dieser Liste kommt an Canvas heran, wenn es um mehrstufige Pipelines mit Fan-out/Fan-in-Semantik geht.
  • Man will Celery-artige Zuverlässigkeit mit kleinerem Footprint und ist auf Redis oder RabbitMQ → Dramatiq. Guter Mittelweg für Teams, denen Celerys Konfigurationsfläche wirklich zu schaffen macht, die aber trotzdem Production-Grade-Retry und Middleware-Unterstützung wollen.
  • Man will das kleinstmögliche Setup, und Redis ist ohnehin schon im Stack → RQ. Bester Fit für kleine bis mittlere Apps, bei denen „eine Redis-Instanz, ein Worker-Befehl“ die ganze Geschichte sein soll.
  • Die Anwendung ist asyncio-nativ (FastAPI, aiohttp), und die Tasks sind I/O-lastige Coroutinen → arq. Vermeidet die Sync/Async-Brückensteuer komplett; ein schlechter Fit, wenn die Anwendung durchgehend synchron ist.

Teamgröße und bestehende Infrastruktur entscheiden das meist schneller als jeder Feature-Vergleich. Ein fünfköpfiges Team, das Django auf einer einzigen Redis-Instanz betreibt, gewinnt fast nichts von Celerys RabbitMQ-Unterstützung oder Canvas-Workflows — es gewinnt Konfiguration, die es nie nutzen wird, und eine Debugging-Fläche, die es sich nicht hätte einladen müssen.

Hinweise zur Migration

Später zwischen diesen Libraries zu wechseln ist mechanischer, als man denkt, weil die zugrunde liegende Form — Funktion plus serialisierte Argumente plus Broker — überall dieselbe ist. Die eigentliche Arbeit hat drei Teile: Task-Definitionen umschreiben (die Decorator-Syntax unterscheidet sich, aber die Funktionskörper müssen selten geändert werden), alle Celery-spezifischen Features neu bauen, auf die man sich verlassen hatte (Canvas-Workflows haben in RQ oder arq keine direkte Entsprechung — man muss die Chaining-Logik selbst nachbauen), und — der Teil, der tatsächlich Incidents verursacht — jeden Task vor der Migration auf Idempotenz prüfen, denn ein Broker-Wechsel ist genau die Art von Änderung, die während des Cutover-Fensters doppelte Zustellung verursacht.

Das sicherste Muster ist ein Parallelbetrieb: die neue Queue neben der alten aufsetzen, neue Task-Typen dorthin dual-writen, während bestehende, bereits laufende Tasks aus dem alten Broker abfließen, und Queue für Queue umschalten statt alles auf einmal. Die noch nicht ausgeführten, aber schon eingereihten Nachrichten eines laufenden Brokers direkt zu migrieren (etwa von Celery zu RQ), unterstützt keine dieser Libraries — man lässt die alte Queue natürlich leerlaufen und startet auf der neuen frisch, was bedeutet, ein traffic-armes Zeitfenster zu wählen, statt einen Zero-Downtime-Cutover zu erwarten.

Wie es von hier weitergeht

Die Queue auszuwählen sind die einfachen 20 % dieses Problems — die anderen 80 % bestehen darin, dass Tasks sich in Produktion unter echten Fehlerbedingungen korrekt verhalten. Drei Folgeartikel, die sich unabhängig von der gewählten Library lohnen:

Zum Abschluss

Es gibt keine universell richtige Antwort, die irgendwo in einem Celery-vs-Dramatiq-Benchmark vergraben liegt, und die sollte es auch nie geben — die richtige Wahl hängt davon ab, auf welchen Broker man sich schon festgelegt hat, ob die Anwendung synchron oder async-nativ ist, und wie viel operative Fläche das eigene Team wirklich besitzen will. Celery verdient sich seine Komplexität, wenn man RabbitMQ, SQS oder echte Workflow-Komposition braucht. Dramatiq verdient sich seinen Platz, wenn man das meiste dieser Zuverlässigkeit ohne das meiste dieser Konfiguration will. RQ gewinnt, wenn Einfachheit das Feature ist. arq gewinnt, wenn die Anwendung ohnehin schon auf asyncio aufgebaut ist und das Einbrücken synchroner Task-Ausführung genau die Komplexität wäre, die man eigentlich vermeiden will.

Entscheide danach, was du bereits betreibst und wie sich deine Tasks im Fehlerfall tatsächlich verhalten müssen — nicht danach, welcher Name in einem Suchergebnis zuerst auftaucht.

Faizan Nadeem
Autor
Faizan Nadeem

AI/GenAI engineer specialising in agentic RAG, LangGraph, and production LLM systems with Python and FastAPI. 2 years building AI agents, RAG pipelines, and the backends behind them.

Weitere Artikel