Belajar Django - Background Tasks & Celery
Episode 13 of 27

Belajar Django - Background Tasks & Celery

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.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

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.

Arsitektur Celery

Celery bekerja dengan tiga komponen:

100%
  1. Producer — aplikasi Django mengirim task (pesan) ke broker.
  2. Broker — Redis atau RabbitMQ; antrian yang menampung pesan.
  3. Worker — proses terpisah yang mengambil pesan dan menjalankan task.

Dengan Redis sudah terpasang dari episode 12, kita tidak perlu layanan baru untuk broker.

Setup Celery

Install Celery dan redis client
pip install celery redis

Buat konfigurasi Celery di project:

Pythondevblog/celery.py
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:

Pythondevblog/__init__.py
from .celery import app as celery_app
 
__all__ = ("celery_app",)

Dan setting-nya:

Pythonsettings.py - Celery
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 = True

CELERY_RESULT_BACKEND menyimpan hasil task. CELERY_TASK_SEND_SENT_EVENT = True penting untuk monitoring Flower (episode 22).

Buat blog/tasks.py:

Pythonblog/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.

Memanggil Task

Pythonblog/views.py - kirim task
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.

Menjalankan Worker

Worker adalah proses terpisah yang wajib berjalan di environment development:

Jalankan Celery worker
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:

Worker development
celery -A devblog worker --loglevel=info --pool=solo

Result Backend dan Async Result

Kadang perlu tahu hasil task di kode lain. AsyncResult membaca result backend:

PythonMembaca hasil task
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.

Beat Scheduler: Job Terjadwal

celery beat adalah scheduler bawaan untuk task periodik. Aktifkan di settings:

Pythonsettings.py - beat schedule
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:

Pythonblog/tasks.py - scheduled task
@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:

Jalankan worker + beat
celery -A devblog worker --loglevel=info &
celery -A devblog beat --loglevel=info

Warning

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.

Django 6.1: Async Email untuk Pendekatan Tanpa Celery

Jika workload asinkron kalian sederhana, Django 6.1 membawa email settings berbasis dictionary dan mendukung backend email async. Untuk kasus ringan, cukup:

Pythonsettings.py - email config (Django 6.1)
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.

Penutup

Inti yang harus dibawa pulang:

  • Celery = producer (Django) → broker (Redis) → worker → result backend.
  • @shared_task + .delay() memindahkan pekerjaan lambat keluar dari request.
  • Argumen task harus JSON-serializable (kirim id, bukan objek).
  • celery beat untuk task terjadwal; jalankan satu beat per environment.
  • CELERY_TASK_SEND_SENT_EVENT = True untuk monitoring (episode 22).
  • Email settings 6.1 cocok untuk kasus ringan; Celery untuk workload nyata.

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!

Belajar Django - Background Tasks & Celery | Belajar Django