Ray Actors
Ray Actors
Ray 액터는 Ray API를 함수(태스크)에서 클래스로 확장한 개념이에요. 액터는 기본적으로 상태를 가진 워커(또는 서비스)예요. 새 액터를 인스턴스화하면 Ray가 새 워커를 만들고 그 특정 워커에서 액터의 메서드를 스케줄링해요. 메서드는 그 워커의 상태에 접근하고 변경할 수 있어요.
출처: Ray Actors
액터 만들기
ray.remote 데코레이터는 Counter 클래스의 인스턴스가 액터임을 나타내요. 각 액터는 자신만의 파이썬 프로세스에서 실행돼요.
import ray
@ray.remote
class Counter:
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
return self.value
def get_counter(self):
return self.value
# Create an actor from this class.
counter = Counter.remote()
Java에서는 Ray.actor(...).remote()로, C++에서는 ray::Actor(...).Remote()로 일반 클래스에서 액터를 만들어요.
State API의 ray list actors로 액터 상태를 확인할 수 있어요.
# This API is only available when you install Ray with `pip install "ray[default]"`.
ray list actors
액터 호출하기
액터는 메서드를 remote 연산자로 호출해서 상호작용하고, get으로 객체 참조에서 실제 값을 검색해요.
# Call the actor.
obj_ref = counter.increment.remote()
print(ray.get(obj_ref))
# 1
서로 다른 액터에서 호출되는 메서드는 병렬로 실행되고, 같은 액터에서 호출되는 메서드는 호출한 순서대로 직렬로 실행돼요. 같은 액터의 메서드는 서로 상태를 공유해요.
:::note 액터 상태는 액터 인스턴스별이에요. 각 액터는 자신만의 프로세스에서 실행되므로, 클래스 변수·정적 필드는 액터 인스턴스 간에 공유되지 않아요. 클래스 수준 상태의 변경은 그 액터 프로세스에만 국한돼요. 여러 액터 간에 가변 상태를 공유하려면 다른 액터에 저장하고 그 액터 핸들을 필요한 곳에 전달해요. :::
# Create ten Counter actors.
counters = [Counter.remote() for _ in range(10)]
# Increment each Counter once and get the results. These tasks all happen in
# parallel.
results = ray.get([c.increment.remote() for c in counters])
print(results)
# Increment the first Counter five times. These tasks are executed serially
# and share state.
results = ray.get([counters[0].increment.remote() for _ in range(5)])
print(results)
# [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
# [2, 3, 4, 5, 6]
액터 핸들 전달하기
액터 핸들을 다른 태스크에 전달할 수 있고, 액터 핸들을 사용하는 원격 함수·액터 메서드도 정의할 수 있어요. 예를 들어 태스크가 액터 핸들을 받아 그 메서드를 호출하는 식이에요.
import time
@ray.remote
def f(counter):
for _ in range(10):
time.sleep(0.1)
counter.increment.remote()
액터를 인스턴스화하면 그 핸들을 여러 태스크에 전달할 수 있어요.
counter = Counter.remote()
# Start some tasks that use the actor.
[f.remote(counter) for _ in range(3)]
# Print the counter value.
for _ in range(10):
time.sleep(0.1)
print(ray.get(counter.get_counter.remote()))
# 0
# 3
# 8
# 10
# 15
# 18
# 20
# 25
# 30
# 30
액터 타입 힌트·정적 타이핑
Ray는 원격 함수와 액터 모두에 파이썬 타입 힌트를 지원해 IDE 지원과 정적 타입 검사를 개선해요. 액터로 작업할 때 최상의 타입 추론을 얻으려면 이 패턴을 따라요.
- 액터에는
@ray.remote보다ray.remote(MyClass)를 선호해요. 원래 클래스 타입을 보존해 타입 체커·IDE가 올바른 타입을 추론하게 해 줘요. - 액터 메서드에는
@ray.method를 사용해 액터 핸들의 원격 메서드 호출에 타입 힌트를 활성화해요. - 액터를 인스턴스화할 때 핸들을
ActorProxy[MyClass]로 주석해 원격 메서드의 타입 힌트를 얻어요.
import ray
from ray.actor import ActorClass, ActorProxy
class Counter:
def __init__(self):
self.value = 0
@ray.method
def increment(self) -> int:
self.value += 1
return self.value
CounterActor: ActorClass[Counter] = ray.remote(Counter)
counter: ActorProxy[Counter] = CounterActor.remote()
# Type checkers and IDEs will now provide type hints for remote methods
obj_ref: ray.ObjectRef[int] = counter.increment.remote()
print(ray.get(obj_ref))
액터 태스크 취소
ray.cancel()을 반환된 ObjectRef에 호출해 액터 태스크를 취소할 수 있어요.
import ray
import asyncio
import time
@ray.remote
class Actor:
async def f(self):
try:
await asyncio.sleep(5)
except asyncio.CancelledError:
print("Actor task canceled.")
actor = Actor.remote()
ref = actor.f.remote()
# Wait until task is scheduled.
time.sleep(1)
ray.cancel(ref)
try:
ray.get(ref)
except ray.exceptions.RayTaskError:
print("Object reference was cancelled.")
액터 태스크 취소는 태스크의 현재 상태에 따라 다른데, 실행 중인 일반·스레드 액터 태스크는 ray.get_runtime_context().is_canceled()로 확인할 수 있는 취소 플래그가 설정되고, async 액터 태스크는 연관된 asyncio.Task를 취소하려 시도해요.
스케줄링과 장애 허용
Ray는 각 액터에 대해 실행할 노드를 고르는데, 액터의 리소스 요구사항과 지정된 스케줄링 전략 등을 기준으로 결정해요.
기본적으로 액터가 예기치 않게 충돌해도 액터는 재시작되지 않고 액터 태스크는 재시도되지 않아요. ray.remote()와 .options()에서 max_restarts, max_task_retries 옵션을 설정해 이 동작을 바꿀 수 있어요.
FAQ: 워커와 액터, 리소스
각 "Ray 워커"는 파이썬 프로세스예요. 태스크와 액터에 대해 워커를 다르게 취급해요. 태스크의 경우 Ray 워커 하나가 여러 Ray 태스크를 실행하는 데 쓰여요(프로세스 풀처럼). 액터의 경우 전용 Ray 액터로 "Ray 워커"를 시작해요. 액터가 필요 없는 상태 있는 부분이 아니라면, 상태 없이 실행할 수 있는 작업은 태스크를 쓰는 게 리소스 활용에 더 좋아요.
더 알아보기
- Tasks — 원격 함수 상세
- Objects — 분산 객체 스토어
- What's Ray Core? — Ray Core 개념 워크스루
- Ray Overview — Ray 전체 구조