[Ray] Ray gRPC 토큰 인증 최적화 -- shared_ptr 캐싱
PR 링크: ray-project/ray#59500 상태: Merged | 변경: +192 / -152
들어가며
Ray 클러스터는 gRPC를 통해 노드 간 통신하며, 인증 토큰으로 요청을 검증한다. 기존에는 std::optional<AuthenticationToken>으로 토큰을 관리했는데, 이 방식은 GetToken() 호출 때마다 토큰 객체를 값으로 복사하고, ValidateToken() 시에도 매번 문자열 파싱(FromMetadata)이 발생한다. 수천 개의 RPC가 초당 처리되는 환경에서 이 오버헤드가 누적된다. 이 PR은 shared_ptr<const AuthenticationToken>으로 전환해 복사를 제거한다.
핵심 코드 분석
GetToken 반환 타입 변경
Before:
// authentication_token_loader.h
optional[CAuthenticationToken] GetToken(c_bool ignore_auth_mode)
After:
shared_ptr[const CAuthenticationToken] GetToken(c_bool ignore_auth_mode)
GetToken()이 optional<AuthenticationToken> 대신 shared_ptr<const AuthenticationToken>을 반환한다. 토큰 객체는 한 번 생성되면 변하지 않으므로 const로 공유한다. 호출자가 몇이든 포인터만 복사되고 실제 토큰 데이터는 한 곳에만 존재한다.
ValidateToken: 파싱 제거
Before:
// authentication_token_validator.h
c_bool ValidateToken(
const optional[CAuthenticationToken]& expected_token,
const CAuthenticationToken& provided_token)
After:
c_bool ValidateToken(
const shared_ptr[const CAuthenticationToken]& expected_token,
const string& provided_metadata)
기존에는 검증 전에 메타데이터 문자열에서 AuthenticationToken 객체를 생성(FromMetadata)해야 했다. 변경 후에는 raw 메타데이터 문자열을 직접 받아 내부에서 비교한다. 불필요한 객체 생성이 제거된다.
Python Cython 바인딩 업데이트
Before:
# rpc_token_authentication.pxi
cdef optional[CAuthenticationToken] expected_opt
expected_opt = CAuthenticationTokenLoader.instance().GetToken(False)
if not expected_opt.has_value():
return False
provided = CAuthenticationToken.FromMetadata(provided_token.encode())
if provided.empty():
return False
return CAuthenticationTokenValidator.instance().ValidateToken(expected_opt, provided)
After:
cdef shared_ptr[const CAuthenticationToken] expected_ptr
expected_ptr = CAuthenticationTokenLoader.instance().GetToken(False)
if not expected_ptr:
return False
return CAuthenticationTokenValidator.instance().ValidateToken(
expected_ptr, provided_metadata.encode())
Python 쪽 코드도 간결해졌다. FromMetadata 파싱 단계가 사라지고, 토큰 존재 여부 확인이 has_value() 대신 shared_ptr의 boolean 변환으로 단순해졌다.
서비스 레이어 전파
Before:
// grpc_services.h (모든 GrpcService 하위 클래스)
void InitServerCallFactories(
const std::unique_ptr<grpc::ServerCompletionQueue> &cq,
std::vector<std::unique_ptr<ServerCallFactory>> *server_call_factories,
const ClusterID &cluster_id,
const std::optional<AuthenticationToken> &auth_token) override;
After:
void InitServerCallFactories(
const std::unique_ptr<grpc::ServerCompletionQueue> &cq,
std::vector<std::unique_ptr<ServerCallFactory>> *server_call_factories,
const ClusterID &cluster_id,
std::shared_ptr<const AuthenticationToken> auth_token) override;
10개 이상의 gRPC 서비스 클래스(ActorInfoGrpcService, NodeInfoGrpcService, JobInfoGrpcService 등) 모두에서 optional -> shared_ptr 전환이 일관되게 적용되었다.
왜 이게 좋은가
- 복사 비용 제거:
optional값 복사 대신shared_ptr포인터 복사만 발생한다. 토큰 문자열이 길수록 효과가 크다. - 파싱 비용 제거: 매 검증마다
FromMetadata호출로 문자열을 파싱해 토큰 객체를 만들던 비용이 사라진다.CompareWithMetadata로 직접 비교한다. - const 보장:
shared_ptr<const T>이므로 토큰이 불변임이 타입 시스템으로 보장된다. - 일관성:
optional->shared_ptr전환이 loader, validator, 모든 gRPC 서비스, Python 바인딩까지 빠짐없이 적용되어 인터페이스가 통일되었다.
정리
optional<T> -> shared_ptr<const T> 전환은 단순해 보이지만, gRPC 인증처럼 매 요청마다 반복 호출되는 핫패스에서는 의미 있는 최적화다. 값 복사 제거와 불필요한 파싱 제거라는 두 가지 개선을 동시에 달성하면서, const 불변 보장까지 얻었다. C++ 스마트 포인터의 교과서적인 활용 사례다.
참고 자료
- Ray Authentication 문서 -- Ray 클러스터 보안 가이드
- std::shared_ptr cppreference -- C++ shared_ptr 참조
알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
PR Analysis 의 다른글
- 이전글 [Triton] SWP 루프 로우어링에서 barrier 위치 결정 로직 수정
- 현재글 : [Ray] Ray gRPC 토큰 인증 최적화 -- shared_ptr 캐싱
- 다음글 [Triton] AMD gfx950/gfx1250에 AsyncCopy 기본 활성화 — 파이프라인 성능 향상
댓글