Skip to content

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

✨ Learn with Quiz
|

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

Feature Store with Feast

들어가며

머신러닝 모델의 프로덕션 배포가 보편화되면서, 피처(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 StoreOffline Store
용도실시간 추론모델 학습, 배치 추론
지연시간1~10ms초~분
데이터 범위최신 값만전체 이력
저장소 예시Redis, DynamoDBBigQuery, 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 ServerREST 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 백엔드 비교

항목RedisDynamoDBPostgreSQL
지연시간0.5~2ms1~5ms2~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
import pandas as pd

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
import pandas as pd
import numpy as np
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
import pandas as pd

@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 솔루션 비교

항목FeastTectonHopsworks
라이선스Apache 2.0 (오픈소스)상용 (관리형)AGPL + 상용
아키텍처모듈형, 플러거블관리형, 엔드투엔드통합 플랫폼
실시간 피처제한적네이티브 지원지원
스트리밍Push 기반Kafka/Kinesis 네이티브Kafka 연동
피처 변환Python SDKSpark/Pandas/SQLSpark/Flink
모니터링기본적내장 (자동 알림)내장 (드리프트 탐지)
거버넌스기본적RBAC, 감사 로그RBAC, 감사, 계보 추적
클라우드멀티 클라우드AWS/DatabricksAWS/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 성숙도를 높이는 투자다. 소규모로 시작하여 하나의 프로덕션 모델에서 검증한 뒤 점진적으로 확대하는 접근을 권장한다.

참고자료

Feature Store Design and Operations Guide: Building Online/Offline Stores with Feast and ML Feature Pipeline Automation

Feature Store with Feast

Introduction

As production deployment of machine learning models becomes commonplace, feature management has emerged as a core challenge in MLOps. Features used for model training must be identically reproduced during real-time serving, multiple teams need to share the same features to avoid redundant computation, and feature quality and freshness must be continuously monitored.

Feature Store is an infrastructure layer designed to solve these problems by centrally managing feature definition, storage, serving, and monitoring. Among them, Feast (Feature Store) is the most widely used open-source Feature Store, providing flexible feature serving while reusing existing data infrastructure.

This article covers Feature Store core concepts, Feast architecture, feature definitions and entity design, materialization pipelines, Online/Offline Store configuration, training-serving skew prevention strategies, feature monitoring, comparison with Tecton/Hopsworks, production deployment patterns, and failure recovery procedures.

Feature Store Core Concepts

Why You Need a Feature Store

Operating an ML pipeline without a Feature Store leads to the following problems.

ProblemDescriptionImpact
Training-Serving SkewFeature computation logic differs between training and servingDegraded model performance
Redundant Feature ComputationEach team implements the same features independentlyWasted computing resources
Data LeakageFuture data included in trainingOverfitting, incorrect evaluation
Feature Discovery DifficultyCannot know what features existReduced development productivity
Serving LatencyComputing features in real-time causes delaysDegraded user experience

Online Store vs Offline Store

A Feature Store has two fundamental storage systems.

ItemOnline StoreOffline Store
PurposeReal-time inferenceModel training, batch inference
Latency1-10msSeconds to minutes
Data ScopeLatest values onlyFull history
Storage ExamplesRedis, DynamoDBBigQuery, Redshift, S3
Query PatternKey-Value lookupSQL/DataFrame queries
Data VolumeGB scaleTB-PB scale
ConsistencyEventual consistencyStrong consistency

Feature Freshness and Consistency

Feature freshness indicates how recently the data reflects the current state.

  • Batch features: Updated hourly to daily (e.g., number of purchases in the last 30 days)
  • Streaming features: Updated every seconds to minutes (e.g., transaction amount in the last 5 minutes)
  • Real-time features: Computed at request time (e.g., number of clicks in the current session)

Feast Architecture

Overall Structure

Feast consists of the following components.

# feature_store.yaml - Feast project configuration
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
ComponentRoleDescription
Feature RegistryFeature metadata managementStores feature definitions, entities, and data source information
Offline StoreHistorical data storageExtracts training data from BigQuery, Redshift, Spark, etc.
Online StoreLatest feature servingReal-time lookups from Redis, DynamoDB, etc.
Feature ServerREST API servingLow-latency feature serving endpoint based on FastAPI
Materialization EngineData synchronizationCopies features from Offline Store to Online Store

Feature Definitions and Entity Design

# features/fraud_detection.py
from datetime import timedelta
from feast import Entity, FeatureView, Field, FileSource, BigQuerySource
from feast.types import Float32, Int64, String

# Entity definition - the target that features are linked to
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 definition
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 definition - a group of features
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 Definition

# 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 Pipeline

Batch Materialization

# Apply Feature Registry with Feast CLI
feast apply

# Run batch materialization
feast materialize 2026-03-01T00:00:00 2026-03-12T00:00:00

# Incremental materialization (since last run)
feast materialize-incremental 2026-03-12T00:00:00

Automation with 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"],
)

# Validate feature source data
validate_sources = PythonOperator(
    task_id="validate_sources",
    python_callable=lambda: __import__("feast").FeatureStore(
        repo_path="/opt/feast/feature_repo"
    ),
    dag=dag,
)

# Run 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,
)

# Validate Online Store consistency
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 Backend Configuration

Redis-Based Online Store

# feature_store.yaml - Redis configuration
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 days

DynamoDB-Based Online Store

# feature_store.yaml - DynamoDB configuration
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 Backend Comparison

ItemRedisDynamoDBPostgreSQL
Latency0.5-2ms1-5ms2-10ms
ScalabilityManual (cluster)AutomaticManual
Cost ModelInstance-basedRequest-basedInstance-based
TTL SupportNativeNativeManual implementation
Operational OverheadMediumLowMedium
Best ForUltra-low latencyServerless, AWSSmall scale, cost-sensitive

Offline Store Configuration

BigQuery-Based Offline Store

# feature_store.yaml - BigQuery configuration
project: fraud_detection
registry: gs://ml-feature-store/registry.db
provider: gcp

offline_store:
  type: bigquery
  dataset: feature_store
  location: asia-northeast3

Training Data Generation (Point-in-Time Join)

# training_data.py
from feast import FeatureStore
import pandas as pd

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

# Entity DataFrame - timestamps and entities for training
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],  # fraud label
})

# Extract features with point-in-time correctness
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 Feature Retrieval (Real-Time Inference)

# inference.py
from feast import FeatureStore

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

# Real-time feature retrieval
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)
# Example output:
# {
#   "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 Prevention Strategies

Training-Serving Skew is one of the most common causes of ML model performance degradation.

Skew Causes and Countermeasures

CauseDescriptionCountermeasure
Feature computation logic mismatchDifferent code used in training/servingUnify source through Feature Store
Data leakageFuture data included in trainingApply Point-in-Time Join
Feature freshness differenceBatch vs real-time update cycle mismatchTTL management and freshness monitoring
Schema changesFeature definitions changedFeature Registry version control
NULL handling differencesInconsistent default value handlingSet unified default value policy

Skew Prevention with Feast

# skew_detection.py
import pandas as pd
import numpy as np
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,
):
    """Compare feature distributions between training data and Online Store."""
    # Sample from 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 test for distribution comparison
    ks_stat, p_value = stats.ks_2samp(
        training_values.dropna(),
        serving_values,
    )

    # PSI (Population Stability Index) calculation
    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):
    """Calculate Population Stability Index (PSI)."""
    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)

    # Avoid division by zero
    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 and Drift Detection

Monitoring Metrics

# monitoring/feature_monitor.py
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
import pandas as pd

@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:
    """Compute statistical information for a feature."""
    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:
    """Check for feature drift alerts."""
    alerts = []

    # Check mean change rate
    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 rate change
    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}"
        )

    # Range anomaly
    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 Solution Comparison

ItemFeastTectonHopsworks
LicenseApache 2.0 (Open Source)Commercial (Managed)AGPL + Commercial
ArchitectureModular, pluggableManaged, end-to-endIntegrated platform
Real-time FeaturesLimitedNative supportSupported
StreamingPush-basedKafka/Kinesis nativeKafka integration
Feature TransformationPython SDKSpark/Pandas/SQLSpark/Flink
MonitoringBasicBuilt-in (auto-alerts)Built-in (drift detection)
GovernanceBasicRBAC, audit logsRBAC, audit, lineage tracking
CloudMulti-cloudAWS/DatabricksAWS/Azure/GCP
Best ForFlexibility-focused orgs with engineering capacityEnterprises needing real-time MLRegulated industries, all-in-one
CommunityVery active (CNCF-related)Commercial supportActive

Operational Notes

1. Entity Design Principles

  • Design entity keys to align with your business domain (user_id, order_id, device_id, etc.)
  • Use composite entity keys carefully as they affect lookup performance
  • Excessively high cardinality entity keys can cause Online Store memory usage to spike

2. TTL Management

  • Set Online Store TTL generously relative to the materialization cycle
  • If TTL is too short, NULL values are returned when materialization is delayed
  • If TTL is too long, Online Store storage costs increase

3. Schema Change Management

  • Adding features is backward compatible, but removing features or changing types requires model retraining
  • Include versions in Feature View names for version management (e.g., user_features_v2)
  • Always verify compatibility with existing models when changing schemas

4. Materialization Failure Response

# Check materialization status
feast materialize-incremental 2026-03-12T00:00:00 --verbose

# Rerun for specific Feature View only
feast materialize-incremental 2026-03-12T00:00:00 \
  --feature-views user_transaction_features

# Check feature freshness in Online Store
feast feature-views list

Failure Cases and Recovery Procedures

Failure Case 1: Online Store Failure (Redis Cluster Down)

Symptom: Feature retrieval fails for all real-time inference requests

Recovery procedure:

  1. Check and recover Redis cluster status
  2. If recovery is not possible, switch to backup Redis (Sentinel/Cluster Failover)
  3. Rerun materialization to restore Online Store data
  4. Verify feature freshness and resume inference service

Failure Case 2: Materialization Pipeline Failure

Symptom: Feature data in Online Store is not updated, serving stale values

# Feature freshness check script
from feast import FeatureStore
from datetime import datetime, timedelta

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

# Check last materialization time per Feature View
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")

Recovery procedure:

  1. Identify the failure cause from materialization logs (Offline Store access issues, schema changes, etc.)
  2. Verify data source availability
  3. Retry materialization for the failed Feature View
  4. Validate feature consistency in the Online Store

Failure Case 3: Feature Drift Detected

Symptom: Model performance metrics gradually degrade

Recovery procedure:

  1. Confirm drift in the feature monitoring dashboard
  2. Identify the root cause (data pipeline changes, upstream schema changes, actual distribution shifts)
  3. Modify the feature pipeline if necessary
  4. Trigger model retraining for severe drift

Production Deployment Checklist

Always verify the following items when deploying a Feature Store to production.

ItemCheckpointRecommended Setting
Online Store AvailabilityCluster configuration, replicasRedis Cluster 3+ nodes
Materialization CycleFreshness vs business requirementsSet cycle matching SLA
TTL SettingImpact on materialization failure2-3x materialization cycle
Backup StrategyOnline/Offline Store backupsDaily snapshots
MonitoringFeature drift, latencyPrometheus + Grafana
AlertingMaterialization failure, driftPagerDuty/Slack integration
SecurityAuthentication/authorization, networkIAM, VPC, TLS

Conclusion

Feature Store is a core infrastructure component that solves the complexity of feature management in production ML model operations. Feast provides the flexibility of open source and a modular architecture that naturally integrates with existing infrastructure.

Here is a summary of the key takeaways.

  • Online/Offline Store separation: Optimize requirements for real-time serving and batch training independently
  • Point-in-Time Correctness: Fundamentally prevent data leakage through Feature Store time-travel queries
  • Training-Serving Skew prevention: Ensure consistency by supporting both training and serving from a single feature definition
  • Materialization automation: Reliably operate feature refresh pipelines by integrating with Airflow and similar tools
  • Feature Monitoring: Maintain model performance continuously through drift detection and feature quality monitoring

Adopting a Feature Store is an investment that raises the ML maturity of the entire organization, not just a single model. Start small, validate with one production model, and gradually expand.

References