Celery 최적화

Jaeuk Ko·2023년 2월 1일
0

1. Ensuring Operation

하나의 시스템이 제 시간에 처리할 수 있는 데이터의 양은 제한되어 있다. 만약 셀러리에서 하나의 태스크가 완료되는데 10분이 걸리고, 매 분마다 새로운 태스크들이 계속해서 유입된다면, 큐는 절대로 비는 일이 없을 것이다. 이것이 큐의 길이를 모니터링 하는 것이 중요한 그 이유이다.

큐를 모니터링 하는 방법으로는 Munin(현재 큐에 있는 태스크의 수를 그래프로 보여줌)을 사용하는 것이 있다. 만약 큐가 더이상 수용할수 없을 만큼 가득 차게 된다면 즉각적으로 확인할 수 있다. 이를 통해 새로운 worker node를 추가하거나, 불필요한 태스크를 제거하는 등의 조치를 취할 수 있다.

2. 일반적인 세팅

Broker Connection pool

Broker Connection pool은 2.5 버전부터 기본사양으로써 사용이 가능하다.
Broker Connection Pool을 미세 조정함으로써 경쟁 상황을 최소화 할 수 있는데, 이 값들은 broker connection을 사용하는 활성화된 thread의 수에 기반해야 한다.

Using Transient Queues

기본적으로 Celery에 의해 만들어진 queue는 영구적이다. 이 말은, 브로커가 재시작한다 할지라도, 브로커는 태스크가 반드시 수행되도록 message를 디스크에 기록한다는 것이다.

하지만 어떤 경우에는, 이러한 메시지가 손실되어도 괜찮기 때문에 모든 task가 내구성을 가질 필요는 없다. 이러한 태스크에는 "임시 큐"를 생성하여 사용함으로써 퍼포먼스를 증대 시킬 수 있다.

  
from kombu import Exchange, Queue

task_queues = (
    Queue('celery', routing_key='celery'),
    Queue('transient', Exchange('transient', delivery_mode=1),
          routing_key='transient', durable=False),
)

혹은 task_routes를 사용한다

task_routes = {
    'proj.tasks.add': {'queue': 'celery', 'delivery_mode': 'transient'}
}

delery_mode는 queue로의 메세지 전달방식을 바꾼다. 1은 message가 반드시 디스크에 기록 되지 않아도 된다는걸 뜻하며, 2는 반드시 디스크에 기록되어야 함을 의미한다.

queue의 인자를 지정해줌으로써, 태스크를 새로 생성한 큐로 가도록 할 수 있다.

task.apply_async(args, queue='transient')

3.Worker Settings

Prefetch Limits

Prefetch limits는 하나의 worker가 처리할 수 있는 task(message)의 수이다. 만약 prefetch limit이 0일 경우, 작업자는 message를 더 빨리 처리할 수 있는 노드가 있거나 혹은 이 message가 메모리 크기에 맞지 않는 등의 문제를 고려하지 않고 계속해서 message(task)를 처리할 것이다.

worker의 기본 prefetch 카운트 수는, worker_prefetch_multiplier옵션을 통해 지정할 수 있다(기본값 4)

개별 작업의 처리 시간이 긴 경우 prefetch 수는 1로 해야 한다. 즉, 한번에 worker process 하나당 하나의 작업만 예약하도록 해야한다.(복잡한 수학 연산등이 이에 해당)

반면, 개별 작업의 처리시간은 짧지만 처리량이 많거나 왕복 대기시간이 긴 경우 prefetch count가 커야 한다. message들이 미리 가져와져서(prefetced) 메모리에 올라가면 worker는 초당 더 많은 작업을 수행할 수 있다. 이 적절한 값을 찾기 위해서는 여러 실험을 해봐야한다...이러한 상황에는 50 이나 150 등이 좋을수도?!

개별 작업의 처리시간이 긴 작업들과 짧은 작업들이 섞여 있을 경우, 가장 좋은 방법은 2개의 worker node를 사용해서 긴 작업과 짧은작업으로 나눠서 처리해주는 것이다.Routing Tasks

Memory Usage

만약 하나의 worker에서 높은 메모리 점유를 경험한다면, 가장 먼저 이 이슈가 Celery master process에서도 일어나는지 확인해야 한다. Celery master process의 메모리 점유는 구동 된 이후에 계속해서 극적으로 증가하면 안된다. 이러한 일이 발생한다면 이는 메모리 누수일 수 있다.

만약 자식 프로세스에만 메모리 사용량이 높을 경우 해당 task에 문제가 있다는 것이다.

파이썬 프로세스의 메모리 사용에는 "high watermark" 라는 것이 있어 자식 프로세스가 중단되기 전까지는 운영체제에게 메모리를 반환하지 않음을 명심하라. 이말은 즉, 하나의 메모리 사용량이 높은 작업이 있다면 자식프로세스를 재시작 하기 전까지는 영구적으로 자식 프로세스의 메모리 사용을 증가시킬 수 있다는 뜻이다. chunking 로직을 이러한 task에 추가하여 메모리 사용을 줄일 필요가 있다.

Celery worker는 "worker_max_tasks_per_child"와 "worker_max_memory_per_child"을 사용하여 자식 프로세스로부터의 메모리 누수를 줄일 수 있다.

이 세팅들을 너무 낮지 않도록 해야하며, 또한 worker가 자식 프로세스를 재시작 하는데 드는 시간이 task를 처리하는데 드는 시간보다 더 걸리지 않도록 주의해야 한다. 예를 들어 만약 " worker_max_tasks_per_child"의 값을 1로 사용하고, 자식 프로세스가 시작되는데 걸리는 시간이 1초라면, 자식프로세스는 분당 최대 60개의 task만을 처리할 수 있을 것이다. 비슷한 상황은 태스크의 수가 언제나 "worker_max_memory_per_child"보다 클 경우 발생할 수 있다.

profile
망원동 개발자

0개의 댓글