Skip to main content

Module 7 — Feast in practice

Enough principle. This module walks the whole loop on the fraud scoring thread — repository, apply, historical retrieval, materialization, online lookup, feature server — using Feast on Parquet and Redis. Everything runs on a laptop.

Setting up the repository

A Feast project is a directory called a feature repository. Two files matter: feature_store.yaml for the store configuration, and one or more .py files declaring entities, sources and feature views.

fraud_repo/
feature_store.yaml
features.py
data/
transactions.parquet

feature_store.yaml names the offline and online providers:

project: fraud
provider: local
registry: data/registry.db
online_store:
type: redis
connection_string: "localhost:6379,db=0"
offline_store:
type: file
entity_key_serialization_version: 2

features.py declares the fraud entity, the transaction source, and the two feature views we will use:

from datetime import timedelta
from feast import Entity, FeatureView, Field, FileSource
from feast.types import Int64, Float64

card = Entity(name="card_id", join_keys=["card_id"])

tx_source = FileSource(
name="tx_source",
path="data/transactions.parquet",
timestamp_field="event_ts",
created_timestamp_column="ingested_at",
)

card_activity_1h = FeatureView(
name="card_activity_1h",
entities=[card],
ttl=timedelta(hours=2),
schema=[
Field(name="n_tx_last_1h", dtype=Int64),
Field(name="sum_amount_last_1h", dtype=Float64),
],
source=tx_source,
owner="team-fraud@example.com",
tags={"domain": "fraud", "freshness": "15min"},
)

card_activity_7d = FeatureView(
name="card_activity_7d",
entities=[card],
ttl=timedelta(days=1),
schema=[
Field(name="n_tx_last_7d", dtype=Int64),
Field(name="avg_amount_last_7d", dtype=Float64),
],
source=tx_source,
owner="team-fraud@example.com",
tags={"domain": "fraud", "freshness": "1h"},
)

Applying the definitions

feast apply reconciles the Python declarations with the registry stored in data/registry.db. It creates the entities, feature views and, in a real deployment, provisions online store keys.

cd fraud_repo/
feast apply

Output lists what was added, changed and removed. Nothing has been materialized yet; only the contract has been recorded. Re-running feast apply after editing features.py is safe — it is idempotent and produces a diff, not a rewrite.

Building a training set: get_historical_features

For training we start from a labelled event dataframe — the same one from module 5 — and ask Feast to attach the features as of each event's timestamp.

from feast import FeatureStore
import pandas as pd

fs = FeatureStore(repo_path=".")

events = pd.DataFrame({
"card_id": ["c-42", "c-42", "c-19"],
"event_timestamp": pd.to_datetime([
"2026-05-14 13:47:03",
"2026-05-14 15:12:41",
"2026-05-14 08:33:12",
], utc=True),
"is_fraud": [0, 1, 0],
})

training_df = fs.get_historical_features(
entity_df=events,
features=[
"card_activity_1h:n_tx_last_1h",
"card_activity_1h:sum_amount_last_1h",
"card_activity_7d:n_tx_last_7d",
"card_activity_7d:avg_amount_last_7d",
],
).to_df()

Under the hood Feast runs the point-in-time correct join of module 5 on the Parquet source, respecting the two timestamps and each view's TTL. The returned dataframe has one row per event, one column per feature, and — critically — no leak from the future.

Pushing values to the online store: materialize

Training is done. To serve, we push the latest values to Redis.

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

Incremental means Feast reads the last materialization timestamp from the registry, computes new values from that timestamp to now, and writes them to Redis. Under Redis the keys look like fraud:card_activity_1h:c-42, storing the latest event timestamp and the feature values.

For a full backfill on a fresh Redis: feast materialize <start> <end>, with wall-clock timestamps.

Serving: get_online_features

At scoring time, the model service reads from Redis in a few milliseconds:

online = fs.get_online_features(
features=[
"card_activity_1h:n_tx_last_1h",
"card_activity_1h:sum_amount_last_1h",
"card_activity_7d:n_tx_last_7d",
"card_activity_7d:avg_amount_last_7d",
],
entity_rows=[{"card_id": "c-42"}],
).to_dict()

score = model.predict_proba([[
online["n_tx_last_1h"][0],
online["sum_amount_last_1h"][0],
online["n_tx_last_7d"][0],
online["avg_amount_last_7d"][0],
]])[0, 1]

The values Redis returns come from the same definition that produced the training set. That is the training-serving skew of module 1 — solved.

The feature server

For services in a language other than Python, Feast exposes an HTTP feature server:

feast serve --host 0.0.0.0 --port 6566

A Java, Go or Node service can then POST to /get-online-features with a JSON body listing the features and entity rows, and receive values in the same schema. Under the hood the server calls Redis directly; it exists to centralize authentication, rate limiting and logging.

The limits of Feast, honestly

Feast is deliberately small. It is the right choice when your team already runs its own infrastructure and wants an open-source layer that stays out of the way. It is the wrong choice when you expect the store to also compute features for you.

Feast does not run a transformation engine. The values you push to the offline source are the values Feast serves. Rolling windows, aggregations, joins between sources — you write them in Spark, Flink or dbt and Feast consumes the result. Tecton, Hopsworks and Databricks Feature Store take the opposite tack and manage the transformations too; that is a different bet, with a different cost profile.

On-demand transformations are limited. Feast supports on-demand feature views (transformations computed at request time from other features), but they are single-row Python only. Anything heavier belongs upstream.

Governance is minimal. Tags are string metadata; there is no built-in access control on individual features. If your regulatory environment requires per-feature access control, budget for it above Feast rather than expecting it inside.

Read feast plan before every feast apply

feast plan shows what apply would change without changing anything. It is the equivalent of terraform plan: cheap, always safe, and it catches the "I edited the wrong file" mistake before it reaches the registry.

Summary

  • A Feast project is a directory: feature_store.yaml for providers, .py files declaring entities, sources and feature views; feast apply reconciles them into the registry.
  • get_historical_features returns a training dataframe with a point-in-time correct join; materialize pushes the latest values to Redis; get_online_features serves them in milliseconds.
  • The feature server exposes the online path over HTTP for non-Python services; the same values are served regardless of the client's language.
  • Feast is a serving layer, not a transformation engine; if you expect the store to compute rolling windows for you, Tecton, Hopsworks and Databricks Feature Store are the closer fit.

Next module: sharing features between teams — the payoff of the store when the fraud and marketing teams both want the same aggregate.