Kiedy naprawdę potrzebujesz kolejki zadań
Zanim w ogóle zaczniesz rozważać Celery czy jakiekolwiek inne rozwiązanie, zadaj sobie pytanie: czy na pewno potrzebujesz kolejki?
Dużo projektów Django ma Celery zainstalowane od dnia pierwszego, bo "tak się robi". To błąd. Celery to realna złożoność operacyjna - dodatkowy broker, dodatkowe workery, dodatkowe logi, dodatkowe punkty awarii.
Synchroniczne podejście jest w porządku, dopóki nie trafisz na jeden z poniższych przypadków:
- Operacje trwające ponad 200ms - email, zewnętrzne API, generowanie PDF
- Fan-out notyfikacji - wysłanie powiadomień do N użytkowników po jednym zdarzeniu
- Retry semantics - chcesz automatycznie ponawiać próby po błędach sieciowych
- Zewnętrzne API - Stripe webhook, integracje które mogą być chwilowo niedostępne
- Operacje CPU-intensive - przetwarzanie obrazów, ML inference
Co ciekawsze, wiele przypadków które wydają się wymagać kolejki, można rozwiązać na poziomie bazy danych.
pg_notify w PostgreSQL obsługuje prostą komunikację zdarzeniową bez żadnego brokera. Tabela deferred_jobs z kolumną run_at i prostym cronem potrafi zastąpić całą infrastrukturę kolejki dla większości małych projektów. Jeśli masz pod 100 zadań dziennie i operacje nie są krytycznie czasochłonne - rozważ to poważnie zanim dodasz Redisa do stosu.
Jeśli jednak potwierdziłeś, że kolejka jest potrzebna, czytaj dalej.
---
Stan rynku w 2026
Ekosystem Django task queues nie zmienił się rewolucyjnie od kilku lat, ale zmieniło się kilka istotnych rzeczy:
- Celery nadal dominuje w większych projektach, ale długi backlog bugów i problemy z chord/chain reliability wciąż są tematem rozmów na konferencjach
- django-q2 to fork oryginalnego django-q, który w 2023 przejął aktywne utrzymanie - projekt żyje i ma sensowne release'y
- Dramatiq zyskał uznanie wśród ludzi którzy wyczerpali cierpliwość do Celery, wersja 1.17+ jest stabilna
- RQ pozostaje bez zaskoczeń - prosta, przewidywalna, bez ambitnych feature planów
Omówię każde z tych rozwiązań z perspektywy kogoś, kto używał ich na produkcji.
---
Celery - incumbent z wieloletnią historią
Celery istnieje od 2009 roku. Jest wszędzie. Jeśli dołączasz do projektu Django z kolejką, prawdopodobnie jest tam Celery.
Co działa dobrze
Ekosystem jest dojrzały. Masz django-celery-beat do schedulingu, flower do monitorowania, integracje z dziesiątkami serwisów. Jeśli cokolwiek musisz zrobić z kolejką - ktoś już to rozwiązał.
Skala. Celery jest battle-tested na setkach workerów. Routing zadań, dynamiczne priorytety, dedykowane kolejki - to wszystko działa i jest dobrze udokumentowane.
Integracja z Django. django-celery-results do przechowywania wyników, django-celery-beat do cron-like schedulingu, integracja z Django ORM - to wszystko jest gotowe.
Co boli
Konfiguracja. Minimalna konfiguracja Celery to kilkanaście linii. Produkcyjna konfiguracja z różnymi kolejkami, prefetch multiplier, task routing, rate limitingiem, error handlerem - łatwo dochodzi do kilkuset linii. I trzeba to rozumieć, bo domyślne wartości nie zawsze są rozsądne.
# Minimalna konfiguracja - i tak już dużo
CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TIMEZONE = 'Europe/Warsaw'
CELERY_TASK_TRACK_STARTED = True
CELERY_TASK_TIME_LIMIT = 30 * 60
CELERY_WORKER_PREFETCH_MULTIPLIER = 1 # prawie zawsze lepsze niż default 4Memory leaks. Długo działające workery Celery mają tendencję do wyciekania pamięci. --max-tasks-per-child to obejście które stało się standardem - worker restartuje się po N zadaniach. To działa, ale to też overhead.
Chord/chain reliability. Chord ("execute callback po zakończeniu grupy zadań") jest notorycznie zawodny przy błędach w jednym z zadań grupy. Chord który nie uruchomi callbacku przy częściowym błędzie jest trudny do debugowania i naprawa wymaga rozumienia wewnętrznych mechanizmów Celery.
Zależność od brokera. Redis albo RabbitMQ - to hard requirement. Redis przez wielu używany jako cache jest "za free", ale jeśli nie masz go w stacku, to dodatkowa usługa do zarządzania.
Kiedy ma sens
- Duży zespół, wiele integracji, potrzebujesz ekosystemu
- Redis już masz w stacku
- Potrzebujesz złożonych workflow (chains, chords, groups) i akceptujesz ich ograniczenia
- Dołączasz do projektu który już ma Celery - nie zmieniaj bez powodu
---
RQ (Redis Queue) - prostota jako feature
RQ to biblioteka która robi jeden rzecz: kolejkuje zadania przez Redis. Nic więcej, nic mniej.
Co działa dobrze
Model mentalny jest trywialny. Funkcja Python idzie do kolejki, worker ją wykonuje. Nie ma specjalnej składni, nie ma task registry, nie ma osobnej konfiguracji aplikacji.
from rq import Queue
from redis import Redis
q = Queue(connection=Redis())
result = q.enqueue(send_notification, user_id=123)To jest całe API. Możesz to zrozumieć w 15 minut.
Worker jest prosty. rq worker uruchamia workera. Nie ma tu żadnych zaawansowanych opcji prefetch, autoscaling, task routing. To jest ograniczenie, ale też zaleta - mniej rzeczy może pójść nie tak.
Redis Dashboard (RQ Dashboard). Prosty UI do podglądu kolejek, zakończonych i nieudanych zadań. Nie ma flower'owej mocy, ale do małego projektu wystarczy.
Co boli
Brak zaawansowanych prymitywów. Nie ma odpowiednika Celery chord/chain. Jeśli potrzebujesz "wykonaj A, B, C równolegle, potem D" - RQ nie ma tego out-of-the-box. Możesz to zbudować ręcznie, ale to nie jest bibliotekowa funkcjonalność.
Priority queues w podstawowej wersji są umowne. Możesz tworzyć wiele kolejek i worker może je odpytywać w kolejności, ale nie ma prawdziwego priority schedulera który dynamicznie reorder'uje zadania.
Monitoring. RQ Dashboard jest podstawowy. Przy większej skali brakuje alertów, metryk throughput, percentyli czasów wykonania.
Brak retry by default. W Celery masz autoretry_for, w Dramatiq masz middleware. W RQ musisz obsłużyć retry samodzielnie.
Kiedy ma sens
- Proste zadania, mały projekt
- Mały zespół, zależy ci na łatwości onboardingu
- Masz już Redisa w stacku
- Nie potrzebujesz złożonych workflows
---
django-q2 - native Django bez brokera
django-q2 to aktywnie rozwijany fork oryginalnego django-q. Kluczowe wyróżnienie: nie potrzebuje zewnętrznego brokera. Używa Django ORM jako brokera.
Co działa dobrze
Zero dodatkowych zależności infrastrukturalnych. Jeśli masz bazę danych (a masz, bo to Django), masz wszystko czego potrzebujesz. To nie jest mały argument dla projektów gdzie minimalizacja stosu technologicznego jest priorytetem.
Integracja z Django Admin. Zadania, wyniki, harmonogram - wszystko widoczne w /admin. Dla małego projektu to może być wystarczający monitoring.
Setup jest dosłownie 5 minut. Dodaj do INSTALLED_APPS, zrób migrate, uruchom qcluster - gotowe.
# settings.py
INSTALLED_APPS = [
...
'django_q',
]
Q_CLUSTER = {
'name': 'myproject',
'workers': 4,
'recycle': 500,
'timeout': 60,
'retry': 120,
'queue_limit': 50,
'bulk': 10,
'orm': 'default', # używa Django ORM jako broker
}Scheduling. Schedule model pozwala na cron-like scheduling bez dodatkowych bibliotek.
Co boli
Polling-based. Workery odpytują bazę danych w pętli, sprawdzając czy są nowe zadania. Domyślny interwał to kilka sekund. Oznacza to latency - zadanie nie uruchomi się natychmiast po dodaniu, tylko przy następnym poll. Dla wielu przypadków to w porządku, ale jeśli potrzebujesz reakcji w czasie rzeczywistym - to problem.
Baza danych pod obciążeniem. Przy dużej liczbie workerów i krótkim polling interwale, ciągłe SELECT'y do tabeli zadań mogą obciążać bazę. Przy 10 workerach odpytujących co sekundę - to 600 zapytań na minutę tylko na polling.
Nie dla wysokiej przepustowości. django-q2 nie jest narzędziem do 10k+ zadań dziennie. Przy takim wolumenie ORM-based broker zaczyna być wąskim gardłem.
Mniejszy ekosystem. Mniej integracji, mniej pytań na Stack Overflow, mniej gotowych rozwiązań.
Kiedy ma sens
- Mały projekt, niski wolumen zadań (pod 1k/dzień)
- Nie chcesz lub nie możesz dodać Redisa do stosu
- Zależy ci na prostocie dewelopmentu i małym narzucie operacyjnym
- Admin jako monitoring wystarczy
---
Dramatiq - nowoczesna alternatywa
Dramatiq jest napisany z myślą o problemach Celery. Autor wprost adresuje ich rozwiązanie w dokumentacji. Pierwsza stabilna wersja pochodzi z 2018, ale dopiero w ostatnich latach zyskał szersze uznanie.
Co działa dobrze
Middleware architektura. Dramatiq używa explicit middleware pipeline zamiast magii dekoratorów. Retry, age limit, time limit, callbacks - to wszystko middleware które możesz rozumieć i konfigurować:
import dramatiq
from dramatiq.brokers.redis import RedisBroker
from dramatiq.middleware import Retries, TimeLimit, AgeLimit
broker = RedisBroker(url='redis://localhost:6379')
broker.add_middleware(Retries(max_retries=3))
broker.add_middleware(TimeLimit(time_limit=60000)) # ms
dramatiq.set_broker(broker)
@dramatiq.actor(max_retries=3, min_backoff=1000, max_backoff=60000)
def send_email(user_id: int, template: str) -> None:
...Retry by default. W odróżnieniu od RQ, Dramatiq domyślnie powtarza zadania przy błędach z exponential backoff. Musisz aktywnie wyłączyć retry, nie włączyć.
Lepsza obsługa błędów. Failed zadania nie znikają - masz pełny stack trace, czas błędu, licznik prób. Dramatiq nie jest "fire and forget".
Actor model. Zadania w Dramatiq są aktorami - izolowanymi jednostkami które komunikują się przez wiadomości. To konceptualnie czystsze niż Celery task.
Typy. Dramatiq traktuje serializację poważnie. Nie ma problemów z nieoczekiwaną deserializacją obiektów Python.
Co boli
Mniejszy ekosystem Django. Nie ma dramatiq-beat na poziomie django-celery-beat. Scheduling przez apscheduler lub periodiq istnieje, ale dokumentacja dla Django jest cieńsza.
Mniej gotowych integracji. Szukasz integracji z jakimś serwisem? Przy Celery prawdopodobnie jest gotowa biblioteka. Przy Dramatiq - napisz sam lub zaadaptuj.
Mniej odpowiedzi na Stack Overflow. Przy niszowych problemach będziesz czytał source code zamiast kopiować ze Stack Overflow. Dla jednych to zaleta, dla innych nie.
Kiedy ma sens
- Nowy projekt, wysoki standard jakości kodu
- Zespół sfrustrowany złożonością Celery
- Potrzebujesz solidnej semantyki retry bez boilerplate
- Możesz zaakceptować mniejszy ekosystem w zamian za czystszy model
---
Tabela porównawcza
| Kryterium | Celery | RQ | django-q2 | Dramatiq | |---|---|---|---|---| | Broker | Redis / RabbitMQ | Redis | Django ORM (lub Redis) | Redis / RabbitMQ | | Setup complexity | Wysoka | Niska | Bardzo niska | Srednia | | Retry semantics | Manualne (autoretry_for) | Brak (DIY) | Wbudowane | Domyslne (middleware) | | Complex workflows | Tak (chain/chord/group) | Nie | Ograniczone | Tak (pipeline/group) | | Monitoring | Flower (dobry) | RQ Dashboard (podstawowy) | Django Admin | Wbudowane + Prometheus | | Django Admin | Czesciowa integracja | Brak | Pelna integracja | Brak | | Skalowanie | Doskonale | Dobre | Slabe (ORM bottleneck) | Dobre | | Spolecznosc | Bardzo duza | Srednia | Mala | Rossnaca | | Incydenty produkcyjne | Czeste (chord bugs, memory) | Rzadkie | Rzadkie | Rzadkie | | Latency | Niska | Niska | Srednia (polling) | Niska | | Dojrzalosc | Bardzo dojrzale (2009) | Dojrzale (2011) | Srednia (fork 2022) | Srednia (2018) |
---
Drzewo decyzyjne
flowchart TD
A[Potrzebujesz kolejki zadań?] --> B{Masz ponad 1k zadań/dzień
lub potrzebujesz niskiej latency?}
B -- Nie --> C[Rozważ django-q2
lub tabelę deferred_jobs]
B -- Tak --> D{Masz już Redis
w stacku?}
D -- Nie --> E{Akceptujesz Redis
jako nową zależność?}
E -- Nie --> C
E -- Tak --> F{Potrzebujesz złożonych
workflows chain/chord?}
D -- Tak --> F
F -- Tak --> G{Duży zespół i
dużo integracji?}
G -- Tak --> H[Celery
- sprawdzony, ekosystem]
G -- Nie --> I[Dramatiq
- czystszy model, retry by default]
F -- Nie --> J{Prosta architektura,
mały projekt?}
J -- Tak --> K[RQ
- prosty, Redis, łatwy onboarding]
J -- Nie --> I---
Co wybieram i kiedy
To nie jest lista "najlepszych praktyk". To lista moich konkretnych decyzji na konkretnych projektach.
Nowy projekt, pod 10k zadań/dzień
django-q2 jeśli nie mam jeszcze Redisa. Brak dodatkowej infrastruktury to realna oszczędność czasu przy MVP. Latency polling-based nie jest problemem przy niskim wolumenie.
RQ jeśli mam już Redisa. Prostota modelu mentalnego przekłada się na szybszy onboarding nowych deweloperów i mniej błędów konfiguracyjnych.
Istniejący Redis w stacku, rosnący projekt
RQ przy prostych zadaniach (do 50k/dzień, bez złożonych workflow).
Dramatiq gdy zaczynam odczuwać ból konfiguracji lub chcę solidnych retry semantics z czytelnym kodem.
Złożone workflow (chains, chords, fan-out)
Celery z pełną świadomością kompromisów. Chord i chain działają dla większości przypadków. Przy error handling w chord trzeba wiedzieć co się dzieje i testować to wprost.
Nie próbowałbym budować złożonych workflow w RQ - to DIY który skończyłby się własną bibliotekę zarządzania stanem.
Greenfield, wysokie standardy jakości kodu
Dramatiq. Middleware pipeline jest testowalny, explicit, czytelny. Retry by default eliminuje całą klasę błędów ("nie wiedziałem że trzeba dodać autoretry_for"). Mniejszy ekosystem jest barierą, ale dla nowego projektu to zarządzalne.
Przejmowanie projektu z Celery
Nie migruję bez powodu. Migracja działającego Celery to koszt bez oczywistej korzyści technicznej. Chyba że projekt ma konkretny problem (chord bugs, memory leaks które nie dają się naprawić, skomplikowana konfiguracja blokująca nowych deweloperów) - wtedy Dramatiq.
---
Praktyczne wskazówki niezależne od wyboru
Zawsze monitoruj kolejki
Niezależnie od biblioteki - ustaw alerting na długość kolejki i wiek najstarszego zadania. Nieprzetwarzana kolejka która rośnie cicho to klasyczny incident produkcyjny.
# Przykład health check endpoint niezależny od biblioteki
from django.http import JsonResponse
def queue_health(request):
# Sprawdź czy workery żyją i kolejka nie rośnie
stats = get_queue_stats() # implementacja zależy od biblioteki
if stats['pending'] > 10000 or stats['oldest_job_age_minutes'] > 30:
return JsonResponse({'status': 'degraded', **stats}, status=503)
return JsonResponse({'status': 'ok', **stats})Idempotentność zadań
Każde zadanie powinno być idempotentne - wykonanie go dwa razy nie powinno powodować błędów. Przy retry semantics to jest warunek konieczny.
@dramatiq.actor
def charge_customer(payment_id: str) -> None:
payment = Payment.objects.get(id=payment_id)
if payment.status == 'charged':
return # już przetworzone, idempotentne
# procesuj...Nie przesyłaj obiektów przez kolejkę
Przesyłaj ID, nie obiekty. Obiekt może się zmienić między kolejkowaniem a wykonaniem zadania.
# ZLE
q.enqueue(process_order, order_obj)
# DOBRZE
q.enqueue(process_order, order_id=order.id)Osobne kolejki dla różnych priorytetów
Niezależnie od biblioteki - nie mieszaj zadań krytycznych (płatności, powiadomienia bezpieczeństwa) z zadań best-effort (generowanie raportów, cache warming). Osobne workery dla osobnych kolejek.
---
Podsumowanie
Nie ma jednej odpowiedzi. Jest odpowiedź dla twojego kontekstu.
Jeśli muszę uprościć do jednego zdania: zacznij od prostszego niż myślisz że potrzebujesz. django-q2 dla małego projektu bez Redisa. RQ dla projektu z Redisem i prostymi zadaniami. Dramatiq dla nowego projektu z wymaganiami jakościowymi. Celery tylko gdy potrzebujesz jego ekosystemu lub już tam jest.
Celery nie jest złe. Jest złożone - i ta złożoność ma sens przy dużej skali. Problem w tym, że jest domyślnym wyborem też dla małych projektów gdzie ta złożoność nie ma sensu.
W 2026 masz dobre opcje. Użyj właściwej na właściwym etapie.
