Feature Stores: Feast, Feature Engineering, and Online/Offline Serving
1731 words
9 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# Feature Stores: Feast, Feature Engineering, and Online/Offline Serving ## 🎯 Learning Objectives - Understand the problems feature stores solve in ML production - Design feature pipelines for batch and streaming data - Implement point-in-time correct feature retrieval - Use Feast for feature management, serving, a...

Feature Stores: Feast, Feature Engineering, and Online/Offline Serving
🎯 Learning Objectives
- Understand the problems feature stores solve in ML production
- Design feature pipelines for batch and streaming data
- Implement point-in-time correct feature retrieval
- Use Feast for feature management, serving, and discovery
- Compare feature store solutions and choose the right one
📋 Prerequisites
- MLOps Lifecycle (Week 1): ML pipeline stages
- Data Engineering basics: Batch processing, ETL, SQL
- Docker basics (recommended): Container concepts
1. 📖 Core Content
1.1 Intuition: The Feature Problem
Imagine building a fraud detection system. Data scientists experiment with hundreds of features: transaction amount, user frequency, merchant category, time since last login, etc. Each model uses a different set of features.
The problems:
- Inconsistency: Training features differ from serving features (training uses pandas, serving uses SQL)
- Duplication: Every team builds the same features (average transaction amount)
- Time travel: Training uses features from the past; serving uses real-time features. If not handled correctly, feature distributions differ.
- Discovery: Data scientists don't know what features exist — they rebuild them
- Serving latency: Batch features (daily aggregates) need different infrastructure than real-time features (seconds-old data) A feature store solves all of these by providing a centralized repository of features with consistent computation and serving.
1.2 Feature Store Architecture
(Diagram)
Key components:
- Offline Store: Column-oriented database (BigQuery, Redshift, Parquet) for batch feature computation and training data extraction. Stores historical feature values.
- Online Store: Low-latency database (Redis, DynamoDB, Cassandra) for serving real-time features. Stores the latest feature values.
- Feature Registry: Metadata catalog of all features, their sources, transformations, and owners.
- Feature Pipelines: Batch (Spark, Beam) and streaming (Kafka, Flink) pipelines that compute features from raw data.
1.3 Point-in-Time Correctness
The most subtle challenge in feature stores: point-in-time joins.
When training, for each prediction timestamp, you must use only data available before that timestamp. If you naively join features, you'll leak future information.
Wrong (looks forward):
sqlSELECT o.order_id, AVG(o_hist.amount) as avg_order_amount -- includes FUTURE orders! FROM orders o JOIN orders o_hist ON o.user_id = o_hist.user_id GROUP BY o.order_id
Correct (point-in-time):
sqlSELECT o.order_id, AVG(o_hist.amount) as avg_order_amount FROM orders o JOIN orders o_hist ON o.user_id = o_hist.user_id AND o_hist.order_time < o.order_time -- only past orders! GROUP BY o.order_id
Worked Example 1: Point-in-Time Feature Computation
User 123's orders:
- Jan 1: $100
- Feb 1: $50
- Mar 1: $200 Training model to predict whether a user will order next month.
| Prediction Date | Avg Order (correct) | Avg Order (naive) |
|---|---|---|
| Before Jan 1 | NULL → impute 0 | NULL → impute 0 |
| After Jan 1, before Feb 1 | $100 (1 order) | $116.67 (3 orders) — WRONG (leaks future) |
| After Feb 1, before Mar 1 | $75 (2 orders) | $116.67 (3 orders) — WRONG |
| After Mar 1 | $116.67 (3 orders) | $116.67 (3 orders) — matches |
The naive method creates a systematic bias: for early predictions, it incorporates future information, artificially inflating feature values. The model learns that "high average order amount" predicts future orders — during training this is true (because of leakage), but at inference time it's not.
1.4 Feast: The Leading Open-Source Feature Store
Feast (Feature Store) is an open-source feature store by Tecton.
1.4.1 Key Concepts
| Concept | Description | Example |
|---|---|---|
| Feature Table | Group of features with same source & key | user_features |
| Feature | Individual attribute | avg_order_amount_7d |
| Entity | Key used to join features | user_id, order_id |
| Feature View | Logical grouping of features | user_transaction_features |
| DataSource | Raw data source | BigQuery table, Parquet file |
| Feature Service | Deployed feature group for serving | fraud_model_features |
1.4.2 Feast Workflow
python# 1. Define features (feature_view.py) from feast import FeatureView, Field from feast.types import Float32, Int64 from feast.value_type import ValueType user_stats = FeatureView( name="user_transaction_stats", entities=["user_id"], ttl=timedelta(days=7), schema=[ Field(name="avg_7d_amount", dtype=Float32), Field(name="num_7d_transactions", dtype=Int64), ], source=bigquery_source, ) # 2. Apply to registry # feast apply # 3. Get training data feature_service = FeatureService( name="fraud_detection_features", features=[user_stats] ) training_df = fs.get_historical_features( entity_df=entity_df, # user_ids + timestamps features=feature_service ).to_df() # 4. Serve online features feature_vector = fs.get_online_features( features=feature_service, entity_rows=[{"user_id": 123}] ).to_dict()
1.5 Feature Pipelines: Batch vs Streaming
1.5.1 Batch Features
Computed on schedule (daily/hourly). Used for most feature types:
python# Daily batch feature computation (Spark) def compute_user_features(spark, date): df = spark.table(f"orders") features = (df .filter(f"order_date < '{date}'") .groupBy("user_id") .agg( avg("amount").alias("avg_order_amount"), count("*").alias("num_orders"), avg("days_since_last_order").alias("avg_recency") ) ) features.write.mode("overwrite").save(f"features/user_features/dt={date}")
1.5.2 Streaming Features
Computed in real-time (sub-second). Used for time-critical features:
python# Streaming feature computation (Flink/Kafka) from kafka import KafkaConsumer import redis consumer = KafkaConsumer('orders', bootstrap_servers='kafka:9092') r = redis.Redis() for msg in consumer: order = json.loads(msg.value) user_id = order['user_id'] # Update running statistics r.hincrby(f"user:{user_id}", "txn_count", 1) r.hincrbyfloat(f"user:{user_id}", "amount_sum", order['amount'])
1.6 Feature Store Comparison
| Feature | Feast | Tecton | SageMaker Feature Store | Databricks Feature Store |
|---|---|---|---|---|
| Open source | Yes | No | No | No |
| Offline store | Any SQL DB | BigQuery, Snowflake | S3 + Athena | Delta Lake |
| Online store | Redis, DynamoDB | DynamoDB, Redis | DynamoDB | Aurora |
| Point-in-time | Yes | Yes | Yes | Yes |
| Streaming | Custom | Yes (Kafka) | No | No |
| Auto-feature engineering | No | Yes (Spark) | No | No |
| Cost | Free | Paid | By AWS resources | By Databricks |
1.7 Edge Cases & Gotchas
- TTL Management: Old feature values must be expired. Set appropriate TTLs to avoid stale features.
- Feature Crossing: Some features are only useful when combined (user_age × product_category). Pre-compute crosses.
- Null Handling: New entities have no feature values. Decide on imputation strategy (zeros, mean, median).
- Feature Drift: Monitor feature distributions over time. A sudden shift can degrade models silently.
- Backfilling: When adding new features, you need to compute historical values for all past training dates. This is computationally expensive.
1.8 Why This Matters
Feature stores are a critical part of production ML infrastructure. They're used by:
- Uber (Michelangelo): Features as a service for hundreds of models
- Netflix: Feature store for recommendation and personalization
- Airbnb: Feature engineering platform for fraud, pricing, search Without a feature store, ML teams spend ~60% of their time on feature engineering and face chronic training-serving skew. A feature store cuts this dramatically.
2. 📐 Key Formulas / Concepts
| Concept | Description | Benefit |
|---|---|---|
| Point-in-time join | Join features using data available at prediction time | Prevents label leakage |
| Offline store | Historical feature values for training | Scalable batch serving |
| Online store | Current feature values for inference | Low-latency serving |
| Feature registry | Centralized catalog of all features | Discoverability, reusability |
| TTL (Time-to-live) | How long feature values are valid | Prevents staleness |
3. ⚠️ Common Pitfalls
Pitfall 1: Not Handling Point-in-Time Correctness
Mistake: Joining features to training data without ensuring temporal ordering.
Why: This leaks future information into the training data. The model learns patterns that don't exist in production.
Correct approach: Use a feature store that supports point-in-time joins (Feast, Tecton) or implement the temporal join manually.
Pitfall 2: Using the Same Features for Training and Serving Without Validation
Mistake: Assuming training features and serving features are computed identically.
Why: Differences in feature computation (e.g., pandas vs SQL aggregation, different rounding, different null handling) create training-serving skew.
Correct approach: Define features once in the feature store and use the same definition for both training and serving.
Pitfall 3: Ignoring Feature TTL
Mistake: Using old feature values that no longer reflect the current state.
Why: A user's behavior changes over time. An avg_order_amount computed from 2-year-old data doesn't reflect current behavior.
Correct approach: Set appropriate TTLs for features. Monitor feature staleness and alert when features are served with old timestamps.
4. 📝 Practice Questions
Q1: A fraud model uses "num_transactions_last_24h" as a feature. How would you compute this for training (with timestamps from 2023) vs. serving (real-time)?For training (offline):sqlSELECT t.transaction_id, COUNT(past.transaction_id) as num_transactions_last_24h FROM transactions t LEFT JOIN transactions past ON t.user_id = past.user_id AND past.timestamp BETWEEN t.timestamp - INTERVAL 24 HOUR AND t.timestamp AND past.transaction_id != t.transaction_idFor serving (online):
- Use a Redis counter that increments on each transaction
- Key:
user:{user_id}:txn_24h- TTL: 24 hours (auto-expires)
- On prediction: retrieve the counter value
The feature store handles both paths from a single feature definition. Q2: Explain the difference between a feature store and a data warehouse. When would you use each?Data Warehouse: Stores raw data optimized for analytics queries. Contains all data at the most granular level. Queried by analysts and BI tools.Feature Store: Stores processed features optimized for ML. Contains pre-computed aggregations. Queried by models (online) and training jobs (offline).
| Aspect | Data Warehouse | Feature Store |
|---|---|---|
| Data | Raw, granular | Processed, aggregated |
| Serving | BI dashboards | ML model inference |
| Latency | Seconds to minutes | Milliseconds (online) |
| Point-in-time | Not built-in | Yes (core feature) |
| Use case | "What happened?" | "Predict what happens" |
Use a data warehouse as the source for feature computation; use a feature store for ML-serving. Q3: Your team has 50 features used across 10 models. Currently, each team computes features independently. Calculate the time savings from adopting a feature store (assuming 60% of ML time is feature engineering).Without feature store: 10 models × 100% feature engineering time = 10 person-units.With feature store:
- Create shared feature registry: 2 units (one-time setup)
- Maintain shared pipelines: 1 unit/cycle
- Each model just uses features: 0 units for feature engineering
Feature engineering collaboration: 50 features built once, shared 10 ways → 50/10 = 5 units saved.Total time savings: ~70% reduction in feature engineering time. This translates to about 42% overall ML project time savings (70% of 60%). Q4: A new feature "user_7d_avg_discount" needs backfilling for 2 years of training data. What approaches can speed this up?
Partitioned computation: Compute the feature day-by-day using incremental updates:pythonfor day in range(-730, 0): new_avg = (prev_avg * (count-1) + today_discount) / count Window functions in SQL: UseROWS BETWEEN 6 PRECEDING AND CURRENT ROWto compute rolling windows efficiently. Incremental materialization: If the computation is additive (sum, count), maintain running totals and update rather than recompute from scratch. Parallelism: Partition by entity (user_id hash) and compute in parallel. Dimensional modeling: Pre-compute daily aggregates, then roll up to weekly:sqlSELECT user_id, date, AVG(avg_daily_discount) OVER ( PARTITION BY user_id ORDER BY date ROWS 6 PRECEDING ) as avg_7d_discount FROM daily_discountsThe fastest approach depends on data volume. For < 1B rows, optimized SQL is sufficient. For > 1B rows, use Spark with incremental updates.
5. 🔗 Cross-References
- Previous: Data Versioning (Week 3) — Data pipeline foundations
- Next: Containers & Docker (Week 5) — Deploying feature pipelines
- Related: Experiment Tracking (Week 2) — Logging feature engineering experiments
- External: Feast documentation (feast.dev) — Practical implementation guide Join Discord PreviousData VersioningNextContainers & Docker