[feast] Feast Redis 온라인 스토어 Protobuf 파싱 최적화
PR 링크: feast-dev/feast#6023 상태: Merged | 변경: +138 / -17
들어가며
Feast는 ML feature store로, 온라인 서빙 시 Redis에서 feature 값을 읽어 Protobuf로 역직렬화합니다. 이 과정에서 Redis가 반환하는 바이너리 데이터를 ParseFromString()으로 파싱하는데, 기존 코드에는 불필요한 bytes() 변환이 포함되어 있었습니다.
이 PR은 세 가지 최적화를 수행합니다: 불필요한 타입 변환 제거, 리스트 컴프리헨션으로의 리팩토링, 그리고 불필요한 else 절 제거입니다.
핵심 코드 분석
1. 불필요한 bytes() 변환 제거
Before:
def _get_features_for_entity(self, values, feature_view, requested_features):
# ...
res_val = dict(zip(requested_features, values))
res_ts = Timestamp()
ts_val = res_val.pop(f"_ts:{feature_view}")
if ts_val:
res_ts.ParseFromString(bytes(ts_val))
res = {}
for feature_name, val_bin in res_val.items():
val = ValueProto()
if val_bin:
val.ParseFromString(bytes(val_bin))
res[feature_name] = val
After:
def _get_features_for_entity(self, values, feature_view, requested_features):
# ...
res_val = dict(zip(requested_features, values))
res_ts = Timestamp()
ts_key = f"_ts:{feature_view}"
ts_val = res_val.pop(ts_key)
if ts_val:
res_ts.ParseFromString(ts_val)
res: Dict[str, ValueProto] = {}
for feature_name, val_bin in res_val.items():
val = ValueProto()
if val_bin:
val.ParseFromString(val_bin)
res[feature_name] = val
bytes(ts_val)과 bytes(val_bin) 호출이 제거되었습니다. Redis 클라이언트는 이미 bytes 또는 memoryview 객체를 반환하는데, Protobuf의 ParseFromString()은 두 타입 모두 직접 처리할 수 있습니다. bytes() 호출은 memoryview인 경우 전체 데이터의 복사본을 생성하므로, 대량의 feature를 처리할 때 불필요한 메모리 할당과 복사가 발생합니다.
2. 리스트 컴프리헨션으로 간소화
Before:
def _convert_redis_values_to_protobuf(
self, redis_values, feature_view, requested_features,
):
result: List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]] = []
for values in redis_values:
features = self._get_features_for_entity(
values, feature_view, requested_features
)
result.append(features)
return result
After:
def _convert_redis_values_to_protobuf(
self, redis_values, feature_view, requested_features,
) -> List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]]:
return [
self._get_features_for_entity(values, feature_view, requested_features)
for values in redis_values
]
명시적 루프 + append가 리스트 컴프리헨션으로 교체되었습니다. CPython에서 리스트 컴프리헨션은 전용 바이트코드(LIST_APPEND)를 사용하여 일반 append() 메서드 호출보다 빠릅니다. 또한 반환 타입 어노테이션이 메서드 시그니처로 이동하여 코드 가독성이 향상되었습니다.
3. 불필요한 else 절 제거
Before:
if not res:
return None, None
else:
# reconstruct full timestamp including nanos
total_seconds = res_ts.seconds + res_ts.nanos / 1_000_000_000.0
timestamp = datetime.fromtimestamp(total_seconds, tz=timezone.utc)
return timestamp, res
After:
if not res:
return None, None
total_seconds = res_ts.seconds + res_ts.nanos / 1_000_000_000.0
timestamp = datetime.fromtimestamp(total_seconds, tz=timezone.utc)
return timestamp, res
early return 패턴 적용으로 불필요한 else 절과 들여쓰기 수준이 제거되었습니다. 기능적 변화는 없지만 코드 가독성이 향상됩니다.
4. 테스트 코드 추가
PR에는 memoryview 입력 처리, None 값 처리, 다중 엔티티 배치 변환 등을 검증하는 테스트가 추가되었습니다.
def test_get_features_for_entity_with_memoryview(
redis_online_store: RedisOnlineStore, feature_view
):
"""Redis may return memoryview objects instead of bytes in some cases."""
values = [
memoryview(val1_bytes),
memoryview(val2_bytes),
memoryview(ts_bytes),
]
timestamp, features = redis_online_store._get_features_for_entity(
values=values,
feature_view=feature_view.name,
requested_features=requested_features,
)
assert features["feature_view_1:feature_10"].int32_val == 100
이 테스트는 bytes() 변환 제거 후에도 memoryview 입력이 정상 처리됨을 보장합니다.
왜 이게 좋은가
온라인 feature 서빙은 ML 추론 파이프라인의 핫 패스(hot path)입니다. 요청당 수십~수백 개의 feature를 조회하므로, feature 하나당 불필요한 bytes() 복사가 누적되면 레이턴시에 영향을 줍니다.
- 메모리 할당 감소:
memoryview→bytes변환 시 발생하는 복사 제거 - GC 압력 감소: 임시
bytes객체가 생성되지 않아 가비지 컬렉션 부담 감소 - 코드 단순화: 타입 어노테이션 추가, early return 패턴으로 유지보수성 향상
정리
- 불필요한 타입 변환을 의심하라:
bytes(),str(),list()같은 변환이 정말 필요한지 확인하십시오. 다운스트림 API가 이미 해당 타입을 지원할 수 있습니다. - memoryview는 zero-copy의 핵심이다:
bytes(memoryview_obj)는 복사를 강제합니다. Protobuf, NumPy 등 많은 라이브러리가memoryview를 직접 처리할 수 있습니다. - 핫 패스의 마이크로 최적화는 유의미하다: 요청당 수백 번 호출되는 함수에서의 작은 개선은 전체 레이턴시에 측정 가능한 차이를 만듭니다.
참고 자료
- feast-dev/feast#6023 — PR 전체 diff 및 테스트 코드
- Python memoryview docs — memoryview 공식 문서
⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
PR Analysis 의 다른글
- 이전글 [Open WebUI] ChatControls 컴포넌트 메모리 누수 수정
- 현재글 : [feast] Feast Redis 온라인 스토어 Protobuf 파싱 최적화
- 다음글 [axolotl] SchedulerMixin.create_scheduler() optimizer 누락 버그 수정
댓글