Feature Stores for ML

Data EngineeringMachine LearningMLOpsFeature Store
Share on LinkedIn

The fraud model looked brilliant offline — until production recall cratered because training joined user_chargeback_count including chargebacks that happened after the transaction being labeled. Data scientists rebuilt the join three times in Spark notebooks; the serving team copied a different SQL snippet into the API. Feature stores exist to make that class of failure boring: define features once, backfill history with point-in-time correctness, serve the same values online at inference, and version definitions like API schema.

Offline vs online stores

Store Latency Use
Offline (warehouse, Parquet) Minutes–hours Training, batch scoring
Online (Redis, DynamoDB) Milliseconds Real-time inference
                    ┌──► Offline (BigQuery/Snowflake)
Feature definition ─┤
                    └──► Online (Redis) via materialization job

Materialization pushes latest feature values from offline compute to online keys on schedule or on change.

Feast example

Define entities and features:

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

user = Entity(name="user_id", join_keys=["user_id"])

user_stats_source = FileSource(
    path="data/user_stats.parquet",
    timestamp_field="event_timestamp",
)

user_stats_fv = FeatureView(
    name="user_stats",
    entities=[user],
    ttl=timedelta(days=7),
    schema=[
        Field(name="transaction_count_7d", dtype=Int64),
        Field(name="avg_amount_7d", dtype=Float32),
    ],
    source=user_stats_source,
)

Historical retrieval for training:

from feast import FeatureStore

store = FeatureStore(repo_path=".")
training_df = store.get_historical_features(
    entity_df=entity_df,  # user_id + event_timestamp per label row
    features=["user_stats:transaction_count_7d", "user_stats:avg_amount_7d"],
).to_df()

Feast performs point-in-time join — feature values at or before each label timestamp only.

Online serving:

features = store.get_online_features(
    features=["user_stats:transaction_count_7d"],
    entity_rows=[{"user_id": 12345}],
).to_dict()

Point-in-time join intuition

Label row: user 123 made transaction at T.

Wrong: join current feature snapshot at training run date T+30 — leaks future.

Correct: join latest feature event where feature_timestamp <= T.

Feature stores encode this in get_historical_features — manual SQL rarely gets it right across dozens of feature tables.

Training-serving skew sources

Skew source Feature store mitigation
Different SQL logic Single feature definition
Different normalization Shared transform in pipeline
Missing values handled differently Central imputation spec
Stale online values TTL monitoring, freshness SLAs
Wrong entity key mapping Entity registry

Log online feature vectors with model version for debugging production mispredictions.

Feature registry and versioning

Registry tracks:

Breaking change → new FeatureView name (user_stats_v2) — train new model, cutover, deprecate v1.

Lineage to dbt models or Spark jobs documents upstream freshness.

Architecture options

Start minimal: one high-value feature group (user aggregates) end-to-end before platformizing.

Operational metrics

Feature stores are organizational glue as much as technology — success requires data eng + ML agreeing on ownership of definitions.

Streaming features

Batch feature computation (nightly Spark job) isn't enough for real-time models — fraud detection, recommendations, dynamic pricing need streaming features:

Event stream → Flink/Spark Streaming → Online store (Redis)
                    ↓
              Offline store (warehouse) for training backfill

Feast supports streaming sources:

from feast import StreamFeatureView
from feast.data_source import PushSource

push_source = PushSource(
    name="user_transactions_push",
    batch_source=user_stats_source,
)

@stream_feature_view(
    sources=[push_source],
    entities=[user],
    mode="spark",
    ttl=timedelta(hours=1),
)
def user_realtime_stats(df):
    return df.groupBy("user_id").agg(
        count("transaction_id").alias("transaction_count_1h"),
        avg("amount").alias("avg_amount_1h"),
    )

Streaming and batch features coexist — training uses batch historical values; serving uses streaming for low-latency inference.

Feature store vs dbt marts

Overlap causes organizational confusion:

Concern dbt mart Feature store
Audience Analysts, BI dashboards ML models, real-time inference
Freshness Hourly/daily batch Seconds (online store)
Point-in-time joins Manual, error-prone Built-in
Versioning Git + dbt manifest Feature registry
Serving latency Seconds (warehouse query) Milliseconds (Redis)

Pattern: dbt builds the mart; feature store reads from mart for offline features and materializes to online store. Don't duplicate computation — feature store references dbt output as batch source.

Failure modes

Production checklist

Evaluate managed feature stores (Tecton, SageMaker Feature Store) when Feast ops overhead exceeds team capacity — the organizational patterns remain the same regardless of vendor.

Feature store maturity stages

Most teams don't need a feature store on day one. Progress through stages:

Stage 1 — dbt marts + Redis cache: Features computed in dbt, cached in Redis for online serving. Sufficient for 1–3 models.

Stage 2 — Feast offline/online: Point-in-time correct joins, materialization jobs, feature registry. Needed when 5+ models share features.

Stage 3 — Managed (Tecton/SageMaker): Real-time feature pipelines, automatic backfill, monitoring. Needed when ML team exceeds 10 engineers.

Skipping stages creates platform overhead before product-market fit. Start with Stage 1; migrate when training-serving skew becomes a recurring incident.

Training-serving skew detection

The most common feature store failure — training and serving use different feature values:

def audit_training_serving_skew(feature_name: str, sample_size: int = 1000):
    offline = feast.get_historical_features(
        entity_df=sample_entities,
        features=[f"{feature_name}"],
    )
    online = [feast.get_online_features(
        features=[f"{feature_name}"],
        entity_rows=[{"user_id": id}],
    ).to_dict() for id in sample_entities["user_id"]]

    diffs = compare(offline, online)
    skew_rate = sum(d != 0 for d in diffs) / len(diffs)
    if skew_rate > 0.01:
        alert(f"Training-serving skew detected: {feature_name} skew_rate={skew_rate:.2%}")

Run skew audit weekly on all production features. >1% skew rate indicates materialization lag or logic divergence.

Feature freshness monitoring

Online features must be fresh — stale features silently degrade model quality:

-- Alert when feature materialization is stale
SELECT feature_name, MAX(materialized_at) AS last_materialized,
       NOW() - MAX(materialized_at) AS staleness
FROM feature_materialization_log
GROUP BY feature_name
HAVING NOW() - MAX(materialized_at) > INTERVAL '1 hour';

Define freshness SLA per feature: user profile features (1 hour), real-time behavioral features (5 minutes), daily aggregates (24 hours).

Resources

Frequently asked questions

What problem does a feature store solve?

Feature stores centralize feature definitions, compute, and serving so training and inference use the same logic. They provide offline stores for batch training with point-in-time correct joins and online stores for low-latency inference — reducing training-serving skew and duplicate feature pipelines across teams.

When do I need a feature store versus SQL views?

SQL views work for small teams with few models and tolerant latency. Adopt a feature store when multiple models reuse features, you need sub-10ms online serving, point-in-time historical joins are painful manually, or feature versioning and lineage become compliance requirements.

What is point-in-time correctness?

Training labels must join feature values as they existed at prediction time — not future-leaked values. Point-in-time joins filter feature history by event timestamp, preventing inflated offline metrics that collapse in production.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →