Celery 데몬으로 관리하기

지금까지는 로컬에서 테스트 하기 위해 celery -A <proj> worker -l info 를 통해서 실행했다. 수동으로 프로세스를 관리하지 않고 데몬으로 하기 위한 필요한 작업들을 정리하자.

간단하게 정리하자면

  • Celery가 사용할 설정 파일 작성
  • 데몬으로 실행하기 위한 service 파일 작성

이라고 할 수 있겠다.

Celery 설정파일 작성하기

파일의 위치는 크게 상관 없다. 나의 경우 uwsgi의 설정파일 처럼 관리하기 위해서 /etc/celery/sites/의 경로에 생성하고 사용하는 중이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# vi /etc/celery/sites/proj.conf

# 노드, 워커 개수 (보통 한 개로 시작한다)
CELERYD_NODES="worker1"

# celery 명령어의 절대 경로 위치
# 가상환경 내에 설치된 경로인지 꼭 확인하자
CELERY_BIN="/home/ubuntu/.pyenv/versions/sample_proj/bin/celery"

# 앱 인스턴스 (예: Proj)
# Django에서 설정한 Celery app 이름을 확인하자
CELERY_APP="proj"

# manage.py 호출 방법
CELERYD_MULTI="multi"

# 워커로 전달할 추가 명령어 옵션
CELERYD_OPTS="--time-limit=300 --concurrency=2"

# - %n 노드 이름의 첫 부분
# - %I 현재 자식 프로세스 인덱스
# prefork pool을 사용할 때 경쟁상태(race condition)을 피하기 위해 중요
CELERYD_PID_FILE="/tmp/celery-%n.pid"
CELERYD_LOG_FILE="/tmp/celery-%n%I.log"
CELERYD_LOG_LEVEL="INFO"

/etc/celery/sites/ 폴더에 프로젝트 이름으로 된 conf 파일을 만들고 위와 같이 내용으르 작성했다.

Celery의 설정 파일을 작성했다면 데몬으로 만들기 위해서 서비스 파일을 만들어야 한다.

systemd로 만들기

위치는 마찬가지로 uwsgi.service가 있는 폴더에서 작업했다. 관리자 권한이 필요할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# vi /etc/systemd/system/celery.service

[Unit]
Description=Celery Service
After=network.target

[Service]
Type=forking

# Celery의 설정파일 경로
EnvironmentFile=/etc/conf.d/celery

# 실행될 디렉터리 - app 프로젝트의 경로를 적어주면 된다
WorkingDirectory=/opt/celery

# 실제 구동할때 입력되는 명령어이다
# 이전에 생성했던 conf 파일의 설정을 통해 괄호 안에 값이 들어가게 된다
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} \
-A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS}'
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} \
--pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} \
-A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS}'
StandardError=syslog

[Install]
WantedBy=multi-user.target

나는 conf 파일에서 사용하지 않았던 설정이 있었기 때문에 몇몇의 옵션은 삭제하고 파일을 만들었다.

Celery의 설정(conf)파일과 데몬(celery.service)파일을 만들었다면 데몬에 등록을 하고 구동하면 된다.

데몬 등록 및 구동

1
2
sudo systemctl enable celery
sudo systemctl start celery

데몬 재시작

1
2
sudo systemctl daemon-reload
sudo systemctl restart celery

celery.service 파일에 변경점이 있다면 deamon-reload를 해주고 재시작하자

지금까지 Celery를 데몬에 등록해서 사용하는 방법을 정리했다. 앞서서 RabbitMQ에 대해서도 정리를 간략하게 했지만 Celery는 메세지를 가지고 작업을 수행하기 때문에 RabbitMQ와 같은 메세지 브로커와 함께 사용되어야 한다는 것을 잊으면 안된다.

[참고]
http://docs.celeryproject.org/en/latest/userguide/daemonizing.html#init-script-celeryd
https://www.pincoin.co.kr/book/1/32/

Share

장고 쿼리셋의 select_related

debug_toolbar를 설치해서 SQL 항목을 보기 전까지 select_related와 prefetch_related는 아직은 필요하지 않은 것들이라고 생각했다. 데이터가 많지 않아 드라마틱한 속도는 볼 수 없었지만 duplicated를 보지 않는 것만으로도 효과는 충분히 보았다고 생각한다.

장고 ORM을 사용할 수 있다는 것은 정말 큰 장점이 되는 것 같다. 물론 이 ORM이 어떻게 동작하는 지 이해하고 쓴다면 더할 나위 없이 좋겠지만 차차 알아보기로 하고 지금은 select_related에 대해서 정리해 보려고 한다.

일단 아래와 같은 모델이 있다고 가정하고 시작해보자.

1
2
3
4
5
6
7
8
# 게시글
class Post(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey('user', on_delete=models.CASCADE)

# 글쓴이
class User(models.Model):
name = models.CharField(max_length=10)

모든 게시글에 대해서 글쓴이를 순회하는 출력하는 HTML 코드가 있다면 아래와 같을 것이다.

1
2
3
{% for post in post_list %}
<p>글쓴이 : {{ post.author }}</p>
{% endfor %}

정말 흔하게 작성하는 코드이기 때문에 큰 의문이 들지 않을 수도 있지만, 장고의 QuerySet의 특성 한 가지를 이해한다면 비효율적으로 실행되는 것을 이해할 수 있게된다.

여기서 필요한 쿼리셋의 특성 한 가지는

  • QuerySet은 기본적으로 지연평가(Lazy Evaluation)를 한다는 것

이다.

지연평가란?

파이썬 코드 상에서 QuerySet을 만드는 작업은 DB에 아무런 작업을 수행하지 않는 다는 것이다. 즉, all() / filter() 과 같이 QuerySet을 생성하는 작업들은 DB에 접근하는 작업이 아니며 실제 값을 뽑는 - 연산 - 과정이 실행되기 전까지 DB query가 일어나지 않는다.

이러한 지연평가로 인해서 views.py에서 쿼리셋을 생성하는 작업들을 반복해서 수행해도 DB 호출을 최소화 할 수 있다는 장점을 가질 수 있다. 하지만 템플릿 상에서 반복문을 통해 외래키를 참조하는 쿼리셋을 평가하는 작업을 반복하게 된다면 SQL문의 중복이 발생할 수 있다는 단점도 있다.

다시 코드로 돌아와서..
쿼리셋의 지연평가를 이해하면

1
2
3
{% for post in post_list %}
<p>글쓴이 : {{ post.author }}</p>
{% endfor %}

부분이 모든 post 객체에 대해서 실행되기 때문에 post 객체의 개수 만큼 쿼리가 중복 발생하는 것을 파악할 수 있다.

문제를 해결하기 위해선 views.py 에서 post_list를 구할때 select_related를 사용하면 된다.

1
post_list = Post.object.select_related('user').all()

post 객체를 만들때 외래키 관계에 있는 객체를 한 번에 다 가져온다. HTML 상에서 지연평가를 하지 않고 DB상에서 Inner join을 통해 한 번의 접근만으로 필요한 정보를 가져오게 되는 셈이다.

지금까지 select_related에 대해서 의식의 흐름대로 정리해 보았다. 이제 막 찾아보고 처음으로 사용했던 지라 제대로 소화하지 못한 것 같아서 prefetch_related 정리할 때 다시 해야겠다.


[참고]
https://wayhome25.github.io/django/2017/06/20/selected_related_prefetch_related/
https://tech.peoplefund.co.kr/2017/11/03/django-db-optimization.html
https://blog.leop0ld.org/posts/database-access-optimization/

Share

RabbitMQ의 간단한 명령어 정리

RabbitMQ를 apt를 통해 설치하면 커맨드 라인 툴인 rabbitmqctl이 같이 설치된다. 이 툴을 통해 RabbitMQ를 관리할 수 있다.

시작 / 재시작 / 중지

1
sudo rabbitmqctl start / restart / stop

상태 보기

1
sudo rabbitmqctl status

현재 시스템에 설치된 RabbitMQ의 상태정보를 보여준다.

계정 관련 명령어

유저 목록

1
sudo rabbitmqctl list_users

생성된 유저의 목록을 출력한다. 유저의 태그도 같이 출력된다.

유저 생성 / 삭제

1
2
3
4
5
# 생성
sudo rabbitmqctl add_user <유저명> <비밀번호>

# 삭제
sudo rabbitmqctl delete_user <유저명>

유저에 태그 할당

1
2
3
4
sudo rabbitmqctl set_user_tags <유저명> <태그>

# management 플러그인 - 어드민 계정 만들기
sudo rabbitmqctl set_user_tags admin administrator

해당 유저의 태그를 설정한다. 15672 기본 포트를 사용하는 rabbitmq-management 플러그인에 접속 가능한 계정을 만들려면 administrator 태그를 주어야 한다.


[참고]
https://teragoon.wordpress.com/2012/01/13/rabbitmqctl-%EC%82%AC%EC%9A%A9%EB%B2%95/
https://www.rabbitmq.com/rabbitmqctl.8.html

Share