Skip to content

필사 모드: Feature Store 设计与运维指南:基于 Feast 构建 Online/Offline Store 与 ML 特征流水线自动化

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
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特征元数据管理存储特征定义、Entity、数据源信息
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"],
)

# 验证特征源数据
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 支持原生支持原生支持需手动实现
运维负担中等较低中等
适用场景需要超低延迟Serverless、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
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
Schema 变更特征定义发生了变化对 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)

    # 防止除零
    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 + 商业
架构模块化、可插拔托管、端到端集成平台
实时特征有限原生支持支持
流式处理基于 PushKafka/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. Schema 变更管理

  • 新增特征是向后兼容的,但删除特征或更改类型则需要重新训练模型
  • 为便于 Feature View 的版本管理,应在名称中包含版本号(例如:user_features_v2
  • Schema 变更时,务必验证与现有模型的兼容性

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 访问问题、Schema 变更等)
  2. 确认数据源的可用性
  3. 对失败的 Feature View 重试 Materialization
  4. 验证 Online Store 的特征一致性

故障案例 3:检测到 Feature Drift

症状:模型性能指标逐渐下降

恢复流程

  1. 在 Feature Monitoring 仪表盘中确认漂移情况
  2. 查明原因(数据流水线变更、上游 Schema 变更、实际分布变化)
  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 成熟度的投资。建议从小规模起步,在一个生产模型上验证之后,再逐步扩大范围。

参考资料

현재 단락 (1/411)

随着机器学习模型的生产部署越来越普遍,特征(Feature)管理已经成为 MLOps 的核心课题。训练模型时使用的特征,必须在实时服务中原样复现;多个团队要共享同一份特征,避免重复计算;特征的质量与新...

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