Celery Beat — 주기적 태스크 스케줄링

Celery Beat가 어떻게 주기적 태스크를 발행하는지, crontab 문법과 timedelta 설정, django-celery-beat로 DB에서 동적으로 스케줄을 관리하는 방법을 설명한다.

Seobway · · 11분

Beat란 무엇인가

Celery Beat는 주기적 태스크를 예약하고 발행하는 스케줄러 프로세스다.[1]

Beat 자체는 태스크를 실행하지 않는다.
정해진 시각이 되면 브로커에 메시지를 발행하고, 실제 실행은 Worker가 담당한다.

%% desc: Beat의 역할 — 시계처럼 정해진 시각에 메시지를 발행하고 Worker가 실행
flowchart LR
  subgraph BEAT["Celery Beat"]
    CLOCK["⏰ 내부 클락\n1초마다 due 체크"]
    SCHEDULE["스케줄 저장소\n(파일 또는 DB)"]
    CLOCK -- "due 태스크 확인" --> SCHEDULE
  end

  BROKER["메시지 브로커\n(Redis)"]
  WORKER["Celery Worker"]

  BEAT -- "메시지 발행" --> BROKER
  BROKER --> WORKER
  WORKER -- "태스크 실행" --> RESULT[결과]

중요: Beat는 항상 단 하나만 실행해야 한다.
두 개 이상 실행하면 같은 태스크가 중복 발행된다.

스케줄 유형

1. timedelta — 일정 간격 반복

from datetime import timedelta

CELERY_BEAT_SCHEDULE = {
    "check-health-every-30s": {
        "task": "myapp.tasks.health_check",
        "schedule": timedelta(seconds=30),
    },
    "cleanup-every-hour": {
        "task": "myapp.tasks.cleanup_expired_sessions",
        "schedule": timedelta(hours=1),
    },
}

2. crontab — 특정 시각 지정

from celery.schedules import crontab

CELERY_BEAT_SCHEDULE = {
    # 매일 오전 8시
    "daily-report": {
        "task": "myapp.tasks.generate_daily_report",
        "schedule": crontab(hour=8, minute=0),
    },
    # 월요일 오전 9시
    "weekly-digest": {
        "task": "myapp.tasks.send_weekly_digest",
        "schedule": crontab(hour=9, minute=0, day_of_week="monday"),
    },
    # 매월 1일 자정
    "monthly-invoice": {
        "task": "myapp.tasks.generate_invoices",
        "schedule": crontab(hour=0, minute=0, day_of_month="1"),
    },
    # 평일 오전 9~18시, 매 15분마다
    "business-hours-sync": {
        "task": "myapp.tasks.sync_data",
        "schedule": crontab(
            minute="*/15",
            hour="9-18",
            day_of_week="mon-fri",
        ),
    },
}

crontab 파라미터

파라미터 의미 예시
minute 분 (0-59) "*/15" → 15분마다
hour 시 (0-23) "9-18" → 9시~18시
day_of_week 요일 (0=일, mon-fri) "monday"
day_of_month 날짜 (1-31) "1,15" → 1일, 15일
month_of_year 월 (1-12) "1,7" → 1월, 7월

3. solar — 일출/일몰 기반

from celery.schedules import solar

CELERY_BEAT_SCHEDULE = {
    "at-sunrise": {
        "task": "myapp.tasks.morning_task",
        "schedule": solar("sunrise", latitude=37.5665, longitude=126.9780),
    },
}

스케줄 저장 방식

Beat는 마지막 실행 시각을 저장해 재시작 후에도 중복 실행을 방지한다.

기본 방식 — 파일

celery -A myproject beat --schedule=/var/run/celerybeat-schedule

celerybeat-schedule 파일에 마지막 실행 시각을 저장한다.
설정을 변경하려면 Beat를 재시작해야 한다.

django-celery-beat — DB 기반 동적 스케줄

pip install django-celery-beat
python manage.py migrate
# settings.py
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"

장점:[2]

%% desc: django-celery-beat의 DB 기반 스케줄 관리 — Admin에서 수정 후 즉시 반영
flowchart TD
  ADMIN["Django Admin\n스케줄 편집 UI"]
  DB["PostgreSQL\nPeriodicTask 테이블"]
  BEAT["Celery Beat\n(DB 폴링)"]
  BROKER["Redis 브로커"]
  WORKER["Worker"]

  ADMIN -- "스케줄 추가/수정/삭제" --> DB
  BEAT -- "변경 감지 (5초마다)" --> DB
  BEAT -- "due 태스크 발행" --> BROKER
  BROKER --> WORKER
# 코드에서 동적으로 스케줄 추가
from django_celery_beat.models import PeriodicTask, CrontabSchedule
import json

schedule, _ = CrontabSchedule.objects.get_or_create(
    minute="0",
    hour="9",
    day_of_week="mon-fri",
    day_of_month="*",
    month_of_year="*",
    timezone="Asia/Seoul",
)

PeriodicTask.objects.create(
    crontab=schedule,
    name="매일 아침 리포트",
    task="myapp.tasks.generate_daily_report",
    args=json.dumps([]),
    kwargs=json.dumps({"send_email": True}),
    enabled=True,
)

태스크에 인자 전달

CELERY_BEAT_SCHEDULE = {
    "weekly-report-kr": {
        "task": "myapp.tasks.generate_report",
        "schedule": crontab(hour=8, minute=0, day_of_week="monday"),
        "args": ("weekly",),
        "kwargs": {"region": "KR", "format": "pdf"},
    },
}

Beat 실행 명령

# 기본 실행
celery -A myproject beat --loglevel=info

# 파일 기반 스케줄 저장 위치 지정
celery -A myproject beat \
  --scheduler celery.beat:PersistentScheduler \
  --schedule /var/run/celerybeat-schedule

# django-celery-beat (DB 기반)
celery -A myproject beat \
  --scheduler django_celery_beat.schedulers:DatabaseScheduler \
  --loglevel=info

자주 하는 실수

Beat를 여러 개 실행

# 잘못된 예: 두 서버에서 동시 실행
server1$ celery -A myproject beat &
server2$ celery -A myproject beat &   # 태스크 중복 발행!

해결: Beat는 반드시 단일 프로세스. 고가용성이 필요하면 Redis 분산 락을 활용한 redbeat 사용을 검토한다.[3]

Worker 없이 Beat만 실행

Beat가 발행하는 메시지를 처리할 Worker가 없으면 메시지가 큐에 쌓인다.
Beat와 Worker는 항상 함께 실행해야 한다.

참고

  1. Celery Periodic Tasks, Celery Docs
  2. django-celery-beat documentation, Read the Docs
  3. redbeat — Redis-based Celery Beat scheduler, GitHub

관련 글