[ray] [Ray 최적화] 액터 제출 병목 현상 해결: Placement Group 번들 정보 캐싱으로 GCS 부하 줄이기
PR 링크: ray-project/ray#64649 상태: Merged | 변경: +4 / -1
들어가며
분산 컴퓨팅 프레임워크 Ray는 대규모 머신러닝 워크로드, 특히 강화 학습(RL), 분산 트레이닝, 전처리/후처리 등에서 액터(Actor) 기반의 병렬 처리를 통해 뛰어난 확장성을 제공합니다. Ray에서 액터는 특정 리소스 요구사항을 가진 작업 단위로, 이들을 효율적으로 스케줄링하고 관리하는 것은 전체 시스템 성능에 지대한 영향을 미칩니다. 특히, 여러 액터가 특정 리소스 제약 조건 내에서 함께 실행되어야 할 때, Ray의 Placement Group(PG)은 이러한 리소스 할당을 추상화하고 관리하는 핵심 기능입니다.
하지만 대규모 Ray 클러스터에서 수많은 액터를 생성하고 제출할 때, 드라이버(Driver)에서 발생하는 액터 제출 속도 병목 현상은 오랫동안 중요한 문제로 지적되어 왔습니다. 이 병목은 주로 액터가 Placement Group 내의 번들(bundle)에 할당될 수 있는지 확인하기 위해 Ray의 중앙 제어 서비스인 GCS(Global Control Store)에 반복적으로 쿼리하는 과정에서 발생했습니다. 이번 PR(ray-project/ray#64446)은 이 문제를 해결하기 위해 Placement Group 생성 시 번들 정보를 로컬에 캐싱하는 간단하지만 매우 효과적인 최적화를 도입했습니다.
문제 상황: GCS 병목 현상
Ray에서 액터를 제출할 때, 드라이버는 해당 액터의 리소스 요구사항이 Placement Group 내의 어떤 번들에 할당될 수 있는지 검증해야 합니다. 이 검증 과정은 액터가 스케줄링 대기 상태로 무한정 머무르는 것을 방지하고, 사용자에게 즉각적인 피드백을 제공하기 위해 필수적입니다. 기존 Ray의 구현 방식은 이 번들 정보를 얻기 위해 매번 GCS에 쿼리하는 방식이었습니다.
문제는 대규모 Ray 클러스터에서 GCS가 이미 수많은 스케줄링 이벤트와 메타데이터 관리 요청으로 매우 바쁘다는 점입니다. 이러한 상황에서 드라이버가 액터 제출 시마다 GCS에 번들 정보를 요청하면, GCS는 이미 쌓여있는 다른 이벤트들을 처리하느라 응답이 지연될 수밖에 없습니다. 이는 액터 제출 속도를 현저히 저하시키는 주요 원인이 되었고, 특히 수천, 수만 개의 액터를 빠르게 생성해야 하는 RL 및 대규모 ML 워크로드에서 심각한 병목으로 작용했습니다.
더욱이, 대부분의 사용 사례에서 드라이버는 Placement Group을 생성할 때 이미 해당 PG의 번들 구성 정보를 알고 있습니다. 즉, 드라이버는 이 정보를 로컬에 가지고 있음에도 불구하고, 불필요하게 원격 서비스인 GCS에 다시 쿼리하는 비효율적인 방식을 사용하고 있었습니다. PR 설명에 따르면, 과거에는 이 정보를 캐싱하는 로직이 있었으나, 여러 리팩토링 과정에서 실수로 제거된 것으로 보입니다.
코드 변경 분석
이번 PR의 핵심 변경사항은 python/ray/util/placement_group.py 파일의 placement_group 함수에 있습니다. 이 함수는 새로운 Placement Group을 생성하고 반환하는 역할을 합니다.
Before
이전 코드에서는 PlacementGroup 객체를 생성할 때 placement_group_id만 전달했습니다. 번들 정보는 객체 내부에서 필요할 때마다 GCS를 통해 조회하는 방식이었습니다.
return PlacementGroup(placement_group_id)
After
변경 후 코드에서는 PlacementGroup 객체를 생성할 때 placement_group_id와 함께 bundle_cache 인자를 추가로 전달합니다. 이 bundle_cache는 placement_group 함수가 이미 알고 있는 bundles 변수로부터 생성됩니다.
return PlacementGroup(
placement_group_id,
bundle_cache=[{k: float(v) for k, v in bundle.items()} for bundle in bundles],
)
변경의 의미
이 변경은 PlacementGroup 객체가 생성되는 시점에 해당 PG의 번들 리소스 요구사항 정보를 bundle_cache라는 형태로 로컬에 저장하도록 합니다. [{k: float(v) for k, v in bundle.items()} for bundle in bundles]와 같은 리스트 컴프리헨션은 bundles 리스트의 각 번들(딕셔너리 형태)을 순회하며, 리소스 값(예: CPU, GPU 개수)을 float 타입으로 변환하여 새로운 딕셔너리 리스트를 생성합니다. 이렇게 캐싱된 정보는 이후 액터 제출 시 번들 용량 검증 과정에서 GCS에 쿼리할 필요 없이 로컬에서 즉시 사용될 수 있게 됩니다.
왜 이 최적화가 좋은가?
이 최적화는 겉보기에는 작은 코드 변경처럼 보이지만, Ray의 대규모 분산 워크로드 성능에 엄청난 영향을 미칩니다. 그 이유는 다음과 같습니다.
-
압도적인 성능 향상: PR 설명에 첨부된 벤치마크 결과 이미지는 이 최적화의 효과를 명확히 보여줍니다. 2000개의 액터를 제출하는 데 걸리는 시간이 기존에는 100초 이상이었으나, 이 변경 후에는 10초 미만으로 단축되었습니다. 이는 수십 배에 달하는 성능 향상으로, GCS와의 불필요한 네트워크 왕복을 제거함으로써 얻어진 결과입니다. 특히 액터 생성 및 소멸이 빈번한 RL 시뮬레이션이나 대규모 분산 학습 환경에서 개발 및 운영 효율성을 크게 높일 수 있습니다.
-
GCS 부하 감소: Ray의 GCS는 클러스터의 핵심 제어 플레인으로, 노드 관리, 스케줄링, 객체 스토어 메타데이터 관리 등 다양한 중요한 작업을 수행합니다. 액터 제출 시마다 발생하는 불필요한 GCS 쿼리가 제거됨으로써, GCS는 다른 핵심 작업에 더 많은 리소스를 할당할 수 있게 됩니다. 이는 전체 Ray 클러스터의 안정성과 응답성을 향상시키는 데 기여합니다.
-
확장성 증대: 액터 제출 속도 병목이 해결됨으로써, Ray는 훨씬 더 많은 수의 액터를 효율적으로 관리하고 스케줄링할 수 있게 됩니다. 이는 대규모 분산 환경에서 복잡한 워크로드를 처리하는 데 필수적인 요소이며, Ray의 핵심 가치인 확장성을 더욱 강화합니다.
-
코드의 의도 명확화 및 효율성: 드라이버가 Placement Group을 생성할 때 이미 번들 정보를 알고 있다는 사실을 활용하여, 불필요한 원격 호출을 제거하고 로컬 캐싱을 통해 정보를 재활용하는 것은 소프트웨어 설계의 모범 사례입니다. 이는 코드의 효율성을 높일 뿐만 아니라, 시스템의 동작 방식을 더 직관적이고 예측 가능하게 만듭니다.
일반적 교훈
이 최적화는 다음과 같은 중요한 소프트웨어 엔지니어링 교훈을 제공합니다.
- 캐싱의 중요성: 원격 서비스 호출은 네트워크 지연 및 서비스 부하로 인해 비용이 많이 듭니다. 가능한 경우, 자주 접근하는 데이터는 로컬에 캐싱하여 성능을 최적화해야 합니다.
- 정보의 재활용: 이미 알고 있거나 쉽게 얻을 수 있는 정보를 불필요하게 다시 조회하지 않도록 시스템을 설계하는 것이 중요합니다.
- 병목 지점 식별: 프로파일링 및 벤치마킹을 통해 시스템의 실제 병목 지점을 정확히 찾아내고, 그 지점을 집중적으로 개선하는 것이 가장 효과적인 최적화 전략입니다.
결론
이번 Ray PR은 단 몇 줄의 코드 변경으로 대규모 분산 워크로드의 액터 스케줄링 성능을 획기적으로 개선한 사례입니다. Placement Group 번들 정보를 로컬에 캐싱함으로써 GCS의 부하를 줄이고, 액터 제출 속도를 수십 배 향상시켜 Ray의 확장성과 효율성을 한 단계 끌어올렸습니다. 이는 Ray 사용자들이 더욱 빠르고 안정적으로 대규모 머신러닝 작업을 수행할 수 있도록 돕는 중요한 개선점이며, 분산 시스템 설계에서 캐싱과 정보 재활용의 중요성을 다시 한번 상기시켜 줍니다.
References
참고 자료
- https://docs.ray.io/en/latest/ray-core/api/doc/ray.util.placement_group.html
- https://docs.ray.io/en/latest/ray-core/walkthrough.html#global-control-store-gcs
⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
- [Ray RLlib] SingleAgentEnvRunner의 validate 호출 위치 최적화로 3.1배 속도 향상
- [Ray Serve] Pack 스케줄링 최적화: O(replicas x total_replicas)에서 O(replicas x nodes)로
- [uv] uv의 패키지 설치 성능 최적화: 불필요한 site-packages 스캔 제거
- [uv] Interning을 통한 uv의 Simple API 파싱 최적화: 메모리와 속도 두 마리 토끼 잡기
- [cpython] CPython unicodedata.normalize() 최적화: Py_UCS4 버퍼 직접 조작으로 성능 향상
PR Analysis 의 다른글
- 이전글 [transformers] Hugging Face Transformers의 Flash Attention 성능 회귀(Regression) 해결
- 현재글 : [ray] [Ray 최적화] 액터 제출 병목 현상 해결: Placement Group 번들 정보 캐싱으로 GCS 부하 줄이기
- 다음글 [loki] Grafana Loki의 안정성 향상: Circuit Breaker 도입을 통한 트래픽 제어
댓글