Django Signals vs Celery Tasks 2026: Khi Nào Sử Dụng Cái Nào
Tìm hiểu khi nào nên sử dụng Django signals so với Celery tasks. Bao gồm xử lý event đồng bộ vs bất đồng bộ, ảnh hưởng hiệu suất, và câu hỏi phỏng vấn cho lập trình viên Django.

Django signals và Celery tasks đều xử lý các event trong ứng dụng Django, nhưng chúng giải quyết các vấn đề khác nhau. Signals được thực thi đồng bộ trong chu kỳ request-response, trong khi Celery chuyển công việc sang background workers. Việc chọn sai công cụ dẫn đến response chậm, race conditions, hoặc kiến trúc phức tạp không cần thiết.
Sử dụng Django signals cho các side effects nhẹ, đồng bộ mà phải hoàn thành trước response. Sử dụng Celery cho bất cứ thứ gì mất hơn 100ms, liên quan đến dịch vụ bên ngoài, hoặc có thể thất bại độc lập với request chính.
Cách Django Signals Hoạt Động Bên Trong
Django signals triển khai observer pattern. Khi một model được lưu, xóa, hoặc khi request bắt đầu hoặc kết thúc, Django gửi một signal. Bất kỳ hàm nào được kết nối với signal đó sẽ thực thi ngay lập tức, trong cùng một database transaction và cùng một thread.
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.cache import cache
from .models import Product
@receiver(post_save, sender=Product)
def invalidate_product_cache(sender, instance, **kwargs):
# Chạy đồng bộ sau khi Product.save() commit
cache_key = f"product:{instance.id}"
cache.delete(cache_key)
# Cũng invalidate listing danh mục
cache.delete(f"category:{instance.category_id}:products")Signal receiver chạy bên trong cùng một database transaction. Nếu transaction rollback, các hiệu ứng của signal handler vẫn còn. Điều này quan trọng cho cache invalidation: cache bị xóa, nhưng thay đổi database không bao giờ được lưu. Kết quả là cache miss tải lại dữ liệu cũ.
Django 5.2 giới thiệu transaction.on_commit() để giải quyết vấn đề này. Bọc logic signal trong on_commit đảm bảo thực thi chỉ sau khi commit thành công:
# signals.py
from django.db import transaction
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Product
from .tasks import reindex_product
@receiver(post_save, sender=Product)
def handle_product_saved(sender, instance, **kwargs):
# Trì hoãn cho đến khi transaction commit thành công
transaction.on_commit(
lambda: reindex_product.delay(instance.id)
)Pattern này kết nối signals và Celery: signal được kích hoạt đồng bộ, nhưng công việc thực tế xảy ra bất đồng bộ sau khi transaction commit.
Mô Hình Thực Thi Celery Task
Celery chạy các tasks trong các tiến trình worker riêng biệt. Một Django view đưa task vào hàng đợi bằng cách serialize các đối số của nó thành message broker (Redis hoặc RabbitMQ). Worker nhận message và thực thi task độc lập với HTTP request ban đầu.
# tasks.py
from celery import shared_task
from django.core.mail import send_mail
from .models import Order
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def send_order_confirmation(self, order_id: int):
"""Gửi email xác nhận sau khi đặt hàng."""
try:
order = Order.objects.select_related('user').get(id=order_id)
send_mail(
subject=f"Đơn hàng #{order.id} Đã Xác Nhận",
message=f"Đơn hàng của bạn với tổng {order.total} đã được đặt.",
from_email="orders@example.com",
recipient_list=[order.user.email],
)
except Order.DoesNotExist:
# Đơn hàng đã bị xóa trước khi task chạy, bỏ qua
return
except Exception as exc:
# Retry khi có lỗi tạm thời (SMTP timeout, v.v.)
raise self.retry(exc=exc)Task chạy trong một tiến trình khác, có thể trên một máy khác. Nó không có quyền truy cập vào ngữ cảnh request ban đầu. Nếu đơn hàng bị xóa giữa thời điểm enqueue và thực thi, task phải xử lý điều đó một cách hợp lý.
Celery 5.4 (phiên bản ổn định hiện tại tính đến tháng 9 năm 2026) đã thêm cải tiến task typing và tích hợp Django tốt hơn thông qua django-celery-results để lưu trữ kết quả task trong database.
So Sánh Đặc Điểm Hiệu Suất
Signals thêm độ trễ vào request. Mỗi receiver được kết nối thực thi trước khi response được trả về. Với ba receivers trung bình 50ms mỗi cái, request mất thêm 150ms.
Celery thêm độ trễ tối thiểu (thường 1-5ms để enqueue), nhưng giới thiệu eventual consistency. Email được gửi "cuối cùng," không phải trước response.
| Yếu Tố | Django Signals | Celery Tasks |
|---|---|---|
| Thực thi | Đồng bộ, cùng tiến trình | Bất đồng bộ, tiến trình worker |
| Ảnh hưởng độ trễ | Thêm vào thời gian request | ~1-5ms overhead enqueue |
| Xử lý lỗi | Phá vỡ request | Retry độc lập |
| Phạm vi transaction | Bên trong transaction | Bên ngoài transaction |
| Gọi dịch vụ bên ngoài | Chặn response | Chạy trong background |
| Độ phức tạp | Setup tối thiểu | Yêu cầu broker + workers |
Khi Nào Signals Là Lựa Chọn Đúng
Signals phù hợp với các tình huống mà side effect phải hoàn thành trước khi tiếp tục và khi lỗi nên hủy bỏ hoạt động.
Cache invalidation hoạt động tốt với signals. Khi Product được cập nhật, biểu diễn được cache phải được invalidate ngay lập tức. Cache cũ thậm chí trong vài giây gây ra hiển thị giá hoặc số lượng tồn kho sai.
# signals.py
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.cache import cache
from .models import Product
@receiver([post_save, post_delete], sender=Product)
def clear_product_cache(sender, instance, **kwargs):
cache.delete(f"product:{instance.id}")
cache.delete(f"product:{instance.slug}")
# Xóa bất kỳ cache danh sách nào có thể bao gồm sản phẩm này
cache.delete_pattern(f"products:category:{instance.category_id}:*")Audit logging nơi log phải tồn tại trước khi response được trả về cũng phù hợp với signals. Nếu admin xóa user, audit log nên ghi lại việc xóa đó một cách nguyên tử với hoạt động xóa.
Denormalization của các trường trên các model liên quan được hưởng lợi từ signals. Khi trạng thái Order thay đổi thành "shipped," cập nhật trường denormalized last_shipped_at trên Customer xảy ra ngay lập tức.
Sẵn sàng chinh phục phỏng vấn Django?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Khi Nào Celery Tasks Là Lựa Chọn Đúng
Celery phù hợp với các tình huống liên quan đến dịch vụ bên ngoài, tính toán chạy lâu, hoặc các hoạt động có thể thất bại và retry độc lập.
Gửi email không nên chặn requests. Máy chủ SMTP có độ trễ không thể đoán trước, đôi khi timeout, và giới hạn rate người gửi. Celery task sẽ retry email thất bại mà không ảnh hưởng đến trải nghiệm người dùng.
Tạo PDF cho hóa đơn hoặc báo cáo mất vài giây. Tạo đồng bộ làm UI cảm thấy bị hỏng. Enqueue task, trả về trạng thái "processing," và cho phép người dùng tải xuống khi sẵn sàng.
Gọi API bên thứ ba thuộc về Celery. Gọi Stripe, Twilio, hoặc bất kỳ dịch vụ bên ngoài nào đồng bộ liên kết thời gian response với thời gian của họ. Khi API của họ chậm lại, ứng dụng cũng chậm lại.
# tasks.py
from celery import shared_task
import stripe
from .models import Subscription
@shared_task(bind=True, max_retries=5, retry_backoff=True)
def sync_stripe_subscription(self, subscription_id: int):
"""Đồng bộ subscription local với trạng thái Stripe."""
try:
sub = Subscription.objects.get(id=subscription_id)
stripe_sub = stripe.Subscription.retrieve(sub.stripe_id)
sub.status = stripe_sub.status
sub.current_period_end = stripe_sub.current_period_end
sub.save(update_fields=['status', 'current_period_end'])
except stripe.error.RateLimitError as exc:
# Stripe giới hạn rate, retry với exponential backoff
raise self.retry(exc=exc)
except Subscription.DoesNotExist:
return # Subscription đã bị xóa local, không có gì để đồng bộTham số retry_backoff=True trong Celery 5.x kích hoạt exponential backoff: retry đầu tiên sau 1 giây, sau đó 2, sau đó 4, đến tối đa. Điều này ngăn việc hammering API bị rate-limited.
Pattern Hybrid: Signals Kích Hoạt Tasks
Kiến trúc robust nhất sử dụng signals cho phối hợp nhẹ, ngay lập tức và Celery cho công việc nặng thực tế.
# signals.py
from django.db import transaction
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Order
from .tasks import (
send_order_confirmation,
notify_warehouse,
update_analytics,
)
@receiver(post_save, sender=Order)
def on_order_created(sender, instance, created, **kwargs):
if not created:
return # Chỉ xử lý đơn hàng mới
# Enqueue tất cả công việc async sau khi transaction commit
transaction.on_commit(lambda: (
send_order_confirmation.delay(instance.id),
notify_warehouse.delay(instance.id),
update_analytics.delay('order_created', instance.id),
))Pattern này đảm bảo:
- Order tồn tại trong database trước khi tasks chạy (transaction đã commit)
- Không có công việc async nào xảy ra nếu transaction rollback
- HTTP response được trả về ngay lập tức
- Mỗi task có thể thất bại và retry độc lập
Câu Hỏi Phỏng Vấn về Django Signals vs Celery
Phỏng vấn kỹ thuật cho vị trí Django thường khám phá sự khác biệt này. Dưới đây là các câu hỏi phân biệt ứng viên có kinh nghiệm với những người chỉ học thuộc tài liệu.
Q: Signal handler gửi email. Transaction database rollback. Điều gì xảy ra?
Email vẫn được gửi. Signal handlers thực thi trong transaction, không phải sau commit. Email được gửi đi, nhưng thay đổi database kích hoạt nó không bao giờ được lưu. Để sửa điều này, bọc lệnh gọi email trong transaction.on_commit(), hoặc tốt hơn, enqueue Celery task bên trong on_commit().
Q: Làm thế nào để ngăn signal firing trong các hoạt động bulk?
bulk_create(), bulk_update(), và QuerySet.update() của Django không kích hoạt signals. Đây là cố ý để tối ưu hiệu suất. Nếu cần hành vi signal, lặp và lưu từng cái (chấp nhận chi phí hiệu suất) hoặc dispatch signal thủ công sau hoạt động bulk.
Q: Celery task tham chiếu request.user. Tại sao nó thất bại?
Celery tasks chạy trong tiến trình riêng biệt không có quyền truy cập vào ngữ cảnh HTTP request. Truyền user ID làm đối số task và lấy đối tượng User bên trong task. Không bao giờ truyền các instance model Django trực tiếp làm đối số task, vì chúng có thể trở nên cũ hoặc không serialize được.
Q: Làm thế nào để test signal handlers một cách cô lập?
Disconnect signal, gọi hàm handler trực tiếp với các đối số mock, sau đó reconnect. Các phương thức Signal.disconnect() và Signal.connect() của Django cho phép điều này. Decorator @factory.django.mute_signals từ thư viện factory_boy cũng giúp bằng cách tạm thời tắt tiếng signals trong quá trình test setup.
# tests.py
from django.test import TestCase
from django.db.models.signals import post_save
from unittest.mock import patch, MagicMock
from .models import Product
from .signals import invalidate_product_cache
class SignalTests(TestCase):
def test_cache_invalidation_called(self):
# Disconnect để ngăn firing tự động
post_save.disconnect(invalidate_product_cache, sender=Product)
try:
product = Product.objects.create(name="Test", price=100)
with patch('myapp.signals.cache') as mock_cache:
# Gọi handler trực tiếp
invalidate_product_cache(
sender=Product,
instance=product,
created=True
)
mock_cache.delete.assert_called()
finally:
# Reconnect cho các test khác
post_save.connect(invalidate_product_cache, sender=Product)Các Anti-Pattern Phổ Biến Cần Tránh
Circular signal chains xảy ra khi Signal A kích hoạt save trên Model B, mà signal của nó kích hoạt save trên Model A. Đệ quy tiếp tục cho đến khi stack overflow hoặc recursion guard dừng nó. Thiết kế signals để terminal: chúng quan sát và phản ứng, nhưng không kích hoạt thêm các thay đổi có thể quan sát được.
Tính toán nặng trong signals chặn mọi request kích hoạt signal. Signal resize hình ảnh, tạo thumbnails, hoặc gọi API bên ngoài nên enqueue Celery task thay thế.
Lỗi signal im lặng ẩn bugs. Theo mặc định, Django bắt exceptions trong signal handlers và ghi log chúng, cho phép request tiếp tục. Điều này che giấu lỗi. Cân nhắc xem handler thất bại có nên hủy bỏ hoạt động hay thực sự là best-effort.
# settings.py
# Làm cho exception signal handler propagate (fail fast trong development)
DEBUG = True # Trong dev, exceptions sẽ propagate tự nhiên
# Trong production, sử dụng Sentry hoặc tương tự để bắt signal errors
import sentry_sdk
sentry_sdk.init(dsn="...")Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Framework Quyết Định cho Xử Lý Event Django
Việc chọn giữa signals và Celery trở nên đơn giản với cách tiếp cận có hệ thống:
- Sử dụng signals khi: hoạt động phải hoàn thành trước response, mất dưới 50ms, chỉ liên quan đến hoạt động database hoặc cache local, và lỗi nên hủy bỏ hoạt động chính
- Sử dụng Celery khi: hoạt động có thể xảy ra sau đó, liên quan đến dịch vụ bên ngoài, mất hơn 100ms, được hưởng lợi từ logic retry, hoặc không nên chặn người dùng
- Sử dụng signals kích hoạt Celery khi: cần xác nhận ngay lập tức nhưng công việc thực tế là async, hoặc khi nhiều async tasks nên được kích hoạt từ một database event
- Tránh signals hoàn toàn khi: logic đủ phức tạp để đảm bảo các lệnh gọi service rõ ràng, khi debugging signal chains trở nên khó khăn, hoặc khi hiệu ứng tương tự có thể đạt được với một lệnh gọi phương thức đơn giản
Tính năng background tasks của Django 6.0 cung cấp một giải pháp trung gian: hỗ trợ async task tích hợp sẵn mà không có overhead vận hành của Celery. Đối với các dự án greenfield bắt đầu vào cuối năm 2026, hãy đánh giá xem background tasks native của Django có đáp ứng yêu cầu không trước khi thêm Celery.
Bạn có tìm ra lỗi trong Django không?
Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Viết bởi
Anthony Fillion-MailletNgười sáng lập SharpSkill
Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.
Cập nhật ngày 2 tháng 9, 2026
Thẻ
Chia sẻ
Bài viết liên quan

Django va Celery: Xu ly Tac vu Bat dong bo va Cau hoi Phong van 2026
Huong dan Django Celery day du voi ma code thuc te, task routing, lap lich Celery Beat, cau hinh production va cau hoi phong van ky thuat 2026.

Async View và ASGI trong Django 2026: Hiệu năng và câu hỏi phỏng vấn
Phân tích chuyên sâu về async view và ASGI trong Django 2026: cơ chế hoạt động bên dưới, server nên triển khai, async ORM cùng cái bẫy SynchronousOnlyOperation, kèm theo các câu hỏi phỏng vấn.

Django 5.2: Middleware Tùy Chỉnh và Xử Lý Signal trong Phỏng Vấn Kỹ Thuật Python
Hướng dẫn chi tiết về custom middleware và signal handling trong Django 5.2 cho phỏng vấn kỹ thuật. Bao gồm request pipeline, async middleware, post_save, pre_save và custom signals với mã mẫu thực tế.