본문으로 건너뛰기

[Ray Serve] AutoscalingPolicy의 cloudpickle 역직렬화 결과 캐싱

PR 링크: ray-project/ray#60844 상태: Merged | 변경: +13 / -2

들어가며

Ray Serve의 AutoscalingPolicy.get_policy()는 매번 cloudpickle.loads()를 호출하여 직렬화된 정책 바이트를 역직렬화합니다. 이 함수는 모든 오토스케일링 틱에서 _create_deployment_snapshot() 내부로 호출되어 정책의 __module____name__을 추출합니다. 정책 바이트는 한번 설정되면 변경되지 않으므로, 역직렬화 결과를 캐싱하면 불필요한 반복 연산을 제거할 수 있습니다.

핵심 코드 분석

Before: 매번 역직렬화

class AutoscalingPolicy(BaseModel):
    _serialized_policy_def: bytes = PrivateAttr(default=b"")

    def get_policy(self) -> Callable:
        """Deserialize policy from cloudpickled bytes."""
        try:
            return cloudpickle.loads(self._serialized_policy_def)
        except (ModuleNotFoundError, ImportError) as e:
            raise ImportError(...) from e

After: lazy 캐싱

class AutoscalingPolicy(BaseModel):
    _serialized_policy_def: bytes = PrivateAttr(default=b"")
    _cached_policy: Optional[Callable] = PrivateAttr(default=None)

    def set_serialized_policy_def(self, serialized_policy_def: bytes) -> None:
        self._serialized_policy_def = serialized_policy_def
        self._cached_policy = None  # 캐시 무효화

    def get_policy(self) -> Callable:
        """Deserialize policy from cloudpickled bytes.
        The result is cached to avoid repeated cloudpickle deserialization."""
        if self._cached_policy is not None:
            return self._cached_policy
        try:
            policy = cloudpickle.loads(self._serialized_policy_def)
        except (ModuleNotFoundError, ImportError) as e:
            raise ImportError(...) from e
        self._cached_policy = policy
        return policy

핵심 변경:

  1. _cached_policy private 속성 추가
  2. get_policy()에서 캐시 히트 시 즉시 반환
  3. set_serialized_policy_def()에서 캐시 무효화

왜 이게 좋은가

벤치마크 결과:

  • Before: 0.9 µs/call
  • After: 0.1 µs/call
  • 약 8배 속도 향상
  1. 오토스케일링 루프 가속: 매 틱마다 호출되므로 누적 효과가 큽니다. 수천 개의 deployment가 있으면 절약량이 상당합니다.
  2. 안전한 캐시 무효화: set_serialized_policy_def()에서 _cached_policy = None으로 설정하여, 정책이 변경되면 다음 호출에서 재역직렬화가 발생합니다.
  3. 최소한의 변경: 13줄 추가, 2줄 수정만으로 큰 성능 개선을 달성했습니다.

cloudpickle.loads()는 바이트를 파싱하고 Python 객체를 재구성하는 비교적 무거운 연산입니다. 불변 데이터의 반복 역직렬화를 캐싱하는 것은 가장 기본적이면서도 효과적인 최적화 패턴입니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글