GitHub ↗
CHAPTER 05 OF 10
🏗️

Data Pipeline Architecture

데이터 파이프라인 아키텍처

Training-serving skew — 학습과 서빙에서 피처가 다르게 계산되는 문제 — 는 프로덕션 ML 실패의 1번 원인이며, 이를 해결하는 Feature Store와 데이터 버전 관리가 ML 시스템 신뢰성의 핵심이다.

Data Pipeline Architecture cheatsheet
🍌 NANO BANANA CHEATSHEET · CH 05

Overview

개관

머신러닝 모델이 프로덕션에서 실패하는 가장 흔한 원인은 아키텍처나 알고리즘 문제가 아니다. 데이터 파이프라인 문제다. 학습 데이터와 서빙 시점의 입력 데이터가 다르게 처리되는 training-serving skew, 시간이 지나면서 데이터 분포가 변하는 data drift, 피처 계산 로직의 불일치 — 이런 파이프라인 문제들이 조용히 모델 성능을 갉아먹는다.

Feature Store는 이 문제를 해결하기 위해 등장한 ML 인프라 컴포넌트다. 피처를 중앙 저장소에서 관리하고, 학습(batch)과 서빙(real-time) 모두에서 동일한 피처 정의를 공유한다. Feast, Tecton, Hopsworks 등의 Feature Store가 현재 ML 인프라의 표준으로 자리잡고 있다.

비전과 비디오 데이터 파이프라인은 정형 데이터와 다른 도전을 제시한다. 원시 픽셀을 저장하고 전처리하는 비용이 엄청나므로, 사전 추출된 임베딩을 캐싱하는 전략이 중요해진다. 이미지 한 장의 CLIP 임베딩(512차원 float32 = 2KB)을 저장하는 것이 원본 이미지(수 MB)를 매번 처리하는 것보다 훨씬 효율적이다.

🎯 Learning Goals
  • Training-serving skew가 발생하는 근본 원인과 방지 전략을 설명할 수 있다
  • Feature Store의 Online/Offline 스토어 아키텍처와 그 필요성을 이해한다
  • Batch(Spark)와 Streaming(Kafka/Flink) 파이프라인을 언제 선택할지 판단할 수 있다
  • 데이터 버전 관리가 ML 재현성에 미치는 영향을 이해한다

Sections

본문

Training-Serving Skew: ML 시스템 최대의 적

Training-serving skew란 학습 시점의 데이터 처리와 서빙(예측) 시점의 데이터 처리가 일치하지 않는 문제다. 이 불일치는 다양한 형태로 나타난다.

케이스 1: 코드 경로 불일치. 훈련 파이프라인은 Python Pandas로 피처를 계산하고, 서빙 API는 Java로 계산한다. 수치형 반올림, null 처리 로직이 미묘하게 다를 수 있다.

케이스 2: 집계 창 불일치. 훈련 시 '최근 30일 평균 구매액'을 학습 날짜 기준으로 계산했지만, 서빙 시에는 현재 시점 기준으로 계산한다 — 이것은 사실 올바른 동작이지만, 분포 변화가 발생한다.

케이스 3: 피처 누수(Feature Leakage). 훈련 데이터에 미래 정보가 포함된다. 예: 이탈 예측 모델에서 '계약 해지 날짜'가 피처로 들어간 경우.

케이스 4: 데이터 소스 불일치. 훈련은 오프라인 데이터 웨어하우스에서, 서빙은 실시간 데이터베이스에서 피처를 읽는다. 업데이트 지연이나 데이터 포맷 차이가 생긴다.

"Hidden technical debt in machine learning systems" (Sculley et al., Google, 2015)은 프로덕션 ML 코드에서 실제 모델 코드는 10% 미만이고, 나머지 90%가 피처 파이프라인, 데이터 수집, 모니터링 등 인프라 코드임을 지적했다.

방지 전략: Feature Store로 피처 정의를 단일화하고, 훈련과 서빙이 같은 피처 계산 코드를 공유하도록 강제한다.

Feature Store: Online과 Offline의 이중 구조

Feature Store는 두 개의 스토어로 구성된다.

Offline Store(오프라인 스토어): 훈련을 위한 대규모 피처 저장소. 수개월-수년치 피처를 보관한다. 컬럼형 스토리지(Parquet, Delta Lake)로 구현해 배치 쿼리에 최적화. 지연시간 허용(수 분~수 시간). S3 + Spark 조합이 일반적.

Online Store(온라인 스토어): 서빙을 위한 실시간 피처 저장소. 최신 피처만 보관(현재 시점 기준). Redis, DynamoDB 같은 key-value 스토어로 구현해 저지연 쿼리에 최적화. 지연시간 요구(<10ms). 요청 당 피처 조회 횟수를 최소화해야 한다.

피처 파이프라인은 배치 처리(Spark)로 Offline Store를 채우고, 스트리밍(Kafka+Flink)이나 배치 sync로 Online Store를 업데이트한다.

비전/비디오 피처의 특수성: 이미지/비디오 임베딩도 Feature Store로 관리할 수 있다. 이미지 ID → CLIP 임베딩 매핑을 Online Store에 저장하면, 서빙 시 원본 이미지 처리 없이 즉시 임베딩을 조회할 수 있다. 단, 임베딩 업데이트 주기(embedding drift)와 저장 비용의 트레이드오프를 고려해야 한다.

오픈소스 Feature Store 비교:

도구 강점 약점
Feast 단순, 오픈소스, AWS/GCP 지원 엔터프라이즈 기능 부족
Tecton 실시간 피처 강력, 엔터프라이즈 유료, 복잡
Hopsworks ML pipeline 통합 자체 호스팅
Vertex AI FS GCP 네이티브 GCP 종속

Batch vs Streaming: 파이프라인 선택의 기준

ML 데이터 파이프라인은 배치(Batch)와 스트리밍(Streaming) 두 패러다임으로 나뉜다. 선택은 데이터 신선도(freshness) 요구와 처리 복잡도로 결정한다.

배치 파이프라인(Apache Spark):

  • 대규모 과거 데이터를 주기적으로 처리 (매일, 매시간)
  • 집계, 조인, 복잡한 피처 계산에 강점
  • 데이터 신선도: 수 분~수 시간 허용
  • 적합 사례: 추천 시스템의 사용자 통계 피처, 이미지 임베딩 사전 계산

스트리밍 파이프라인(Kafka + Apache Flink):

  • 이벤트 발생 즉시 처리 (밀리초~초 단위)
  • 실시간 집계, 이상 탐지, 실시간 피처 업데이트
  • 데이터 신선도: 초~분 수준 요구
  • 적합 사례: 사기 탐지(클릭 패턴), 실시간 재고 변동, 라이브 비디오 분석

Lambda Architecture: 두 패러다임을 조합한다. 배치 레이어(정확하지만 느림)와 스피드 레이어(빠르지만 근사)를 병렬 운영하고 서빙 레이어에서 결합한다. 복잡도가 높아 Kappa Architecture(스트리밍만 사용, 배치는 replay로)로 단순화하는 추세다.

실용적 가이드: 신선도 요구가 1시간 이하이면 스트리밍을 고려하고, 그 이상이면 배치로 충분하다. 처음부터 스트리밍 파이프라인을 구축하지 말 것 — 운영 복잡도가 배치의 10배다.

데이터 버전 관리: ML 재현성의 기반

모델을 재현하려면 데이터도 재현할 수 있어야 한다. '3개월 전 훈련 데이터로 모델을 다시 학습하라'는 요청에 대응할 수 있어야 한다. 이것이 데이터 버전 관리가 필요한 이유다.

DVC(Data Version Control): Git에서 데이터를 직접 관리하기 어렵기 때문에(파일 크기), DVC는 데이터를 원격 스토리지(S3, GCS)에 저장하고 Git에는 그 포인터(.dvc 파일)만 커밋한다. 코드와 데이터 버전을 함께 관리할 수 있다.

Delta Lake / Apache Iceberg: 데이터 레이크에서 ACID 트랜잭션과 Time Travel을 지원한다. RESTORE TABLE events TO TIMESTAMP AS OF '2024-01-01' 같은 쿼리로 특정 시점의 데이터 상태로 돌아갈 수 있다. ML 훈련 데이터의 버전을 타임스탬프로 관리한다.

비전/비디오 데이터 버전 관리의 특수성: 이미지/비디오 원본 파일 자체보다 어노테이션(레이블)의 버전 관리가 핵심이다. 어노테이션 툴(Labelbox, Scale AI)에서 레이블 버전을 관리하고, 훈련 시 어떤 버전의 레이블을 사용했는지 추적해야 한다.

💡 Analogy · 비유
레스토랑의 레시피 표준화

Feature Store의 필요성을 레스토랑 체인으로 이해할 수 있다. 어떤 레스토랑 체인이 각 지점 주방장이 자기 방식으로 소스를 만든다고 해보자. 손님이 어느 지점을 가든 비슷한 맛이 나야 하는데, 각자 레시피가 다르면 품질이 들쭉날쭉해진다. 이것이 training-serving skew다 — 훈련용 '주방'과 서빙용 '주방'이 다른 레시피를 쓰는 상황.

Feature Store는 중앙 소스 공장과 같다. 모든 지점이 같은 공장에서 만든 소스를 받아 쓴다. 레시피가 하나로 통일되고, 누가 어떤 버전의 소스를 언제 썼는지 추적 가능하다. 공장(Feature Store)에는 두 종류의 창고가 있다: 대량 재고창고(Offline Store, 배치 훈련용)와 당일 납품 냉장고(Online Store, 실시간 서빙용).

배치 파이프라인은 대형 트럭으로 주 1회 대량 납품, 스트리밍 파이프라인은 오토바이로 실시간 배달이다. 어떤 배송 방식이 필요한지는 '소스가 얼마나 신선해야 하는가'가 결정한다. 날마다 바뀌는 재료가 필요하면 실시간 배달이 필요하고, 한 달치 소스를 한 번에 만들어도 되면 대형 트럭으로 충분하다.

Feast를 사용한 간단한 Feature Store 설정과, 학습 데이터 추출과 실시간 서빙 피처 조회를 동일 정의로 처리하는 패턴을 살펴보자.

python
from datetime import datetime
from feast import Entity, Feature, FeatureStore, FeatureView, ValueType
from feast.infra.offline_stores.file_source import FileSource
import pandas as pd

# 1. 피처 소스 정의 (오프라인 파일 또는 데이터 웨어하우스 테이블)
user_stats_source = FileSource(
    path="data/user_stats.parquet",
    event_timestamp_column="event_timestamp",
)

# 2. 엔티티 정의 (피처의 키)
user = Entity(name="user_id", value_type=ValueType.INT64)

# 3. 피처 뷰 정의 — 훈련과 서빙이 동일한 정의를 공유
user_feature_view = FeatureView(
    name="user_stats",
    entities=["user_id"],
    features=[
        Feature(name="purchase_count_30d", dtype=ValueType.INT64),
        Feature(name="avg_order_value_30d", dtype=ValueType.FLOAT),
        Feature(name="days_since_last_purchase", dtype=ValueType.INT64),
    ],
    online=True,  # 온라인 스토어에도 저장
    source=user_stats_source,
    ttl=None,
)

# 4. 훈련 데이터 추출 (오프라인)
store = FeatureStore(repo_path=".")
entity_df = pd.DataFrame({
    "user_id": [1001, 1002, 1003],
    "event_timestamp": [datetime(2024, 1, 15)] * 3,
})
training_df = store.get_historical_features(
    entity_df=entity_df,
    features=["user_stats:purchase_count_30d", "user_stats:avg_order_value_30d"]
).to_df()

# 5. 실시간 서빙 (온라인) — 동일 피처 정의
online_features = store.get_online_features(
    features=["user_stats:purchase_count_30d", "user_stats:avg_order_value_30d"],
    entity_rows=[{"user_id": 1001}]
).to_dict()

FeatureView에서 피처를 한 번 정의하면, get_historical_features(훈련용 배치 쿼리)와 get_online_features(서빙용 실시간 쿼리) 모두 같은 정의를 사용한다. 이것이 training-serving skew를 방지하는 핵심이다. 피처 계산 로직이 Python으로 한 번만 작성되고, 학습과 서빙 모두에서 같은 코드가 실행된다.

🏭 현업에서의 평가
데이터 파이프라인 설계 능력은 ML 엔지니어링 시니어 인터뷰에서 아키텍처 설계 문제로 자주 등장한다. 핵심은 training-serving skew에 대한 이해와, 데이터 신선도/처리 복잡도 트레이드오프에 따른 배치/스트리밍 선택이다.

✅ 시니어가 보는 것

  • Feature Store의 필요성을 training-serving skew 문제로 설명하는 능력
  • Online/Offline Store의 아키텍처 차이와 각각의 스토리지 선택 근거
  • 배치 vs 스트리밍 파이프라인 선택 기준을 데이터 신선도와 운영 복잡도로 정량화
  • 비전/비디오 임베딩 파이프라인의 캐싱 전략 이해

⚠️ 레드 플래그

  • 훈련 데이터 전처리와 서빙 전처리를 별도로 구현하겠다고 제안 — training-serving skew 인식 부족
  • 모든 경우에 스트리밍 파이프라인을 제안 — 운영 복잡도 무시
  • 데이터 버전 관리 없이 학습하겠다는 계획 — ML 재현성 무시

🎤 예상 인터뷰 질문

  1. 대형 이커머스에서 사용자 구매 이력 기반 추천 모델을 만들 때, 피처 파이프라인을 어떻게 설계하겠습니까? Training-serving skew를 어떻게 방지할 것입니까?
  2. 실시간 사기 탐지 시스템에서 피처 신선도 요구가 5초라면 어떤 파이프라인 아키텍처를 쓰겠습니까?
  3. 이미지 10억 장의 CLIP 임베딩을 Feature Store로 관리한다면 Online Store와 Offline Store에 각각 어떤 스토리지를 선택하겠습니까?
숙달 vs 익숙함: 익숙한 수준은 Pandas와 scikit-learn으로 데이터를 처리하고 모델을 학습하는 것이다. 숙달은 Feature Store 아키텍처를 설계하고, training-serving skew를 제도적으로 방지하고, 배치/스트리밍 파이프라인을 데이터 신선도와 비용에 맞게 선택하며, 데이터 버전 관리로 ML 실험의 재현성을 보장하는 것이다.

Key Takeaways

핵심 정리

Training-serving skew = ML 최대의 적

학습과 서빙의 데이터 처리 불일치는 조용히, 지속적으로 성능을 갉아먹는다. 제도적으로 방지해야 한다.

Feature Store = 피처 단일 진실 원천

훈련과 서빙이 같은 피처 정의를 공유하면 skew가 구조적으로 제거된다.

Online/Offline 이중 구조

배치 훈련용 Offline Store(S3+Parquet)와 실시간 서빙용 Online Store(Redis)는 요구사항이 달라 별도로 최적화된다.

배치는 기본, 스트리밍은 선택

데이터 신선도 요구가 1시간 이하일 때만 스트리밍을 고려하라. 운영 복잡도가 10배다.

임베딩 캐싱 = 비전 파이프라인의 핵심

원본 이미지 매번 처리 대신 사전 추출된 임베딩을 Feature Store에 캐싱하면 서빙 비용이 극적으로 줄어든다.

데이터 버전 = ML 재현성의 기반

DVC 또는 Delta Lake Time Travel로 특정 시점의 훈련 데이터를 재현할 수 있어야 한다.