Skip to content

필사 모드: Feature Store 설계와 운영 가이드: Feast 기반 Online/Offline Store 구축·ML 피처 파이프라인 자동화

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

머신러닝 모델의 프로덕션 배포가 보편화되면서, 피처(Feature) 관리가 MLOps의 핵심 과제로 떠올랐다. 모델 학습에 사용한 피처를 실시간 서빙에서도 동일하게 재현해야 하고, 여러 팀이 동일한 피처를 중복 계산하지 않도록 공유해야 하며, 피처의 품질과 신선도를 지속적으로 모니터링해야 한다.

Feature Store는 이러한 문제를 해결하기 위해 등장한 인프라 계층으로, 피처의 정의, 저장, 서빙, 모니터링을 중앙에서 관리한다. 그중 Feast(Feature Store)는 가장 널리 사용되는 오픈소스 Feature Store로, 기존 데이터 인프라를 재활용하면서 유연한 피처 서빙을 제공한다.

이 글에서는 Feature Store의 핵심 개념, Feast 아키텍처, 피처 정의와 Entity 설계, Materialization 파이프라인, Online/Offline Store 설정, Training-Serving Skew 방지 전략, Feature Monitoring, Tecton/Hopsworks와의 비교, 프로덕션 배포 패턴, 장애 대응까지 전 과정을 다룬다.

Feature Store 핵심 개념

왜 Feature Store가 필요한가

Feature Store 없이 ML 파이프라인을 운영하면 다음과 같은 문제가 발생한다.

| 문제 | 설명 | 영향 |

| --------------------- | ------------------------------------- | ------------------- |

| Training-Serving Skew | 학습과 서빙에서 피처 계산 로직이 다름 | 모델 성능 저하 |

| 피처 중복 계산 | 팀마다 동일 피처를 각자 구현 | 컴퓨팅 자원 낭비 |

| 데이터 누수 | 미래 데이터가 학습에 포함됨 | 과적합, 잘못된 평가 |

| 피처 발견 어려움 | 어떤 피처가 존재하는지 알 수 없음 | 개발 생산성 저하 |

| 서빙 지연 | 실시간으로 피처를 계산하면 지연 발생 | 사용자 경험 악화 |

Online Store vs Offline Store

Feature Store는 두 가지 저장소를 기본으로 갖는다.

| 항목 | Online Store | Offline Store |

| ----------- | --------------- | ---------------------- |

| 용도 | 실시간 추론 | 모델 학습, 배치 추론 |

| 지연시간 | 1~10ms | 초~분 |

| 데이터 범위 | 최신 값만 | 전체 이력 |

| 저장소 예시 | Redis, DynamoDB | BigQuery, Redshift, S3 |

| 쿼리 패턴 | Key-Value 조회 | SQL/DataFrame 조회 |

| 데이터 양 | GB 수준 | TB~PB 수준 |

| 일관성 | 최종 일관성 | 강한 일관성 |

Feature Freshness와 Consistency

피처의 신선도(Freshness)는 얼마나 최근의 데이터를 반영하는지를 나타낸다.

- **배치 피처**: 1시간~1일 단위로 갱신 (예: 사용자의 최근 30일 구매 횟수)

- **스트리밍 피처**: 초~분 단위로 갱신 (예: 최근 5분간 거래 금액)

- **실시간 피처**: 요청 시점에 계산 (예: 현재 세션의 클릭 수)

Feast 아키텍처

전체 구조

Feast는 다음과 같은 컴포넌트로 구성된다.

feature_store.yaml - Feast 프로젝트 설정

project: fraud_detection

registry: gs://ml-feature-store/registry.db

provider: gcp

offline_store:

type: bigquery

dataset: feature_store

online_store:

type: redis

connection_string: 'redis-cluster.internal:6379'

redis_type: redis_cluster

entity_key_serialization_version: 2

| 컴포넌트 | 역할 | 설명 |

| ---------------------- | -------------------- | ------------------------------------------------- |

| Feature Registry | 피처 메타데이터 관리 | 피처 정의, 엔티티, 데이터 소스 정보 저장 |

| Offline Store | 이력 데이터 저장 | BigQuery, Redshift, Spark 등에서 학습 데이터 추출 |

| Online Store | 최신 피처 서빙 | Redis, DynamoDB 등에서 실시간 조회 |

| Feature Server | REST API 서빙 | FastAPI 기반 저지연 피처 서빙 엔드포인트 |

| Materialization Engine | 데이터 동기화 | Offline Store에서 Online Store로 피처 복사 |

Feature 정의와 Entity 설계

features/fraud_detection.py

from datetime import timedelta

from feast import Entity, FeatureView, Field, FileSource, BigQuerySource

from feast.types import Float32, Int64, String

Entity 정의 - 피처가 연결되는 대상

user_entity = Entity(

name="user_id",

join_keys=["user_id"],

description="Unique identifier for a user",

)

merchant_entity = Entity(

name="merchant_id",

join_keys=["merchant_id"],

description="Unique identifier for a merchant",

)

Data Source 정의

user_transactions_source = BigQuerySource(

name="user_transactions",

table="ml_data.user_transaction_features",

timestamp_field="event_timestamp",

created_timestamp_column="created_timestamp",

)

merchant_stats_source = BigQuerySource(

name="merchant_stats",

table="ml_data.merchant_statistics",

timestamp_field="event_timestamp",

)

Feature View 정의 - 피처 그룹

user_transaction_features = FeatureView(

name="user_transaction_features",

entities=[user_entity],

ttl=timedelta(days=7),

schema=[

Field(name="transaction_count_7d", dtype=Int64),

Field(name="transaction_amount_avg_7d", dtype=Float32),

Field(name="transaction_amount_max_7d", dtype=Float32),

Field(name="unique_merchants_7d", dtype=Int64),

Field(name="avg_time_between_transactions", dtype=Float32),

],

source=user_transactions_source,

online=True,

tags={

"team": "fraud-detection",

"version": "v2",

},

)

merchant_risk_features = FeatureView(

name="merchant_risk_features",

entities=[merchant_entity],

ttl=timedelta(days=30),

schema=[

Field(name="chargeback_rate_30d", dtype=Float32),

Field(name="avg_transaction_amount", dtype=Float32),

Field(name="total_transactions_30d", dtype=Int64),

Field(name="risk_score", dtype=Float32),

],

source=merchant_stats_source,

online=True,

tags={

"team": "fraud-detection",

"version": "v1",

},

)

Feature Service 정의

features/services.py

from feast import FeatureService

fraud_detection_service = FeatureService(

name="fraud_detection_v2",

features=[

user_transaction_features,

merchant_risk_features,

],

tags={

"model": "fraud_detector_v2",

"owner": "ml-team",

},

)

Materialization 파이프라인

배치 Materialization

Feast CLI로 Feature Registry 적용

feast apply

배치 Materialization 실행

feast materialize 2026-03-01T00:00:00 2026-03-12T00:00:00

증분 Materialization (마지막 실행 이후)

feast materialize-incremental 2026-03-12T00:00:00

Airflow를 활용한 자동화

dags/feast_materialization.py

from datetime import datetime, timedelta

from airflow import DAG

from airflow.operators.bash import BashOperator

from airflow.operators.python import PythonOperator

default_args = {

"owner": "ml-team",

"retries": 3,

"retry_delay": timedelta(minutes=5),

}

dag = DAG(

"feast_materialization",

default_args=default_args,

description="Daily feature materialization pipeline",

schedule_interval="0 6 * * *",

start_date=datetime(2026, 1, 1),

catchup=False,

tags=["feast", "mlops"],

)

Feature 소스 데이터 검증

validate_sources = PythonOperator(

task_id="validate_sources",

python_callable=lambda: __import__("feast").FeatureStore(

repo_path="/opt/feast/feature_repo"

),

dag=dag,

)

Materialization 실행

materialize = BashOperator(

task_id="materialize_features",

bash_command="""

cd /opt/feast/feature_repo && \

feast materialize-incremental $(date -u +%Y-%m-%dT%H:%M:%S)

""",

dag=dag,

)

Online Store 정합성 검증

validate_online = PythonOperator(

task_id="validate_online_store",

python_callable=lambda: print("Validating online store consistency..."),

dag=dag,

)

validate_sources >> materialize >> validate_online

Online Store 백엔드 설정

Redis 기반 Online Store

feature_store.yaml - Redis 설정

project: fraud_detection

registry: gs://ml-feature-store/registry.db

provider: gcp

online_store:

type: redis

connection_string: 'redis-cluster.internal:6379,redis-cluster.internal:6380,redis-cluster.internal:6381'

redis_type: redis_cluster

key_ttl_seconds: 604800 # 7일

DynamoDB 기반 Online Store

feature_store.yaml - DynamoDB 설정

project: fraud_detection

registry: s3://ml-feature-store/registry.db

provider: aws

online_store:

type: dynamodb

region: ap-northeast-2

table_name_template: 'feast_online_{project}_{table}'

Online Store 백엔드 비교

| 항목 | Redis | DynamoDB | PostgreSQL |

| ----------- | --------------- | ------------- | ----------------- |

| 지연시간 | 0.5~2ms | 1~5ms | 2~10ms |

| 확장성 | 수동 (클러스터) | 자동 | 수동 |

| 비용 모델 | 인스턴스 기반 | 요청 기반 | 인스턴스 기반 |

| TTL 지원 | 네이티브 | 네이티브 | 수동 구현 |

| 운영 부담 | 중간 | 낮음 | 중간 |

| 적합한 환경 | 초저지연 필요 | 서버리스, AWS | 소규모, 비용 민감 |

Offline Store 설정

BigQuery 기반 Offline Store

feature_store.yaml - BigQuery 설정

project: fraud_detection

registry: gs://ml-feature-store/registry.db

provider: gcp

offline_store:

type: bigquery

dataset: feature_store

location: asia-northeast3

학습 데이터 생성 (Point-in-Time Join)

training_data.py

from feast import FeatureStore

store = FeatureStore(repo_path="./feature_repo")

Entity DataFrame - 학습에 사용할 시점과 엔티티

entity_df = pd.DataFrame({

"user_id": ["user_001", "user_002", "user_003", "user_001"],

"merchant_id": ["merch_100", "merch_200", "merch_100", "merch_300"],

"event_timestamp": pd.to_datetime([

"2026-03-01 10:00:00",

"2026-03-02 14:30:00",

"2026-03-03 09:15:00",

"2026-03-05 16:45:00",

]),

"label": [0, 1, 0, 1], # 사기 여부

})

Point-in-Time 정확한 피처 추출

training_df = store.get_historical_features(

entity_df=entity_df,

features=[

"user_transaction_features:transaction_count_7d",

"user_transaction_features:transaction_amount_avg_7d",

"user_transaction_features:transaction_amount_max_7d",

"user_transaction_features:unique_merchants_7d",

"merchant_risk_features:chargeback_rate_30d",

"merchant_risk_features:risk_score",

],

).to_df()

print(training_df.head())

print(f"Training data shape: {training_df.shape}")

Online 피처 조회 (실시간 추론)

inference.py

from feast import FeatureStore

store = FeatureStore(repo_path="./feature_repo")

실시간 피처 조회

feature_vector = store.get_online_features(

features=[

"user_transaction_features:transaction_count_7d",

"user_transaction_features:transaction_amount_avg_7d",

"user_transaction_features:unique_merchants_7d",

"merchant_risk_features:chargeback_rate_30d",

"merchant_risk_features:risk_score",

],

entity_rows=[

{"user_id": "user_001", "merchant_id": "merch_100"},

],

).to_dict()

print(feature_vector)

출력 예시:

{

"user_id": ["user_001"],

"merchant_id": ["merch_100"],

"transaction_count_7d": [23],

"transaction_amount_avg_7d": [45000.5],

"unique_merchants_7d": [8],

"chargeback_rate_30d": [0.02],

"risk_score": [0.15]

}

Training-Serving Skew 방지 전략

Training-Serving Skew는 ML 모델의 성능을 저하시키는 가장 흔한 원인 중 하나다.

Skew 발생 원인과 대응

| 원인 | 설명 | 대응 방법 |

| --------------------- | ----------------------------- | ----------------------------- |

| 피처 계산 로직 불일치 | 학습/서빙에서 다른 코드 사용 | Feature Store로 단일 소스화 |

| 데이터 누수 | 미래 데이터가 학습에 포함 | Point-in-Time Join 적용 |

| 피처 신선도 차이 | 배치 vs 실시간 갱신 주기 차이 | TTL 관리와 Freshness 모니터링 |

| 스키마 변경 | 피처 정의가 변경됨 | Feature Registry 버전 관리 |

| NULL 처리 차이 | 기본값 처리 방식 불일치 | 통일된 기본값 정책 설정 |

Feast를 통한 Skew 방지

skew_detection.py

from feast import FeatureStore

from scipy import stats

store = FeatureStore(repo_path="./feature_repo")

def detect_training_serving_skew(

feature_name: str,

training_values: pd.Series,

sample_size: int = 1000,

):

"""학습 데이터와 Online Store의 피처 분포를 비교한다."""

Online Store에서 샘플링

online_features = []

entity_rows = [{"user_id": f"user_{i:04d}"} for i in range(sample_size)]

online_result = store.get_online_features(

features=[feature_name],

entity_rows=entity_rows,

).to_df()

serving_values = online_result[feature_name.split(":")[-1]].dropna()

KS 테스트로 분포 비교

ks_stat, p_value = stats.ks_2samp(

training_values.dropna(),

serving_values,

)

PSI (Population Stability Index) 계산

psi = calculate_psi(training_values.dropna(), serving_values)

return {

"feature": feature_name,

"ks_statistic": ks_stat,

"p_value": p_value,

"psi": psi,

"skew_detected": psi > 0.2 or p_value < 0.05,

}

def calculate_psi(expected, actual, bins=10):

"""PSI(Population Stability Index)를 계산한다."""

breakpoints = np.linspace(

min(expected.min(), actual.min()),

max(expected.max(), actual.max()),

bins + 1,

)

expected_counts = np.histogram(expected, breakpoints)[0] / len(expected)

actual_counts = np.histogram(actual, breakpoints)[0] / len(actual)

0 방지

expected_counts = np.clip(expected_counts, 0.001, None)

actual_counts = np.clip(actual_counts, 0.001, None)

psi = np.sum(

(actual_counts - expected_counts) * np.log(actual_counts / expected_counts)

)

return psi

Feature Monitoring과 Drift Detection

모니터링 지표

monitoring/feature_monitor.py

from dataclasses import dataclass

from datetime import datetime

from typing import Optional

@dataclass

class FeatureStats:

feature_name: str

timestamp: datetime

mean: float

std: float

min_val: float

max_val: float

null_rate: float

unique_count: int

p99_latency_ms: Optional[float] = None

def compute_feature_stats(df: pd.DataFrame, feature_name: str) -> FeatureStats:

"""피처의 통계 정보를 계산한다."""

series = df[feature_name]

return FeatureStats(

feature_name=feature_name,

timestamp=datetime.utcnow(),

mean=series.mean(),

std=series.std(),

min_val=series.min(),

max_val=series.max(),

null_rate=series.isnull().sum() / len(series),

unique_count=series.nunique(),

)

def check_drift_alerts(

current: FeatureStats,

baseline: FeatureStats,

thresholds: dict,

) -> list:

"""피처 드리프트 알림을 확인한다."""

alerts = []

평균 변화율 확인

if baseline.mean != 0:

mean_change = abs(current.mean - baseline.mean) / abs(baseline.mean)

if mean_change > thresholds.get("mean_change", 0.3):

alerts.append(

f"Mean drift detected: {baseline.mean:.4f} -> {current.mean:.4f} "

f"(change: {mean_change:.2%})"

)

NULL 비율 변화

null_diff = abs(current.null_rate - baseline.null_rate)

if null_diff > thresholds.get("null_rate_change", 0.05):

alerts.append(

f"Null rate change: {baseline.null_rate:.4f} -> {current.null_rate:.4f}"

)

범위 이상

if current.max_val > baseline.max_val * thresholds.get("max_multiplier", 2.0):

alerts.append(

f"Max value anomaly: {current.max_val} (baseline max: {baseline.max_val})"

)

return alerts

Feature Store 솔루션 비교

| 항목 | Feast | Tecton | Hopsworks |

| ----------- | --------------------------------- | ---------------------- | ---------------------- |

| 라이선스 | Apache 2.0 (오픈소스) | 상용 (관리형) | AGPL + 상용 |

| 아키텍처 | 모듈형, 플러거블 | 관리형, 엔드투엔드 | 통합 플랫폼 |

| 실시간 피처 | 제한적 | 네이티브 지원 | 지원 |

| 스트리밍 | Push 기반 | Kafka/Kinesis 네이티브 | Kafka 연동 |

| 피처 변환 | Python SDK | Spark/Pandas/SQL | Spark/Flink |

| 모니터링 | 기본적 | 내장 (자동 알림) | 내장 (드리프트 탐지) |

| 거버넌스 | 기본적 | RBAC, 감사 로그 | RBAC, 감사, 계보 추적 |

| 클라우드 | 멀티 클라우드 | AWS/Databricks | AWS/Azure/GCP |

| 적합한 조직 | 유연성 중시, 엔지니어링 역량 보유 | 대기업, 실시간 ML 필수 | 규제 산업, 올인원 필요 |

| 커뮤니티 | 매우 활발 (CNCF 관련) | 상용 지원 | 활발 |

운영 시 주의사항

1. Entity 설계 원칙

- Entity 키는 비즈니스 도메인에 맞게 설계한다 (user_id, order_id, device_id 등)

- 복합 Entity 키는 조회 성능에 영향을 미치므로 신중하게 사용한다

- Entity 키의 카디널리티가 너무 높으면 Online Store의 메모리 사용량이 급증한다

2. TTL 관리

- Online Store의 TTL은 Materialization 주기보다 넉넉하게 설정한다

- TTL이 너무 짧으면 Materialization 지연 시 NULL 값이 반환된다

- TTL이 너무 길면 Online Store의 스토리지 비용이 증가한다

3. 스키마 변경 관리

- 피처 추가는 하위 호환이 가능하지만, 피처 제거나 타입 변경은 모델 재학습이 필요하다

- Feature View의 버전 관리를 위해 이름에 버전을 포함한다 (예: `user_features_v2`)

- 스키마 변경 시 기존 모델과의 호환성을 반드시 검증한다

4. Materialization 실패 대응

Materialization 상태 확인

feast materialize-incremental 2026-03-12T00:00:00 --verbose

특정 Feature View만 재실행

feast materialize-incremental 2026-03-12T00:00:00 \

--feature-views user_transaction_features

Online Store의 피처 신선도 확인

feast feature-views list

장애 사례와 복구 절차

장애 사례 1: Online Store 장애 (Redis 클러스터 다운)

**증상**: 모든 실시간 추론 요청에서 피처 조회 실패

**복구 절차**:

1. Redis 클러스터 상태 확인 및 복구

2. 복구 불가 시 백업 Redis로 전환 (Sentinel/Cluster Failover)

3. Materialization을 재실행하여 Online Store 데이터 복원

4. 피처 신선도 검증 후 추론 서비스 재개

장애 사례 2: Materialization 파이프라인 실패

**증상**: Online Store의 피처 데이터가 갱신되지 않아 오래된 값이 서빙됨

피처 신선도 확인 스크립트

from feast import FeatureStore

from datetime import datetime, timedelta

store = FeatureStore(repo_path="./feature_repo")

Feature View별 최종 Materialization 시간 확인

for fv in store.list_feature_views():

if fv.materialization_intervals:

last_mat = fv.materialization_intervals[-1]

staleness = datetime.utcnow() - last_mat.end_date

if staleness > timedelta(hours=24):

print(f"ALERT: {fv.name} is stale by {staleness}")

else:

print(f"OK: {fv.name} last materialized at {last_mat.end_date}")

else:

print(f"WARNING: {fv.name} has never been materialized")

**복구 절차**:

1. Materialization 로그에서 실패 원인 파악 (Offline Store 접근 문제, 스키마 변경 등)

2. 데이터 소스 가용성 확인

3. 실패한 Feature View에 대해 Materialization 재시도

4. Online Store의 피처 정합성 검증

장애 사례 3: Feature Drift 탐지

**증상**: 모델 성능 지표가 점진적으로 저하

**복구 절차**:

1. Feature Monitoring 대시보드에서 드리프트 확인

2. 원인 파악 (데이터 파이프라인 변경, 업스트림 스키마 변경, 실제 분포 변화)

3. 필요시 피처 파이프라인 수정

4. 심각한 드리프트의 경우 모델 재학습 트리거

프로덕션 배포 체크리스트

프로덕션 환경에 Feature Store를 배포할 때 다음 항목을 반드시 확인한다.

| 항목 | 체크 포인트 | 권장 설정 |

| -------------------- | ------------------------------ | ---------------------------- |

| Online Store 가용성 | 클러스터 구성, 복제본 | Redis Cluster 3+ 노드 |

| Materialization 주기 | 비즈니스 요구 대비 신선도 | SLA에 맞는 주기 설정 |

| TTL 설정 | Materialization 실패 시 영향 | Materialization 주기의 2~3배 |

| 백업 전략 | Online/Offline Store 백업 | 일일 스냅샷 |

| 모니터링 | 피처 드리프트, 지연시간 | Prometheus + Grafana |

| 알림 | Materialization 실패, 드리프트 | PagerDuty/Slack 연동 |

| 보안 | 인증/인가, 네트워크 | IAM, VPC, TLS |

마치며

Feature Store는 ML 모델의 프로덕션 운영에서 피처 관리의 복잡성을 해결하는 핵심 인프라다. Feast는 오픈소스의 유연성과 모듈형 아키텍처를 통해 기존 인프라에 자연스럽게 통합할 수 있는 선택지를 제공한다.

핵심 정리는 다음과 같다.

- **Online/Offline Store 분리**: 실시간 서빙과 배치 학습의 요구사항을 각각 최적화한다

- **Point-in-Time Correctness**: Feature Store의 시간 여행 쿼리로 데이터 누수를 근본적으로 방지한다

- **Training-Serving Skew 방지**: 단일 피처 정의에서 학습과 서빙 모두를 지원하여 일관성을 보장한다

- **Materialization 자동화**: Airflow 등과 연동하여 피처 갱신 파이프라인을 안정적으로 운영한다

- **Feature Monitoring**: 드리프트 탐지와 피처 품질 모니터링으로 모델 성능을 지속적으로 유지한다

Feature Store 도입은 단일 모델이 아닌 조직 전체의 ML 성숙도를 높이는 투자다. 소규모로 시작하여 하나의 프로덕션 모델에서 검증한 뒤 점진적으로 확대하는 접근을 권장한다.

참고자료

- [Feast Official Documentation](https://docs.feast.dev)

- [Feast Architecture Overview](https://docs.feast.dev/getting-started/architecture/overview)

- [Feature Store Architecture and Storage - DragonflyDB](https://www.dragonflydb.io/blog/feature-store-architecture-and-storage)

- [A Comparative Analysis: Feast vs Tecton vs Hopsworks - Uplatz](https://uplatz.com/blog/a-comparative-analysis-of-modern-feature-stores-feast-vs-tecton-vs-hopsworks/)

- [Feature Store 101: Build, Serve, and Scale ML Features - Aerospike](https://aerospike.com/blog/feature-store/)

- [What is a Feature Store? - Databricks](https://www.databricks.com/blog/what-feature-store-complete-guide-ml-feature-engineering)

- [Solving Training-Serving Skew with Feast - Medium](https://medium.com/@scoopnisker/solving-the-training-serving-skew-problem-with-feast-feature-store-3719b47e23a2)

현재 단락 (1/411)

머신러닝 모델의 프로덕션 배포가 보편화되면서, 피처(Feature) 관리가 MLOps의 핵심 과제로 떠올랐다. 모델 학습에 사용한 피처를 실시간 서빙에서도 동일하게 재현해야 하고,...

작성 글자: 0원문 글자: 14,870작성 단락: 0/411