Celery task 디버깅 방법에 대해 알아 보겠습니다.
목적
- Celery task 디버깅에 대해 알아보기
- rdb를 이용한 celery task 디버깅
방법1 : Eager Mode
Celery 태스크 디버깅은 쉽지 않습니다.. Celery 태스크는 일반적으로 비동기적으로 실행되기 때문에, 전통적인 디버깅 방법(예: 브레이크포인트 설정)을 사용하는 것이 어려울 수 있습니다. 이 장에서는 Celery 태스크를 디버깅하는 몇 가지 방법을 살펴보고, 그 중 'Eager Mode'를 사용하는 방법에 대해 집중적으로 설명하겠습니다.
'Eager Mode'는 Celery 태스크를 동기적으로 실행하게 하여, 일반 코드를 디버깅할 때와 같은 방식으로 태스크 내 코드를 디버깅할 수 있게 해줍니다. 태스크가 메시지 큐로 전송되지 않고 즉시 실행됩니다. 따라서, 브레이크포인트나 print
문을 사용한 디버깅이 가능해집니다.
설정 방법
FastAPI 애플리케이션의 설정에 CELERY_TASK_ALWAYS_EAGER
키를 True
로 설정함으로써 Eager Mode를 활성화할 수 있습니다. 이는 테스트 중이나 디버깅할 때 유용하게 사용될 수 있습니다. 단, 기본적으로 이 옵션은 False
로 설정되어 있으며, 실수로 프로덕션 환경에서 활성화하는 것을 방지합니다.
Eager Mode 활성화 예시
tasks.py
파일 내에 Celery 애플리케이션 인스턴스를 생성하는 부분에 Eager Mode를 활성화하는 설정을 추가할 수 있습니다.
from celery import Celery
celery_app = Celery('tasks', broker='redis://localhost:6379/0')
# Eager Mode 활성화
celery_app.conf.task_always_eager = True
이 설정을 추가하면, Celery는 태스크를 비동기적으로 메시지 큐에 보내는 대신, 코드가 호출될 때 즉시(동기적으로) 태스크를 실행합니다. 이는 로컬 개발 환경에서 태스크의 동작을 테스트하거나 디버깅할 때 유용합니다.
Eager Mode 환경 변수를 통한 설정
환경 변수를 사용하여 Eager Mode를 설정하려면, 먼저 Celery 설정을 환경 변수에서 읽어들일 수 있도록 해야 합니다. 이는 FastAPI 애플리케이션의 설정 관리 방법에 따라 달라질 수 있습니다. 예를 들어, dotenv
를 사용하거나 FastAPI의 Settings
모델을 통해 환경 변수를 로드할 수 있습니다.
환경 변수를 통해 Eager Mode를 설정하는 예시는 다음과 같습니다. 이 방법을 사용하기 위해서는 Celery 설정을 환경 변수에서 로드하는 추가적인 코드가 필요할 수 있습니다.
.env
파일 또는 환경에 다음을 추가합니다:CELERY_TASK_ALWAYS_EAGER=True
- Celery 애플리케이션 설정에서 이 환경 변수를 참조하도록 합니다(이 부분은 프로젝트의 설정 로딩 방식에 따라 달라질 수 있습니다).
위의 방법은 개발 및 테스트 단계에서 태스크의 동작을 보다 쉽게 이해하고 문제를 진단할 수 있게 해줍니다. 그러나, 실제 프로덕션 환경에서는 Eager Mode가 활성화되지 않도록 주의해야 합니다.
장점
- 워커(worker), 메시지 브로커(message broker), 결과 백엔드(result backend) 프로세스를 실행할 필요가 없습니다. 즉, Uvicorn 서버 프로세스 내에서 직접 코드를 디버깅할 수 있습니다. 이는 디버깅과 테스팅을 간소화합니다.
단점
- 이 모드가 활성화되어 있을 때,
task.delay()
는AsyncResult
대신EagerResult
를 반환합니다. 이는 실제 문제를 가릴 수 있으며, 비동기 실행 환경에서 발생할 수 있는 문제를 놓칠 수 있습니다.
결론
Eager Mode는 Celery 태스크의 동작을 이해하고 문제를 식별하는 데 있어 초기 단계로서 매우 유용합니다. 그러나, 실제 비동기 환경에서 발생할 수 있는 이슈를 포착하기 위해서는, 실제 Celery 환경(비동기 모드)에서도 테스트를 진행하는 것이 중요합니다. Eager Mode를 사용하는 동안 발견되지 않은 문제들은 실제 운영 환경에서 다시 테스트하고 검증해야 할 수도 있습니다. 따라서, Eager Mode는 디버깅과 테스팅의 한 부분으로 사용되어야 하며, 전체 프로세스의 일부로 간주되어야 합니다.
방법2. Pycharm
PyCharm을 사용하면 강력한 디버깅 기능을 활용하여 FastAPI 애플리케이션과 Celery 태스크를 디버깅할 수 있습니다. PyCharm을 사용하는 경우, Docker를 사용하고 있지 않든, 사용하고 있든 여러 방법으로 디버깅이 가능합니다.
Docker를 사용하지 않는 경우
Docker를 사용하지 않고 로컬에서 애플리케이션을 실행하는 경우, 다음 단계를 따라 Celery 태스크의 디버깅을 도울 수 있습니다:
- 메시지 브로커와 `result backend` 설정이 되었는지 확인하고, 관련 서비스가 실행 중인지 확인합니다.
- FastAPI용 디버거를 시작합니다(PyCharm에서 Python 실행 구성을 설정해야 합니다).
- Celery 워커용 디버거를 시작합니다(이것 역시 PyCharm에서 Python 실행 구성을 설정해야 합니다).
- FastAPI 서버와 Celery 워커의 콘솔 출력을 볼 수 있습니다. 이제 태스크 코드에 중단점을 설정할 수 있고, 평소처럼 디버깅을 진행할 수 있습니다.
Docker를 사용하는 경우
앱이 Docker를 통해 실행되는 경우, PyCharm은 웹 앱에 대한 디버깅은 수행하지만 Celery 워커에 대해서는 항상 제대로 작동하지 않을 수 있습니다. 제대로 작동하지 않을 경우, 몇 가지 방법을 시도할 수 있습니다:
- FastAPI 앱의 디버거를 시작하고 PyCharm 디버그 창에서 웹 앱을 디버깅합니다.
- 터미널에서 Celery 태스크를 디버깅하기 위해 rdb(다음에 설명할 방법)를 사용합니다.
이 방법은 VS Code와 같은 다른 에디터를 사용하는 경우에도 적용됩니다. 즉, 에디터를 사용하여 웹 앱을 디버깅하고 rdb를 사용하여 태스크를 디버깅할 수 있습니다.
장점
- PyCharm 하나의 IDE에서 FastAPI 앱과 Celery 태스크를 모두 디버깅할 수 있습니다.
- Celery 태스크는 비동기 모드로 실행되므로,
task.delay()
는AsyncResult
를 반환합니다.
단점
- 이 방법은 PyCharm에서만 작동합니다.
- PyCharm 설정이 다소 복잡할 수 있습니다.
- `worker`, `message broker`, `result backend`를 실행해야 합니다.
참고사항
PyCharm에서 개발할 때, task_always_eager
를 True
로 설정하여 전체 프로세스를 더 간단하게 만들 수 있습니다. 이 설정을 사용하면 실제로 태스크를 비동기로 실행하지 않고, 요청을 처리하는 동안 태스크를 동기적으로 실행하여 바로 결과를 반환받을 수 있습니다. 이는 개발 중 디버깅을 훨씬 쉽게 만들어 줍니다.
방법3 : rdb
rdb
는 Celery 태스크를 터미널에서 직접 디버깅할 수 있는 강력한 도구입니다. 이를 사용하면 IDE 없이도 Celery 태스크를 효율적으로 디버깅할 수 있습니다. rdb
를 사용하려면 Telnet이 설치되어 있어야 합니다.
rdb
사용의 장단점
장점
- IDE 없이도 터미널에서 Celery 태스크를 디버깅할 수 있습니다. 이는 개발 환경이 제한적인 상황이나 터미널을 선호하는 개발자에게 유용할 수 있습니다.
단점
- 초보자에게는 사용법이 다소 어려울 수 있습니다.
rdb
는 Python의 내장 디버거인pdb
와 유사하게 작동하지만, Telnet을 통해 리모트 디버깅을 수행한다는 점에서 차이가 있습니다.
사용 예시
준비 작업
- 먼저,
task_always_eager
가True
로 설정되어 있지 않은지 확인해야 합니다. 이는 태스크를 비동기적으로 실행하는 대신 동기적으로 실행하게 만들어,rdb
를 사용한 디버깅을 방해할 수 있습니다. - 프로젝트와 함께 Docker를 사용하는 경우,
Dockerfile
에 Telnet을 설치해야 합니다. 예를 들어, FastAPI를 사용하는compose/local/fastapi/Dockerfile
에 다음과 같은 내용을 추가합니다:RUN apt-get update \ && apt-get install -y telnet netcat \ && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/*
Celery 태스크에 rdb
추가
project/users/tasks.py
파일에 있는 Celery 태스크에rdb.set_trace()
를 추가하여 디버깅 포인트를 설정합니다.from celery import shared_task @shared_task def divide(x, y): from celery.contrib import rdb rdb.set_trace() import time time.sleep(5) return x / y
디버깅 시작
- Docker 이미지를 다시 빌드하고 컨테이너를 실행합니다.
$ docker compose up -d --build $ docker compose logs -f
- 새 터미널 창에서 Python 쉘을 통해 태스크를 실행합니다.
$ docker compose exec web bash (container)$ python >>> from main import app >>> from project.users.tasks import divide >>> divide.delay(1, 2)
- 로그에서
rdb
디버거의 대기 메시지와 포트 번호를 확인합니다. - 다른 터미널 창에서 Telnet을 사용하여 해당 포트에 연결하고
rdb
세션을 시작합니다.$ docker compose exec celery_worker bash (container)$ telnet 127.0.0.1 <port_number>
- 참고로, port_number는
docker compose logs -f
명령어를 통해서Remote Debugger:6915: Waiting for client...
와 같이 출력됩니다.
- 참고로, port_number는
- 이제
rdb
디버깅 세션에서pdb
와 유사한 명령어를 사용하여 디버깅할 수 있습니다.
처음 컴포즈로 컨테이너를 기동하고 logs
명령어에 -f
옵션으로 로그를 계속해서 확인 할 수 있습니다.
그리고 web 컨테이너에 접속하여 task를 생성하면 아래와 같이 로그를 확인 할 수 있을 것입니다.
fastapi-celery-project-celery_worker-1 | [2024-01-04 13:55:45,219: INFO/MainProcess] Task project.users.tasks.divide[cc086baa-731f-4075-aa10-bd3f73f87d8b] received
fastapi-celery-project-celery_worker-1 | [2024-01-04 13:55:45,252: WARNING/ForkPoolWorker-16] Remote Debugger:6915: Ready to connect: telnet 127.0.0.1 6915
fastapi-celery-project-celery_worker-1 |
fastapi-celery-project-celery_worker-1 | Type `exit` in session to continue.
fastapi-celery-project-celery_worker-1 |
fastapi-celery-project-celery_worker-1 | Remote Debugger:6915: Waiting for client...
이후 다른 터미널을 열어줍니다. 이는 Telnet을 통해서 디버깅을 진행하기 위해서입니다.
$ docker compose exec celery_worker bash
(container)$ telnet 127.0.0.1 6915
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
> /app/project/users/tasks.py(9)divide()
-> import time
(Pdb)
이제 디버깅이 가능합니다.
(Pdb) args
x = 1
y = 2
(Pdb) help
Documented commands (type help <topic>):
========================================
EOF cl display j next run unalias where
a clear down jump p rv undisplay
alias commands enable l pp s unt
args condition h list r source until
b d help ll restart step up
break debug ignore longlist return tbreak w
bt disable interact n retval u whatis
Miscellaneous help topics:
==========================
exec pdb
Undocumented commands:
======================
c cont continue exit q quit
c(continue)
를 타이핑하여서 디버그 쉘에서 나갈 수 있습니다.
이후 컴포즈 로그 파일을 확인해 보면 아래와 같은 내용을 자세히 보면 확인 할 수 있습니다.
fastapi-celery-project-celery_worker-1 | [2024-01-04 13:56:18,920: WARNING/ForkPoolWorker-16] Remote Debugger:6915: Now in session with 127.0.0.1:43634.
fastapi-celery-project-celery_worker-1 | [2024-01-04 13:56:28,345: WARNING/ForkPoolWorker-16] Remote Debugger:6915: Session with 127.0.0.1:43634 ended.
fastapi-celery-project-celery_worker-1 | [2024-01-04 13:56:33,352: INFO/ForkPoolWorker-16] Task project.users.tasks.divide[cc086baa-731f-4075-aa10-bd3f73f87d8b] succeeded in 48.17242526903283s: 0.5
그리고 debug가 완료 되었다면 python code에서 rdb.set_trace()
부분을 삭제하거나 꼭 주석 처리해주어야 합니다.
정리
- 디버깅이 끝나면
rdb.set_trace()
호출을 주석 처리하거나 삭제해야 합니다. 이는 실제 운영 환경에서 불필요한 디버깅 세션을 방지하기 위함입니다. rdb
는 특히 비동기 태스크의 복잡한 문제를 해결하는 데 유용한 도구입니다. 하지만, 사용법을 익히는 데 시간이 걸릴 수 있으므로, 문서를 참고하거나 예제를 통해 연습하는 것이 좋습니다.
'프로그래밍 언어 > 파이썬' 카테고리의 다른 글
Celery와 FastAPI - 8 (0) | 2024.03.27 |
---|---|
Celery와 FastAPI - 7 (1) | 2024.03.26 |
Celery와 FastAPI - 5 (1) | 2024.03.24 |
Celery와 FastAPI - 4 (0) | 2024.03.24 |
Celery와 FastAPI - 3 (0) | 2024.03.23 |