Celery là task queue chạy ngoài request cycle — request handler đẩy task vào broker (Redis/RabbitMQ), worker process pick lên xử lý.
Dùng cho gửi email, sinh thumbnail, export file, sync 3rd-party API.
# config/celery.py
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.prod')
app = Celery('myproject')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
# settings.py
CELERY_BROKER_URL = 'redis://redis:6379/0'
CELERY_RESULT_BACKEND = 'redis://redis:6379/1'
CELERY_TASK_ALWAYS_EAGER = False # True chỉ trong test
# apps/blog/tasks.py
from celery import shared_task
@shared_task(bind=True, autoretry_for=(SMTPException,), retry_backoff=True, max_retries=5)
def send_post_email(self, post_id: int) -> None:
post = Post.objects.get(id=post_id)
EmailService.send_to_subscribers(post)
# views.py
send_post_email.delay(post.id) # non-blockingChạy worker: celery -A config worker -l info. Cần cron-like schedule thì thêm celery -A config beat.
Hai nguyên tắc quan trọng: đừng bao giờ pass model instance vào task — bằng lúc worker chạy thì instance đã stale, dữ liệu sai bét. Pass id rồi Model.objects.get(id=...) lại trong task. Và task phải idempotent vì worker có thể retry; mỗi task ghi DB phải đảm bảo chạy lần 2 không gây side-effect kép.
Celery is a task queue running outside the request cycle — the handler enqueues a task on a broker (Redis/RabbitMQ), and worker processes pick it up and process it.
Use it for email, image thumbnails, export, third-party API sync.
# config/celery.py
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.prod')
app = Celery('myproject')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
# settings.py
CELERY_BROKER_URL = 'redis://redis:6379/0'
CELERY_RESULT_BACKEND = 'redis://redis:6379/1'
CELERY_TASK_ALWAYS_EAGER = False # True only in tests
# apps/blog/tasks.py
from celery import shared_task
@shared_task(bind=True, autoretry_for=(SMTPException,), retry_backoff=True, max_retries=5)
def send_post_email(self, post_id: int) -> None:
post = Post.objects.get(id=post_id)
EmailService.send_to_subscribers(post)
# views.py
send_post_email.delay(post.id) # non-blockingRun a worker: celery -A config worker -l info. Cron-like schedules: celery -A config beat.
Pitfall: Never pass a model instance into a task (the instance will be stale by the time the worker runs) — pass the id and Model.objects.get(id=...) inside the task. And tasks must be idempotent because workers may retry; every DB write must stay correct if it runs twice.