Study/django

[인프런] 실리콘밸리 엔지니어와 함께하는 샐러리(Celery) 학습 정리 2

bluebamus 2024. 6. 30. 17:40

2024.06.29 - [Study/django] - [인프런] 실리콘밸리 엔지니어와 함께하는 샐러리(Celery) 학습 정리 1

 

1. Scheduling and Periodic Task

   1) Celery Monitoring Tool, Flower에 대해 알아보기

      - 참고 페이지 : https://flower.readthedocs.io/en/latest/

      - 실시간으로 작업 실행, worker 상태, 운영 지표를 제공받을 수 있다.

      - 사용시 장점 :

         - Task Monitoring : task progress와 히스토리를 모니터링 할 수 있다. 

         - Worker Monitoring : worker의 상태를 모니터링 할 수 있다.

         - Real-time Updates : task를 실행 할 때마다 실시간 모니터링을 할 수 있다.

         - Task Management : task 관리를 할 수 있다. 취소, 재시작 등의 작업을 스케줄링되었거나 작업 중인 작업에 지시할 수 있다.

         - Worker management : worker 또 시작 정지 등의 관리를 할 수 있다.

         - Broker Moniroring : redis나 rabbitMQ와 같은 broker도 모니터링 할 수 있다.

         - Secure Access : 기본 HTTP 인증, OAuth2 등의 메커니즘이 지원된다.

 

         - docker-compose 업데이트

version: "3.9"

services:
  app:
    build:
      context: .
      args:
        - DEV=true
    ports:
      - "8000:8000"
    volumes:
      - ./app:/app
    depends_on:
      - redis
    command: >
      sh -c "
        python manage.py makemigrations &&
        python manage.py migrate &&
        python manage.py runserver 0.0.0.0:8000
      "

  redis:
    image: redis:latest

  celery:
    build:
      context: .
    volumes:
      - ./app:/app
    command: celery --app=worker worker -l INFO -Q celery,celery:1,celery:2
    # command: celery --app=worker worker -l INFO -Q queue1
    # command: celery --app=worker worker -l INFO
    # command: celery --app=worker worker -l INFO -P gevent  # for windows
    depends_on:
      - redis

  celery-standalone:
    build:
      context: standalone_celery
    volumes:
      - ./standalone_celery:/app
    command: celery --app=main worker -l INFO -Q celery3,celery:4,celery:5
    # command: celery --app=main worker -l INFO -Q queue2
    # command: celery --app=main worker -l INFO
    depends_on:
      - redis

  flower:
    image: mher/flower:latest
    ports:
      - "5555:5555"
    environment:
      - CELERY_BROKER_URL=redis://redis:6379/0
    depends_on:
      - celery
      - celery-standalone

 

   2) Celery Scheduling에 대해 알아보기

      - 관련 페이지 : https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html#starting-the-scheduler

      - Time-based : celery-beat을 사용해 카운트다운이나 ETA를 사용한다.

      - Event-based : adhoc 개념이다.

      - Dependency-based : 체이닝과 그룹핑 task이다.

      - 개발용에는 명령어 끝에 -B를 추가하면 된다.

      - 프로덕트용에는 따로 적용을 해줘야 한다. 공식 페이지를 참고한다.

 

      1. celery.py에 설정하는 방법

app.conf.beat_schedule = {
    "add-every-5-seconds": {
        "task": timedelta(seconds=5),
        "args": (10, 10),
        # "kwargs": {"key": "value"},
        # "options": {"queue": "celery"},
    },
    "add-every-minute": {
        "task": "worker.tasks.add",
        "scheduel": crontab(minute="*"),
        "args": (20, 20),
    },
}

 

   3) Django Celery Beat과 Django Celery Result에 대해 알아보기

      - celery-beat 설치 관련 문서 : https://github.com/celery/django-celery-beat?tab=readme-ov-file#installation

      - celery-result 설치 관련 문서 : https://github.com/celery/django-celery-results?tab=readme-ov-file#installing

 

      1. 설치 

pip install django-celery-beat django-celery-results

 

      2. settings.py에 추가

INSTALLED_APPS = [
	...
    "django_celery_beat",
    "django_celery_results",
]

 

      3. migration 실행

python manage.py makemigrations
python manage.py migrate

 

      4. docker-compose 업데이트

         - celery-standalone은 데이터베이스 설정이 안되어 있기 때문에 저장이 안된다. 주석 처리한다.

services:
  app:
    build:
      context: .
      args:
        - DEV=true
    ports:
      - "8000:8000"
    volumes:
      - ./app:/app
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: root
      POSTGRES_PASSWORD: admin
      POSTGRES_HOST: db
    depends_on:
      - redis
    command: >
      sh -c "
        python manage.py makemigrations &&
        python manage.py migrate &&
        python manage.py runserver 0.0.0.0:8000
      "

  redis:
    image: redis:latest
    ports:
      - "6379:6379"

  celery:
    build:
      context: .
    volumes:
      - ./app:/app
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: root
      POSTGRES_PASSWORD: admin
      POSTGRES_HOST: db
    command: celery --app=worker worker -l INFO -Q celery, celery,1, celery,2
    # command: celery --app=worker worker -l INFO -Q queue1
    # command: celery --app=worker worker -l INFO
    # command: celery --app=worker worker -l INFO -P gevent  # for windows
    depends_on:
      - redis

  # celery-standalone:
  #   build:
  #     context: standalone_celery
  #   volumes:
  #     - ./standalone_celery:/app
  #   environment:
  #     POSTGRES_DB: app
  #     POSTGRES_USER: root
  #     POSTGRES_PASSWORD: admin
  #     POSTGRES_HOST: db
  #   command: celery --app=main worker -l INFO -Q celery,3, celery,4, celery,5
  #   # command: celery --app=main worker -l INFO -Q queue2
  #   # command: celery --app=main worker -l INFO
  #   depends_on:
  #     - redis

  flower:
    image: mher/flower:master
    ports:
      - "5555:5555"
    environment:
      - CELERY_BROKER_URL=redis://redis:6379/0
    depends_on:
      - celery
      # - celery-standalone

  celery-beat:
    build:
      context: .
    volumes:
      - ./app:/app
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: root
      POSTGRES_PASSWORD: admin
      POSTGRES_HOST: db
    command: celery -A worker beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler
    depends_on:
      - redis
      - db

  db:
    image: postgres:latest
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: root
      POSTGRES_PASSWORD: admin

 

       5. settings.py 업데이트

# db변경
# DATABASES = {
#     "default": {
#         "ENGINE": "django.db.backends.sqlite3",
#         "NAME": BASE_DIR / "db.sqlite3",
#     }
# }

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql_psycopg2",
        "NAME": os.environ.get("POSTGRES_DB"),
        "USER": os.environ.get("POSTGRES_USER"),
        "PASSWORD": os.environ.get("POSTGRES_PASSWORD"),
        "HOST": os.environ.get("POSTGRES_HOST"),
        "PORT": "5432",
    }
}

# celery 설정 추가
# Celery Configuration Options
CELERY_TIMEZONE = TIME_ZONE
CELERY_TASK_TRACK_STARTED = True
CELERY_TASK_TIME_LIMIT = 30 * 60
CELERY_BROKER_URL = os.environ.get("CELERY_BROKER", "redis://127.0.0.1:6379/0")
# CELERY_RESULT_BACKEND = os.environ.get("CELERY_BACKEND", "redis://127.0.0.1:6379/0")
# CELERY_BROKER_URL = os.environ.get("CELERY_BROKER", "redis://redis:6379/0")
# CELERY_RESULT_BACKEND = os.environ.get("CELERY_BACKEND", "redis://redis:6379/0")

CELERY_RESULT_BACKEND = "django-db"
CELERY_TASK_SERIALIZER = "json"
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"

 

       6. flower 버그 수정

          - 최신 버전의 이미지와 패키지를 사용하면 flower가 데이터 동기화 실패를 한다는 문제가 있었다.

 

          - docker 이미지 

  flower:
    image: mher/flower:master

 

          - poetry 버전

celery = "^5.2.0"
django-celery-beat = "^2.3.0"
django-celery-results = "^2.0.1"

 

   4) 동적으로 tasks들을 레지스터해 보기

      - 관련 페이지 : https://docs.celeryq.dev/en/stable/reference/celery.html#celery.Celery.autodiscover_tasks

      - 사용하는 목적이 여러개가 있겠지만, 특정 app, 폴더에만 있는 tasks만 동작하게 만드는 경우가 적절한것 같다.

      - autodiscover_tasks 분석:

autodiscover_tasks(packages=None, related_name='tasks', force=False)

         - packages : 디렉토리의 위치 정의

         - related_name : 기존과 다른 파일명일 경우 정의

 

         1. 2개의 app을 가지고 있는 my_project의 경우

my_project/
├── manage.py
├── app1/
│   ├── __init__.py
│   └── tasks.py
├── app2/
│   ├── __init__.py
│   └── tasks.py
└── config/
    ├── __init__.py
    └── celery.py

 

            - 2개의 디렉토리 위치 정의

               - config.celery는 생략 가능

               - 각 app의 하위 폴더에도 task가 있는 경우, 재귀로 자동으로 찾는다. 정의를 해줘도 된다.

# config/celery.py
from celery import Celery
from celery.schedules import crontab

app = Celery('my_project')
app.config_from_object('config.settings', namespace='CELERY')

# 프로젝트 루트 디렉토리와 각 앱의 경로를 전달
app.autodiscover_tasks(['app1', 'app2', 'config.celery'])

# 다른 Celery 설정...

 

          2. 각 폴더에 있는 tasks의 파일 이름이 다른 경우

            - related_name 사용

# config/celery.py
from celery import Celery
from celery.loaders.base import autodiscover_tasks

app = Celery('my_project')
app.config_from_object('config.settings', namespace='CELERY')

# 파일명을 지정하여 autodiscover_tasks 호출
app.autodiscover_tasks(['app1', 'app2', 'app3'], related_name='my_tasks')

 

            - task_modules 사용

app.autodiscover_tasks(['app1', 'app2', 'app3'], task_modules=['my_tasks'])

 

            - 각 폴더별 이름이 다른 경우

app.autodiscover_tasks(
    ['app1', 'app2', 'app3'],
    related_name={
        'app1': 'my_tasks',
        'app2': 'your_tasks',
        'app3': 'their_tasks',
    }
)