[python] 다중 처리를 사용하는 셀러리 병렬 분산 작업

CPU 집약적 인 셀러리 작업이 있습니다. 이 작업을 더 빠르게 수행하기 위해 많은 EC2 인스턴스에서 모든 처리 능력 (코어)을 사용하고 싶습니다 (다중 처리가 포함 된 셀러리 병렬 분산 작업- 제 생각에는 ) .

용어, 스레딩 , 멀티 프로세싱 , 분산 컴퓨팅 , 분산 병렬 처리는 내가 더 잘 이해하기 위해 노력하고있어 모든 용어이다.

예제 작업 :

  @app.task
  for item in list_of_millions_of_ids:
      id = item # do some long complicated equation here very CPU heavy!!!!!!! 
      database.objects(newid=id).save()

위의 코드 (가능한 경우 예제 포함)를 사용하여 클라우드에서 사용 가능한 모든 컴퓨터에 걸쳐 모든 컴퓨팅 CPU 성능을 활용하여이 하나의 작업을 분할하도록 허용함으로써 Celery를 사용하여이 작업을 배포하는 방법은 무엇입니까?



답변

당신의 목표는 :

  1. 작업을 여러 머신에 배포 (분산 컴퓨팅 / 분산 병렬 처리)
  2. 모든 CPU (다중 처리 / 스레딩)에 지정된 컴퓨터의 작업을 배포합니다.

셀러리는이 두 가지를 매우 쉽게 할 수 있습니다. 가장 먼저 이해해야 할 것은 각 셀러리 작업자가 기본적 으로 시스템에서 사용 가능한 CPU 코어 수만큼 작업을 실행 하도록 구성 되어 있다는 것입니다.

동시성은 작업을 동시에 처리하는 데 사용되는 프리 포크 작업자 프로세스의 수입니다. 이러한 모든 작업이 바쁘면 새 작업이 처리되기 전에 작업 중 하나가 완료 될 때까지 기다려야합니다.

기본 동시성 수는 해당 시스템 (코어 포함)의 CPU 수이며 -c 옵션을 사용하여 사용자 지정 번호를 지정할 수 있습니다. 최적의 수는 여러 요인에 따라 달라 지므로 권장되는 값은 없지만 작업이 대부분 I / O 바운드 인 경우이를 늘릴 수 있습니다. 실험에 따르면 CPU 수를 두 배 이상 추가하는 것은 거의 발생하지 않습니다. 효과적이고 대신 성능을 저하시킬 가능성이 있습니다.

즉, 각 개별 작업은 다중 CPU / 코어를 사용하기 위해 다중 처리 / 스레딩을 사용하는 것에 대해 걱정할 필요가 없습니다. 대신 셀러리는 사용 가능한 각 CPU를 사용하기에 충분한 작업을 동시에 실행합니다.

그 과정에서 다음 단계는 .NET Framework의 일부 하위 집합 처리를 처리하는 작업을 만드는 것 list_of_millions_of_ids입니다. 여기에는 몇 가지 옵션이 있습니다. 하나는 각 작업이 단일 ID를 처리하도록하는 것이므로 N 개의 작업을 실행합니다 N == len(list_of_millions_of_ids). 이렇게하면 한 명의 작업자가 일찍 끝나고 그냥 기다리는 경우가 없기 때문에 작업이 모든 작업에 균등하게 분배됩니다. 작업이 필요한 경우 대기열에서 ID를 가져올 수 있습니다. 셀러리를 사용하여 (John Doe가 언급했듯이) 이것을 할 수 있습니다 group.

tasks.py :

@app.task
def process_id(item):
    id = item #long complicated equation here
    database.objects(newid=id).save()

그리고 작업을 실행하려면 :

from celery import group
from tasks import process_id

jobs = group(process_id.s(item) for item in list_of_millions_of_ids)
result = jobs.apply_async()

또 다른 옵션은 목록을 작은 조각으로 나누고 그 조각을 작업자에게 배포하는 것입니다. 이 접근 방식은 일부 작업자가 작업을 계속하는 동안 대기중인 작업자가있을 수 있기 때문에 일부주기를 낭비 할 위험이 있습니다. 그러나 셀러리 문서 에서는 이러한 우려가 종종 근거가 없다고 말합니다 .

일부 사람들은 작업을 청킹하면 병렬 처리가 저하 될 것이라고 걱정할 수 있지만 바쁜 클러스터에서는 거의 해당되지 않으며 메시징 오버 헤드를 피하고 있기 때문에 성능이 크게 향상 될 수 있습니다.

따라서 목록을 청크하고 각 작업에 청크를 배포하면 메시징 오버 헤드가 줄어들어 성능이 더 우수하다는 것을 알 수 있습니다. 한 번에 하나의 ID를 수행하는 대신 각 ID를 계산하고 목록에 저장 한 다음 DB에 전체 목록을 추가하는 방식으로 데이터베이스에 대한 부하를 약간 줄일 수도 있습니다. . 청크 접근 방식은 다음과 같습니다.

tasks.py :

@app.task
def process_ids(items):
    for item in items:
        id = item #long complicated equation here
        database.objects(newid=id).save() # Still adding one id at a time, but you don't have to.

작업을 시작하려면 :

from tasks import process_ids

jobs = process_ids.chunks(list_of_millions_of_ids, 30) # break the list into 30 chunks. Experiment with what number works best here.
jobs.apply_async()

어떤 청킹 크기가 최상의 결과를 제공하는지 약간 실험 할 수 있습니다. 메시지 오버 헤드를 줄이면서 작업자가 다른 작업자보다 훨씬 빨리 청크를 완료하고 할 일이없는 상태로 대기하는 일이 없도록 크기를 충분히 작게 유지하는 최적의 지점을 찾고 싶습니다.


답변

유통의 세계에서 무엇보다도 기억해야 할 것은 단 하나뿐입니다.

조기 최적화는 모든 악의 근원입니다. D. Knuth 작성

분명하게 들리지만 이중 확인을 배포하기 전에 최상의 알고리즘을 사용하고 있습니다 (존재하는 경우 …). 하지만 배포를 최적화하는 것은 다음 세 가지 간의 균형을 맞추는 작업입니다.

  1. 영구 매체에서 데이터 쓰기 / 읽기,
  2. 매체 A에서 매체 B로 데이터 이동,
  3. 데이터 처리,

컴퓨터는 처리 장치에 가까울수록 (3) 더 빠르고 효율적으로 (1), (2) 만들어집니다. 클래식 클러스터의 순서는 다음과 같습니다. 네트워크 하드 드라이브, 로컬 하드 드라이브, RAM, 내부 처리 장치 영역 … 요즘 프로세서는 일반적으로 코어라고하는 독립적 인 하드웨어 처리 장치의 앙상블로 간주 될 수있을만큼 정교 해지고 있습니다. 스레드 (2)를 통해 데이터 (3). 코어가 너무 빨라서 하나의 스레드로 데이터를 보낼 때 컴퓨터 성능의 50 %를 사용하고 코어에 2 개의 스레드가 있으면 100 %를 사용한다고 상상해보십시오. 코어 당 2 개의 스레드를 하이퍼 스레딩이라고하며 OS는 하이퍼 스레드 코어 당 2 개의 CPU를 볼 수 있습니다.

프로세서에서 스레드를 관리하는 것을 일반적으로 멀티 스레딩이라고합니다. OS에서 CPU를 관리하는 것을 일반적으로 다중 처리라고합니다. 클러스터에서 동시 작업을 관리하는 것을 일반적으로 병렬 프로그래밍이라고합니다. 클러스터에서 종속 작업을 관리하는 것을 일반적으로 분산 프로그래밍이라고합니다.

그렇다면 병목은 어디에 있습니까?

  • In (1) : 상위 레벨에서 스트리밍을 시도합니다 (예를 들어 네트워크 하드 드라이브가 느린 경우 먼저 로컬 하드 드라이브에 저장하는 경우 처리 장치에 더 가까운 것).
  • (2)에서 : 이것은 가장 일반적인 것입니다. 배포에 필요하지 않은 통신 패킷을 피하거나 “즉시”패킷을 압축하십시오 (예를 들어 HD가 느린 경우 “배치 계산”메시지 만 저장하고 중간 결과 RAM).
  • In (3) : 완료되었습니다! 모든 처리 능력을 마음대로 사용하고 있습니다.

셀러리는 어때?

Celery는 분산 프로그래밍을위한 메시징 프레임 워크로, 통신용 브로커 모듈 (2)과 지속성 용 백엔드 모듈 (1)을 사용합니다. 즉, 가능한 경우 대부분의 병목 현상을 방지하기 위해 구성을 변경할 수 있습니다. 네트워크에서만 가능합니다. 먼저 단일 컴퓨터에서 최고의 성능을 얻으려면 코드를 프로파일 링하십시오. 그런 다음 기본 구성으로 클러스터에서 셀러리를 사용하고 다음을 설정합니다 CELERY_RESULT_PERSISTENT=True.

from celery import Celery

app = Celery('tasks',
             broker='amqp://guest@localhost//',
             backend='redis://localhost')

@app.task
def process_id(all_the_data_parameters_needed_to_process_in_this_computer):
    #code that does stuff
    return result

실행하는 동안 좋아하는 모니터링 도구를 열고 rabbitMQ에 기본값을 사용하고 셀러리에 꽃을, cpus에 top을 사용하면 결과가 백엔드에 저장됩니다. 네트워크 병목 현상의 예는 작업 대기열이 너무 커져 실행이 지연되는 것입니다. 병목 현상이 다른 곳에 있지 않은 경우 모듈 또는 셀러리 구성을 변경할 수 있습니다.


답변

group셀러리 작업을 사용하지 않으 시겠습니까?

http://celery.readthedocs.org/en/latest/userguide/canvas.html#groups

기본적 ids으로 청크 (또는 범위)로 나누고 group.

특정 셀러리 작업의 결과를 집계하는 것과 같이 좀 더 정교한 경우 chord비슷한 목적 으로 작업을 성공적으로 사용했습니다 .

http://celery.readthedocs.org/en/latest/userguide/canvas.html#chords

settings.CELERYD_CONCURRENCY합리적이고 감당할 수있는 숫자로 늘리면 셀러리 작업자가 작업이 끝날 때까지 그룹이나 화음으로 계속 실행합니다.

참고 : kombu과거에는 많은 수의 작업에 작업자를 재사용하는 데 문제가 있었던 버그로 인해 지금 수정되었는지 모르겠습니다. 그럴 수도 있지만 그렇지 않다면 CELERYD_MAX_TASKS_PER_CHILD를 줄이십시오.

내가 실행하는 단순화되고 수정 된 코드를 기반으로 한 예제 :

@app.task
def do_matches():
    match_data = ...
    result = chord(single_batch_processor.s(m) for m in match_data)(summarize.s())

summarize모든 single_batch_processor작업의 결과를 얻습니다 . 모든 작업은 Celery 작업자에서 실행되며이를 kombu조정합니다.

지금은 그것을 얻을 : single_batch_processorsummarizeALSO 셀러리하지 작업, 정기적으로 기능해야 -이 병렬화되지 않습니다, 그렇지 않으면 물론 (나는 그것이 셀러리 작업이 아니라면에서도 확인 코드 생성자가 그것을 받아 들일 것 아니에요).


답변

더 많은 셀러리 작업자를 추가하면 작업 실행 속도가 확실히 빨라질 것입니다. 하지만 또 다른 병목 현상이있을 수 있습니다 : 데이터베이스. 동시 삽입 / 업데이트를 처리 할 수 ​​있는지 확인하십시오.

질문과 관련하여 : EC2 인스턴스에 다른 프로세스를 celeryd. 필요한 작업자 수에 따라 더 많은 인스턴스를 추가 할 수 있습니다.


답변