Distributed task queue system for Python enabling asynchronous execution of background jobs, scheduled tasks, and workflows across multiple workers with Django, Flask, and FastAPI integration.
Celery is a distributed task queue system for Python that enables asynchronous execution of background jobs across multiple workers. It supports scheduling, retries, task workflows, and integrates seamlessly with Django, Flask, and FastAPI.
Don't Use When:
asyncio instead)# Basic installation
pip install celery
# With Redis broker
pip install celery[redis]
# With RabbitMQ broker
pip install celery[amqp]
# Full batteries (recommended)
pip install celery[redis,msgpack,auth,cassandra,elasticsearch,s3,sqs]
# celery_app.py
from celery import Celery
# Create Celery app with Redis broker
app = Celery(
'myapp',
broker='redis://localhost:6379/0',
backend='redis://localhost:6379/1'
)
# Configuration
app.conf.update(
task_serializer='json',
accept_content=['json'],
result_serializer='json',
timezone='UTC',
enable_utc=True,
)
# Define a task
@app.task
def add(x, y):
return x + y
@app.task
def send_email(to, subject, body):
# Simulate email sending
import time
time.sleep(2)
print(f"Email sent to {to}: {subject}")
return {"status": "sent", "to": to}
# Start worker
celery -A celery_app worker --loglevel=info
# Multiple workers with concurrency
celery -A celery_app worker --concurrency=4 --loglevel=info
# Named worker for specific queues
celery -A celery_app worker -Q emails,reports --loglevel=info
# Call task asynchronously
result = add.delay(4, 6)
# Wait for result
print(result.get(timeout=10)) # 10
# Apply async with options
result = send_email.apply_async(
args=['user@example.com', 'Hello', 'Welcome!'],
countdown=60 # Execute after 60 seconds
)
# Check task state
print(result.status) # PENDING, STARTED, SUCCESS, FAILURE
Broker: Message queue that stores tasks
Workers: Processes that execute tasks
Result Backend: Storage for task results
Beat Scheduler: Periodic task scheduler
PENDING → STARTED → SUCCESS
→ RETRY → SUCCESS
→ FAILURE
# celery_config.py
broker_url = 'redis://localhost:6379/0'
result_backend = 'redis://localhost:6379/1'
# With authentication
broker_url = 'redis://:password@localhost:6379/0'
# Redis Sentinel (high availability)
broker_url = 'sentinel://localhost:26379;sentinel://localhost:26380'
broker_transport_options = {
'master_name': 'mymaster',
'sentinel_kwargs': {'password': 'password'},
}
# Redis connection pool settings
broker_pool_limit = 10
broker_connection_retry = True
broker_connection_retry_on_startup = True
broker_connection_max_retries = 10
# Basic RabbitMQ
broker_url = 'amqp://guest:guest@localhost:5672//'
# With virtual host
broker_url = 'amqp://user:password@localhost:5672/myvhost'
# High availability (multiple brokers)
broker_url = [
'amqp://user:password@host1:5672//',
'amqp://user:password@host2:5672//',
]
# RabbitMQ-specific settings
broker_heartbeat = 30
broker_pool_limit = 10
# AWS SQS (serverless)
broker_url = 'sqs://'
broker_transport_options = {
'region': 'us-east-1',
'queue_name_prefix': 'myapp-',
'visibility_timeout': 3600,
'polling_interval': 1,
}
# With custom credentials
import boto3
broker_transport_options = {
'region': 'us-east-1',
'predefined_queues': {
'default': {
'url': 'https://sqs.us-east-1.amazonaws.com/123456789/myapp-default',
}
}
}
from celery import Task, shared_task
from celery_app import app
# Method 1: Decorator
@app.task
def simple_task(x, y):
return x + y
# Method 2: Shared task (framework-agnostic)
@shared_task
def framework_task(data):
return process(data)
# Method 3: Task class (advanced)
class CustomTask(Task):
def on_success(self, retval, task_id, args, kwargs):
print(f"Task {task_id} succeeded with {retval}")
def on_failure(self, exc, task_id, args, kwargs, einfo):
print(f"Task {task_id} failed: {exc}")
def on_retry(self, exc, task_id, args, kwargs, einfo):
print(f"Task {task_id} retrying: {exc}")
@app.task(base=CustomTask)
def monitored_task(x):
return x * 2
@app.task(
name='custom.task.name', # Custom task name
bind=True, # Bind task instance as first arg
ignore_result=True, # Don't store result (performance)
max_retries=3, # Max retry attempts
default_retry_delay=60, # Retry delay in seconds
rate_limit='100/h', # Rate limiting
time_limit=300, # Hard time limit (kills task)
soft_time_limit=240, # Soft time limit (raises exception)
serializer='json', # Task serializer
compression='gzip', # Compress large messages
priority=5, # Task priority (0-9)
queue='high_priority', # Target queue
routing_key='priority.high', # Routing key
acks_late=True, # Acknowledge after execution
reject_on_worker_lost=True, # Reject if worker dies
)
def advanced_task(self, data):
try:
return process(data)
except Exception as exc:
# Retry with exponential backoff
raise self.retry(exc=exc, countdown=2 ** self.request.retries)
@app.task(bind=True)
def context_aware_task(self, x, y):
# Access task metadata
print(f"Task ID: {self.request.id}")
print(f"Task Name: {self.name}")
print(f"Args: {self.request.args}")
print(f"Kwargs: {self.request.kwargs}")
print(f"Retries: {self.request.retries}")
print(f"Delivery Info: {self.request.delivery_info}")
# Manual retry
try:
result = risky_operation(x, y)
except Exception as exc:
raise self.retry(exc=exc, countdown=60, max_retries=3)
return result
# delay() - Simple async execution
result = add.delay(4, 6)
# apply_async() - Full control
result = add.apply_async(
args=(4, 6),
kwargs={'extra': 'data'},
# Timing options
countdown=60, # Execute after N seconds
eta=datetime(2025, 12, 1, 10, 0), # Execute at specific time
expires=3600, # Task expires after N seconds
# Routing options
queue='math', # Target specific queue
routing_key='math.add', # Custom routing key
exchange='tasks', # Target exchange
priority=9, # High priority
# Execution options
serializer='json', # Message serializer
compression='gzip', # Compress payload
retry=True, # Auto-retry on failure
retry_policy={
'max_retries': 3,
'interval_start': 0,
'interval_step': 0.2,
'interval_max': 0.2,
},
# Task linking
link=log_result.s(), # Success callback
link_error=handle_error.s(), # Error callback
)
# Check result
if result.ready():
print(result.get()) # Get result (blocks)
print(result.result) # Get result (non-blocking)
from celery import signature
# Create signature (doesn't execute)
sig = add.signature((2, 2), countdown=10)
sig = add.s(2, 2) # Shorthand
# Partial arguments (currying)
partial = add.s(2) # One arg fixed
result = partial.apply_async(args=(4,)) # Add second arg
# Immutable signature (args can't be replaced)
immutable = add.si(2, 2)
# Clone and modify
new_sig = sig.clone(countdown=60)
# Execute signature
result = sig.delay()
result = sig.apply_async()
result = sig() # Synchronous execution
# Basic result retrieval
result = add.delay(4, 6)
value = result.get(timeout=10) # Blocks until complete
# Non-blocking result check
if result.ready():
print(result.result)
# Result states
print(result.status) # PENDING, STARTED, SUCCESS, FAILURE
print(result.successful()) # True if SUCCESS
print(result.failed()) # True if FAILURE
# Result metadata
print(result.traceback) # Exception traceback if failed
print(result.info) # Task return value or exception
# Forget result (free memory)
result.forget()
# Revoke task (cancel)
result.revoke(terminate=True) # Kill running task
add.AsyncResult(task_id).revoke() # Revoke by ID
# Define queues
from kombu import Queue, Exchange
app.conf.task_queues = (
Queue('default', Exchange('default'), routing_key='default'),
Queue('high_priority', Exchange('priority'), routing_key='priority.high'),
Queue('low_priority', Exchange('priority'), routing_key='priority.low'),
Queue('emails', Exchange('tasks'), routing_key='tasks.email'),
Queue('reports', Exchange('tasks'), routing_key='tasks.report'),
)
# Default queue
app.conf.task_default_queue = 'default'
app.conf.task_default_exchange = 'tasks'
app.conf.task_default_routing_key = 'default'
# Route specific tasks to queues
app.conf.task_routes = {
'myapp.tasks.send_email': {'queue': 'emails'},
'myapp.tasks.generate_report': {'queue': 'reports', 'priority': 9},
'myapp.tasks.*': {'queue': 'default'},
}
# Function-based routing
def route_task(name, args, kwargs, options, task=None, **kw):
if 'email' in name:
return {'queue': 'emails', 'routing_key': 'email.send'}
elif 'report' in name:
return {'queue': 'reports', 'priority': 5}
return {'queue': 'default'}
app.conf.task_routes = (route_task,)
# Worker consuming specific queues
celery -A myapp worker -Q emails,reports --loglevel=info
# Multiple workers for different queues
celery -A myapp worker -Q high_priority -c 4 --loglevel=info
celery -A myapp worker -Q default -c 2 --loglevel=info
celery -A myapp worker -Q low_priority -c 1 --loglevel=info
# Configure priority support
app.conf.task_queue_max_priority = 10
app.conf.task_default_priority = 5
# Send task with priority
high_priority_task.apply_async(args=(), priority=9)
low_priority_task.apply_async(args=(), priority=1)
# Priority-based routing
app.conf.task_routes = {
'critical_task': {'queue': 'default', 'priority': 10},
'background_task': {'queue': 'default', 'priority': 1},
}
# celery_config.py
from celery.schedules import crontab, solar
# Periodic task schedule
beat_schedule = {
# Run every 30 seconds
'add-every-30-seconds': {
'task': 'myapp.tasks.add',
'schedule': 30.0,
'args': (16, 16)
},
# Run every morning at 7:30 AM
'send-daily-report': {
'task': 'myapp.tasks.send_daily_report',
'schedule': crontab(hour=7, minute=30),
},
# Run every Monday morning
'weekly-cleanup': {
'task': 'myapp.tasks.cleanup',
'schedule': crontab(hour=0, minute=0, day_of_week=1),
},
# Run on specific days
'monthly-report': {
'task': 'myapp.tasks.monthly_report',
'schedule': crontab(hour=0, minute=0, day_of_month='1'),
'kwargs': {'month_offset': 1}
},
# Solar schedule (sunrise/sunset)
'wake-up-at-sunrise': {
'task': 'myapp.tasks.morning_routine',
'schedule': solar('sunrise', -37.81, 144.96), # Melbourne
},
}
app.conf.beat_schedule = beat_schedule
from celery.schedules import crontab
# Every minute
crontab()
# Every 15 minutes
crontab(minute='*/15')
# Every hour at :30
crontab(minute=30)
# Every day at midnight
crontab(hour=0, minute=0)
# Every weekday at 5 PM
crontab(hour=17, minute=0, day_of_week='1-5')
# Every Monday, Wednesday, Friday at noon
crontab(hour=12, minute=0, day_of_week='mon,wed,fri')
# First day of month
crontab(hour=0, minute=0, day_of_month='1')
# Last day of month (use day_of_month='28-31' with logic in task)
crontab(hour=0, minute=0, day_of_month='28-31')
# Quarterly (every 3 months)
crontab(hour=0, minute=0, day_of_month='1', month_of_year='*/3')
# Start beat scheduler
celery -A myapp beat --loglevel=info
# Beat with custom scheduler
celery -A myapp beat --scheduler django_celery_beat.schedulers:DatabaseScheduler
# Combine worker and beat (development only)
celery -A myapp worker --beat --loglevel=info
pip install django-celery-beat
# settings.py (Django)
INSTALLED_APPS = [
'django_celery_beat',
]
# Migrate database
python manage.py migrate django_celery_beat
# Run beat with database scheduler
celery -A myapp beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
# Create periodic task via Django admin or ORM
from django_celery_beat.models import PeriodicTask, IntervalSchedule, CrontabSchedule
import json
# Create interval schedule (every 10 seconds)
schedule, created = IntervalSchedule.objects.get_or_create(
every=10,
period=IntervalSchedule.SECONDS,
)
PeriodicTask.objects.create(
interval=schedule,
name='Import feed every 10 seconds',
task='myapp.tasks.import_feed',
args=json.dumps(['https://example.com/feed']),
)
# Create crontab schedule
schedule, created = CrontabSchedule.objects.get_or_create(
minute='0',
hour='*/4', # Every 4 hours
day_of_week='*',
day_of_month='*',
month_of_year='*',
)
PeriodicTask.objects.create(
crontab=schedule,
name='Hourly cleanup',
task='myapp.tasks.cleanup',
)
from celery import chain
# Sequential execution
result = chain(add.s(2, 2), add.s(4), add.s(8))()
# Equivalent to: add(add(add(2, 2), 4), 8)
# Result: 16
# Shorthand syntax
result = (add.s(2, 2) | add.s(4) | add.s(8))()
# Chain with different tasks
workflow = (
fetch_data.s(url) |
process_data.s() |
save_results.s()
)
result = workflow.apply_async()
from celery import group
# Parallel execution
job = group([
add.s(2, 2),
add.s(4, 4),
add.s(8, 8),
])
result = job.apply_async()
# Wait for all results
results = result.get(timeout=10) # [4, 8, 16]
# Group with callbacks
job = group([
process_item.s(item) for item in items
]) | summarize_results.s()
from celery import chord
# Group with callback
job = chord([
fetch_url.s(url) for url in urls
])(combine_results.s())
# Example: Process multiple files, then merge
workflow = chord([
process_file.s(file) for file in files
])(merge_results.s())
result = workflow.apply_async()
from celery import group
# Map: Apply same task to list of args
results = add.map([(2, 2), (4, 4), (8, 8)])
# Starmap: Unpack arguments
results = add.starmap([(2, 2), (4, 4), (8, 8)])
# Equivalent to:
results = group([add.s(2, 2), add.s(4, 4), add.s(8, 8)])()
from celery import chain, group, chord
# Parallel processing with sequential steps
workflow = chain(
# Step 1: Fetch data
fetch_data.s(source),
# Step 2: Process in parallel
group([
process_chunk.s(chunk_id) for chunk_id in range(10)
]),
# Step 3: Aggregate results
aggregate.s(),
# Step 4: Save to database
save_results.s()
)
# Nested chords
workflow = chord([
chord([
subtask.s(item) for item in chunk
])(process_chunk.s())
for chunk in chunks
])(final_callback.s())
# Real-world example: Report generation
generate_report = chain(
fetch_user_data.s(user_id),
chord([
calculate_stats.s(),
fetch_transactions.s(),
fetch_activity.s(),
])(combine_sections.s()),
render_pdf.s(),
send_email.s(user_email)
)
@app.task(
autoretry_for=(RequestException, IOError), # Auto-retry these exceptions
retry_kwargs={'max_retries': 5}, # Max 5 retries
retry_backoff=True, # Exponential backoff
retry_backoff_max=600, # Max 10 minutes backoff
retry_jitter=True, # Add randomness to backoff
)
def fetch_url(url):
response = requests.get(url)
response.raise_for_status()
return response.json()
@app.task(bind=True, max_retries=3)
def process_data(self, data):
try:
result = external_api_call(data)
return result
except TemporaryError as exc:
# Retry after 60 seconds
raise self.retry(exc=exc, countdown=60)
except PermanentError as exc:
# Don't retry, log and fail
logger.error(f"Permanent error: {exc}")
raise
except Exception as exc:
# Exponential backoff
raise self.retry(
exc=exc,
countdown=2 ** self.request.retries,
max_retries=3
)
@app.task
def on_error(request, exc, traceback):
"""Called when task fails"""
logger.error(f"Task {request.id} failed: {exc}")
send_alert(f"Task failure: {request.task}", str(exc))
@app.task
def risky_task(data):
return process(data)
# Link error callback
risky_task.apply_async(
args=(data,),
link_error=on_error.s()
)
from celery import Task
class CallbackTask(Task):
def on_failure(self, exc, task_id, args, kwargs, einfo):
"""Handle task failure"""
logger.error(f"Task {task_id} failed with {exc}")
# Send notification
send_notification('Task Failed', str(exc))
def on_success(self, retval, task_id, args, kwargs):
"""Handle task success"""
logger.info(f"Task {task_id} succeeded: {retval}")
def on_retry(self, exc, task_id, args, kwargs, einfo):
"""Handle task retry"""
logger.warning(f"Task {task_id} retrying: {exc}")
@app.task(base=CallbackTask)
def monitored_task(x):
if x < 0:
raise ValueError("Negative value")
return x * 2
@app.task(bind=True)
def robust_task(self, data):
# Categorize exceptions
try:
return process(data)
except NetworkError as exc:
# Transient error - retry
raise self.retry(exc=exc, countdown=60, max_retries=5)
except ValidationError as exc:
# Permanent error - don't retry
logger.error(f"Invalid data: {exc}")
return {'status': 'failed', 'error': str(exc)}
except DatabaseError as exc:
# Critical error - retry with exponential backoff
backoff = min(2 ** self.request.retries * 60, 3600)
raise self.retry(exc=exc, countdown=backoff, max_retries=10)
except Exception as exc:
# Unknown error - retry limited times
if self.request.retries < 3:
raise self.retry(exc=exc, countdown=120)
else:
# Max retries exceeded - fail and alert
logger.critical(f"Task failed after retries: {exc}")
send_alert('Critical Task Failure', str(exc))
raise
# Enable events
app.conf.worker_send_task_events = True
app.conf.task_send_sent_event = True
# Event listeners
from celery import signals
@signals.task_prerun.connect
def task_prerun_handler(sender=None, task_id=None, task=None, args=None, kwargs=None, **extra):
print(f"Task {task.name}[{task_id}] starting")
@signals.task_postrun.connect
def task_postrun_handler(sender=None, task_id=None, task=None, retval=None, **extra):
print(f"Task {task.name}[{task_id}] completed: {retval}")
@signals.task_failure.connect
def task_failure_handler(sender=None, task_id=None, exception=None, traceback=None, **extra):
print(f"Task {task_id} failed: {exception}")
@signals.task_retry.connect
def task_retry_handler(sender=None, task_id=None, reason=None, **extra):
print(f"Task {task_id} retrying: {reason}")
# Install Flower
pip install flower
# Start Flower
celery -A myapp flower --port=5555
# Access dashboard
# http://localhost:5555
# Flower configuration
flower_basic_auth = ['admin:password']
flower_persistent = True
flower_db = 'flower.db'
flower_max_tasks = 10000
from celery_app import app
# Get active tasks
i = app.control.inspect()
print(i.active())
# Get scheduled tasks
print(i.scheduled())
# Get reserved tasks
print(i.reserved())
# Get worker stats
print(i.stats())
# Get registered tasks
print(i.registered())
# Revoke task
app.control.revoke(task_id, terminate=True)
# Shutdown worker
app.control.shutdown()
# Pool restart
app.control.pool_restart()
# Rate limit
app.control.rate_limit('myapp.tasks.slow_task', '10/m')
# List active tasks
celery -A myapp inspect active
# List scheduled tasks
celery -A myapp inspect scheduled
# Worker stats
celery -A myapp inspect stats
# Registered tasks
celery -A myapp inspect registered
# Revoke task
celery -A myapp control revoke <task_id>
# Shutdown workers
celery -A myapp control shutdown
# Purge all tasks
celery -A myapp purge
@app.task(bind=True)
def tracked_task(self, data):
from prometheus_client import Counter, Histog
<!-- Content truncated for initial SEO render. Open the source file tab for the full file. -->
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer