Skip to main content

Command Palette

Search for a command to run...

Celery in Django: The Complete Beginner to Intermediate Guide 🚀 -------------------------------------------

Updated
•9 min read
Celery in Django: The Complete Beginner to Intermediate Guide 🚀
-------------------------------------------

INTRODUCTION:

When I first started learning Django, I kept hearing people say:

"Use Celery for background tasks."

But nobody explained:

  • What exactly is a background task?

  • Why do we need Celery?

  • What is Redis doing in between?

  • What is RabbitMQ?

  • What is a Worker?

  • What is Celery Beat?

  • What's the difference between delay() and apply_async()?

  • When should I use @shared_task vs @app.task?

After spending time building projects and reading documentation, here's my complete understanding of Celery.


The Problem Celery Solves:

Imagine a user registers on your website.

After registration you want to:

  • Send welcome email

  • Generate profile image

  • Send push notification

  • Update analytics

A beginner usually writes:

def register_user(request):

    create_user()

    send_email()
    generate_thumbnail()
    send_notification()

    return Response("Success")

# It looks fine but here the user must wait for completion of each methods

What is Celery?

Celery is a distributed task queue.

Instead of performing heavy work immediately, Celery pushes that work into a queue so that workers can process it in the background.

The user receives a response immediately while Celery handles the expensive operations separately.


Components of Celery

Before writing any code, it's important to understand the core components that make Celery work. Every Celery setup consists of four main parts: Producer, Broker, Worker, and Result Backend.

1. Producer

The producer is responsible for creating tasks and sending them to the message broker.

In a Django application, your API endpoints, views, or business logic usually act as producers.

Example:

send_email.delay(user.id)

When this line executes, Django does not send the email immediately. Instead, it creates a task and pushes it to the broker.

Think of the producer as the person placing an order in a restaurant.


2. Broker

The broker acts as a middleman between Django and Celery workers.

Its job is to receive tasks from producers and store them until workers are ready to process them.

Popular brokers include:

  • Redis

  • RabbitMQ

Without a broker, Django and Celery workers would not be able to communicate.

Think of the broker as a waiter carrying food orders from customers to the kitchen.

[INSERT DIAGRAM 3: Producer → Broker → Multiple Workers → Result Backend]


3. Worker

Workers are the processes responsible for executing tasks.

They continuously listen to the broker for new tasks.

Whenever a task arrives, an available worker picks it up and executes it.

Example:

@shared_task
def send_email(user_id):
    ...

Once a worker receives this task, it executes the code independently from Django.

Think of workers as chefs inside a restaurant kitchen.


4. Result Backend

The result backend stores the outcome of completed tasks.

Common choices include:

  • Redis

  • Database

Example:

result = task.get()

This allows you to retrieve task status and results later.

Think of the result backend as a record book that keeps track of completed work.

Core components involved in processing Celery tasks.

Understanding Tasks

Tasks are simply Python functions that Celery can execute asynchronously.

The only difference between a normal Python function and a Celery task is that Celery tasks can be executed by workers in the background.

Using @shared_task

The most common way of creating tasks in Django is by using @shared_task.

from celery import shared_task

@shared_task
def send_email(user_id):
    print(user_id)

This task can now be executed asynchronously using:

send_email.delay(user.id)

Why Use @shared_task?

A common question is:

Why not use @app.task everywhere?

The answer is flexibility.

@shared_task allows reusable Django applications to define tasks without directly importing the Celery application instance.

This keeps applications loosely coupled and reusable.

For example, if you're building a reusable notification app that may be installed in multiple projects, using @shared_task prevents dependency on a specific Celery instance.

This is the recommended approach in Django projects.


Using @app.task

Celery also allows tasks to be registered directly on the Celery application.

Example:

from config.celery import app

@app.task
def send_email(user_id):
    pass

This works perfectly but creates tighter coupling between the task and your Celery application.


@shared_task vs @app.task

@shared_task @app.task
Recommended in Django Direct Celery registration
Reusable apps Project-specific usage
Loosely coupled Tightly coupled
Most common approach Less common in Django

In most Django projects, you will use @shared_task.


delay() vs apply_async()

This is one of the most frequently asked Celery interview questions.

Many developers use delay() every day without realizing what it actually does.

delay()

Example:

send_email.delay(user.id)

Internally, Celery converts this into:

send_email.apply_async(args=[user.id])

So delay() is simply a convenience wrapper around apply_async().

Use it when you want the task to run immediately.


apply_async()

apply_async() provides complete control over task execution.

Example:

send_email.apply_async(
    args=[user.id],
    countdown=60
)

This schedules the task to run after 60 seconds.


ETA Scheduling

Suppose you want an email to be sent exactly 10 minutes later.

from datetime import timedelta
from django.utils import timezone

send_email.apply_async(
    args=[user.id],
    eta=timezone.now() + timedelta(minutes=10)
)

The worker will execute the task at the specified time.


Retry Policies

send_email.apply_async(
    retry=True,
    retry_policy={
        "max_retries": 5
    }
)

This allows Celery to retry failed tasks automatically.


Priority Support

send_email.apply_async(
    args=[user.id],
    priority=9
)

Higher-priority tasks can be processed before lower-priority tasks.


Quick Rule

Use:

task.delay(...)

when you simply want background execution.

Use:

task.apply_async(...)

when you need scheduling, retries, priorities, expiration, or routing options.


Celery Beat

Many developers think Celery itself schedules tasks.

That's not actually true.

Celery workers only execute tasks.

Celery Beat is responsible for scheduling tasks.

Think of Celery Beat as a cron scheduler designed specifically for Celery.


How Celery Beat Works

Every few seconds, Celery Beat checks whether any scheduled task needs to run.

If yes:

  1. Beat creates a task.

  2. Sends it to the broker.

  3. Workers pick it up.

  4. Task executes.


Example Configuration

CELERY_BEAT_SCHEDULE = {
    "cleanup": {
        "task": "app.tasks.cleanup",
        "schedule": crontab(hour=0, minute=0),
    }
}

This task runs every day at midnight.


Common Uses of Celery Beat

  • Daily emails

  • Weekly reports

  • Database cleanup

  • Subscription reminders

  • Log rotation

  • Backup jobs

  • Analytics aggregation

If something needs to run automatically on a schedule, Celery Beat is usually the solution.


Retries

Real-world systems fail.

Emails fail.

Third-party APIs fail.

Payment gateways fail.

Network requests fail.

Celery provides built-in retry mechanisms.

Example:

@shared_task(bind=True, max_retries=3)
def send_email(self):

    try:
        ...
    except Exception:
        self.retry(countdown=60)

In this example:

  • First failure → wait 60 seconds

  • Retry #1

  • Retry #2

  • Retry #3

  • Mark task as failed

Retries are extremely useful when dealing with external systems.


Monitoring Celery

As applications grow, monitoring becomes important.

Questions you will eventually ask:

  • Which tasks are running?

  • Which tasks failed?

  • Which worker is overloaded?

  • How many tasks are queued?

The most popular monitoring tool is Flower.

Install Flower

pip install flower

Run:

celery -A config flower

Flower provides:

  • Running task monitoring

  • Worker monitoring

  • Queue inspection

  • Failed task tracking

  • Task history

It's essentially a dashboard for Celery.


Real Django Example

Imagine you're building an Instagram-like platform.

A user uploads a video.

Several expensive operations need to happen:

  • Generate thumbnail

  • Compress video

  • Notify followers

  • Update analytics

Without Celery, the user waits for all operations to finish.

With Celery:

  1. Django stores the video.

  2. Django immediately returns success.

  3. Background workers handle the heavy work.

This results in a significantly faster user experience.


Redis vs RabbitMQ

Choosing a broker is another common question.

Redis

Pros:

  • Extremely easy setup

  • Very fast

  • Beginner friendly

  • Perfect for most Django projects

Cons:

  • Fewer advanced messaging features

RabbitMQ

Pros:

  • Advanced routing

  • Better message reliability

  • Enterprise-grade features

Cons:

  • More complex configuration

  • Higher learning curve


Recommendation

For most Django applications:

Start with Redis.

Move to RabbitMQ only when your architecture requires advanced messaging capabilities.


Common Interview Questions

What is Celery?

A distributed task queue used for executing background jobs asynchronously.


Why use Celery?

To prevent long-running tasks from blocking user requests.


What is a Worker?

A process that consumes and executes tasks.


What is a Broker?

A messaging layer between producers and workers.


What is a Result Backend?

A storage system used for task status and results.


What is the difference between delay() and apply_async()?

delay() is a shortcut for apply_async().

apply_async() provides advanced features like ETA, countdown, retries, priorities, expiration, and routing.


What is Celery Beat?

A scheduler that periodically sends tasks to Celery workers.


Conclusion

Celery is one of the most important technologies in the Django ecosystem.

Whenever an application needs to send emails, process images, compress videos, generate reports, communicate with external APIs, or perform any long-running operation, Celery provides a clean and scalable solution.

The most important concepts to remember are:

  • Tasks

  • Producers

  • Brokers

  • Workers

  • Result Backends

  • @shared_task

  • delay()

  • apply_async()

  • Celery Beat

  • Retries

  • Monitoring

Once these concepts become clear, Celery becomes much easier to understand and use in real-world projects.