Memindahkan pekerjaan berat ke latar belakang dengan Celery: arsitektur Celery + Redis, menulis dan memanggil tasks, result backend, beat scheduler untuk job terjadwal, serta praktik async email dan laporan di blog devblog.

Di episode 12 kita membuat pembacaan cepat dengan cache. Tapi ada satu kelas masalah yang tidak bisa diselesaikan cache: operasi yang lambat dan tidak perlu ditunggu user — mengirim email, membuat laporan, memproses upload. Di episode ini kita mengenalkan Celery, distributed task queue yang menjadi standar de facto background processing di ekosistem Django.
Mengapa topik ini penting? Karena waktu respons adalah produk. Ketika submit form memicu pengiriman email, user tidak seharusnya menunggu SMTP yang lambat. Dengan Celery, pekerjaan itu dilempar ke antrian dan dikerjakan worker secara asinkron — respons HTTP kembali cepat, dan pekerjaan tetap selesai.
Celery bekerja dengan tiga komponen:
Dengan Redis sudah terpasang dari episode 12, kita tidak perlu layanan baru untuk broker.
pip install celery redisBuat konfigurasi Celery di project:
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "devblog.settings")
app = Celery("devblog")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()config_from_object(..., namespace="CELERY") membaca setting yang berawalan CELERY_. autodiscover_tasks() mencari tasks.py di setiap app — konvensi yang membuat setiap app bisa punya task sendiri.
Inisialisasi Celery bersama Django di devblog/__init__.py:
from .celery import app as celery_app
__all__ = ("celery_app",)Dan setting-nya:
CELERY_BROKER_URL = "redis://127.0.0.1:6379/2"
CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/3"
CELERY_TASK_SERIALIZER = "json"
CELERY_TIMEZONE = "Asia/Jakarta"
CELERY_TASK_TRACK_STARTED = True
CELERY_TASK_SEND_SENT_EVENT = TrueCELERY_RESULT_BACKEND menyimpan hasil task. CELERY_TASK_SEND_SENT_EVENT = True penting untuk monitoring Flower (episode 22).
Buat blog/tasks.py:
from celery import shared_task
from django.core.mail import send_mail
@shared_task
def send_comment_notification(post_title, author_email, comment_body):
send_mail(
subject=f"Komentar baru di {post_title}",
message=f"Komentar: {comment_body}",
from_email="noreply@devblog.test",
recipient_list=[author_email],
)
return {"sent_to": author_email}@shared_task membuat task tanpa harus tahu instance app Celery mana — ia terikat ke app yang terautodiscover, sehingga task bisa dipindah antar project. Task hanyalah fungsi Python biasa, tetapi dipanggil dengan .delay() atau .apply_async() agar berjalan di worker.
from .tasks import send_comment_notification
def add_comment(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.author = request.user
comment.save()
send_comment_notification.delay(
post.title, post.author.email, comment.body
)
return redirect("blog:post_detail", pk=post.pk)
...send_comment_notification.delay(...) mengirim pesan ke broker dan kembali seketika — view tidak menunggu email terkirim. Email dikirim worker di proses terpisah.
Tip
Pastikan argumen task JSON-serializable: string, int, list, dict — bukan objek model. Mengirim objek model ke task adalah kesalahan klasik yang akan error saat serializer JSON dimuat (dan berisiko data basi jika model berubah sebelum worker jalan). Kirim post.pk, lalu query lagi di dalam task.
Worker adalah proses terpisah yang wajib berjalan di environment development:
celery -A devblog worker --loglevel=info-A devblog menunjuk modul Celery (devblog/celery.py). Di terminal lain, jalankan view yang memicu task, dan perhatikan worker memprosesnya. Untuk development, --pool=solo berguna karena tanpa fork proses:
celery -A devblog worker --loglevel=info --pool=soloKadang perlu tahu hasil task di kode lain. AsyncResult membaca result backend:
from celery.result import AsyncResult
result = send_comment_notification.delay(...)
print(result.id) # id task
print(result.ready()) # False jika masih jalan
print(result.get(timeout=5)) # blokir sampai selesai (hati-hati)result.get() memblokir sampai task selesai — meniadakan manfaat asinkron. Gunakan hanya untuk task yang benar-benar harus sinkron, atau simpan result.id untuk di-poll nanti.
celery beat adalah scheduler bawaan untuk task periodik. Aktifkan di settings:
from celery.schedules import crontab
CELERY_BEAT_SCHEDULE = {
"weekly-summary-email": {
"task": "blog.tasks.send_weekly_summary",
"schedule": crontab(day_of_week="monday", hour=7, minute=0),
},
"cleanup-expired-sessions": {
"task": "blog.tasks.cleanup_sessions",
"schedule": crontab(hour=2, minute=0),
},
}Definisikan task yang dijadwalkan di blog/tasks.py:
@shared_task
def cleanup_sessions():
from django.contrib.sessions.models import Session
expired = Session.objects.filter(expire_date__lt=timezone.now())
count, _ = expired.delete()
return {"deleted": count}Lalu jalankan beat bersama worker:
celery -A devblog worker --loglevel=info &
celery -A devblog beat --loglevel=infoWarning
Jalankan satu proses celery beat saja per environment. Dua beat yang berjalan bersamaan akan mengirim task duplikat untuk schedule yang sama. Di produksi multi-server (episode 24), jalankan beat di satu node khusus, atau pertimbangkan django-celery-beat yang menyimpan schedule di database.
Jika workload asinkron kalian sederhana, Django 6.1 membawa email settings berbasis dictionary dan mendukung backend email async. Untuk kasus ringan, cukup:
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.example.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "noreply@example.com"
EMAIL_HOST_PASSWORD = "secret"Pada 6.1, konfigurasi di atas bisa dirangkum sebagai dictionary settings. Namun untuk antrian pekerjaan yang benar-benar scalable — retry, scheduled jobs, distributed — Celery tetap jawabannya. Batasan email-email ringan inilah perbandingan yang kita buat di episode 22.
Inti yang harus dibawa pulang:
@shared_task + .delay() memindahkan pekerjaan lambat keluar dari request.celery beat untuk task terjadwal; jalankan satu beat per environment.CELERY_TASK_SEND_SENT_EVENT = True untuk monitoring (episode 22).Di episode 14 selanjutnya kita mengasah senjata terpenting untuk performa: Advanced ORM & Query Optimization — Q objects, F expressions, subquery, window functions, dan pembasmian N+1 dengan query yang benar-benar efisien. Sampai jumpa di episode 14!