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 워커"를 시작해요. 액터가 필요 없는 상태 있는 부분이 아니라면, 상태 없이 실행할 수 있는 작업은 태스크를 쓰는 게 리소스 활용에 더 좋아요.

더 알아보기