Queues and routing

Celery · Practical · 10 min

Separating slow and fast work into queues keeps a backlog of heavy jobs from delaying time-critical tasks.

By default every task goes to one queue named celery, and every worker consumes it. That's fine until a burst of 10-minute video encodes blocks password-reset emails.

Routing sends tasks to named queues:

  • Per call: apply_async(queue="emails").
  • Per task in config: task_routes = {"proj.tasks.send_email": {"queue": "emails"}, "proj.video.*": {"queue": "video"}}; glob patterns work.
  • Per task in the decorator: @app.task(queue="emails").

Then start workers for specific queues: celery -A proj worker -Q emails -c 8 and celery -A proj worker -Q video -c 2. A worker can consume several queues (-Q celery,emails). Queues are created automatically on first use (task_create_missing_queues, on by default).

Design queues by workload characteristics, not by feature: latency-sensitive vs batch, CPU-bound vs IO-bound, memory-hungry. Each kind then gets a worker pool sized and configured for it, and you can scale them independently.

Don't forget: a task routed to a queue nobody consumes simply waits there forever with no error. When you add a queue, add a worker for it.

Example

# proj/celery.py
from celery import Celery

app = Celery("proj", broker="redis://localhost:6379/0")
app.conf.task_routes = {
    "proj.tasks.send_email": {"queue": "emails"},
    "proj.video.*": {"queue": "video"},     # every task in proj.video
}

@app.task(name="proj.tasks.send_email")
def send_email(to): ...

@app.task(name="proj.tasks.cleanup")
def cleanup(): ...                       # default queue: "celery"

cleanup.apply_async(queue="maintenance")   # per-call override

# $ celery -A proj worker -Q emails -c 8 -n emails@%h
# $ celery -A proj worker -Q video  -c 2 -n video@%h
# $ celery -A proj worker -Q celery,maintenance -n default@%h

Remember

  • Default queue is 'celery'; route with task_routes, queue= or the decorator
  • Start workers per queue with -Q
  • Split queues by workload: latency, CPU vs IO, memory
  • A queue without a consumer silently accumulates tasks

Watch out for

  • Adding a route to a new queue but no worker consuming it.

Check yourself

  1. Password-reset emails are delayed behind long report tasks. Best fix?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.celeryq.dev/en/stable/userguide/routing.html