Redis Feature Form concepts
Learn the workspace, resource graph, provider, secret, and serving model behind Redis Feature Form.
Redis Feature Form is a feature platform. It turns raw data from your existing systems into the values your models read at inference time. This page introduces the core concepts behind that workflow.
How the pieces fit together
A Feature Form deployment runs one or more workspaces. Each workspace owns a versioned resource graph that describes what features should exist, where their inputs live, and how they're served. You author that graph in a Python definitions file and apply it with ff apply. The ff command-line tool also handles workspaces, providers, RBAC, and serving operations.
The graph itself is data, not credentials or connections. Providers connect the workspace to external systems (Postgres, Redis, S3, Spark, an Iceberg catalog), and secret references point to the backend that holds the credentials. At the end of the chain, a feature view is the single resource the rest of your stack reads from to serve features online.
Workspaces
A workspace is a self-contained environment in Feature Form. Each one owns its own resource graph, providers, secret references, and serving metadata. Nothing is shared between workspaces.
Use workspaces to keep environments such as dev, staging, and prod separate, or to give independent teams their own area on a shared deployment. Conceptually, a workspace is a single tenant in a multi-tenant Feature Form deployment — providers, secret references, RBAC bindings, and graph state are all scoped per workspace, even though they share the underlying server. Two workspaces can connect to the same external Postgres database and remain fully isolated, because each workspace tracks its own resources.
Every workspace also has a last_applied_version counter that increases each time you successfully apply a change. Read commands always return the latest committed version.
To create, inspect, update, or delete workspaces, see Manage workspaces.
The resource graph
The resource graph describes what a workspace should look like. Every feature, label, transformation, dataset, and feature view lives in this graph and refers to the others by name.
Two properties shape how you work with it:
- It is versioned as a whole. Each successful change creates a new version of the entire graph. Either every resource in the change lands together, or nothing does — you never end up with half-applied feature definitions.
- It is declarative. You describe what the graph should look like, not the steps to get there. Feature Form figures out the difference between the current graph and the new one and applies only what changed.
When ff apply accepts a change, Feature Form's planner converts the desired graph into a task DAG that runs against your data infrastructure — materializing new datasets, executing transformations, and populating the online store. The graph is the logical specification; the task DAG is the executed plan.
Resource types
A graph is built from seven resource types. New users encountering Feature Form for the first time benefit from learning these as a vocabulary list — every other concept on this page builds on them.
- Entities identify the real-world objects features describe, such as a
customerororder. Other resources join on the entity's primary key — the actual column name can vary across tables (customer_id,user_id,id), as long as the values identify the same business entity. - Datasets point at an existing table, view, or file in an offline store and make it visible to the graph. The data itself stays where it lives; Feature Form just registers a handle to it.
- Transformations produce new datasets from existing ones, expressed as SQL or as a Spark job. A transformation describes the shape of the output; the compute that runs it is supplied by a provider.
- Features are named, measurable characteristics of an entity that a model reads as input at inference time. A feature attaches to a column of a dataset (or transformation), optionally applies an aggregation (such as
SUMover a 7-day window), and declares which provider owns its computation. - Labels are the target value a model is trained to predict — the known outcome attached to each training example. They share the entity-keyed shape of features but feed offline training rather than online serving.
- Training sets join one or more features with a label by entity key and time, so an offline training job reads a single point-in-time-correct table instead of stitching inputs together by hand. The point-in-time alignment prevents data leakage — features whose values weren't actually known at the label's timestamp.
- Feature views group related features for an entity behind a shared definition, materialization policy, and serving contract. They're the primary interface model-serving systems use to read feature values at inference time, and the only graph resource downstream applications interact with directly.
The following example definitions file shows how the vocabulary above appears as code.
import featureform as ff
from datetime import timedelta
customer = ff.Entity(name="customer") # Entity: the real-world object features describe
transactions = postgres.dataset( # Dataset: registers an existing table with the graph
name="transactions_raw",
table="transactions",
timestamp_column="timestamp",
)
# Transformation: derives a new dataset from existing ones via SQL
@postgres.sql_transformation(name="customer_daily_rollups", inputs=[transactions])
def customer_daily_rollups() -> str:
return """
SELECT customer_id,
date_trunc('day', timestamp) AS event_day,
SUM(transaction_amount) AS total_amount
FROM {{transactions_raw}}
GROUP BY 1, 2
"""
# Feature: an aggregated value served to models at inference time
customer_total_amount_7d = (
ff.Feature(name="customer_total_amount_7d")
.from_dataset(customer_daily_rollups, entity="customer",
entity_column="customer_id", value="total_amount",
timestamp="event_day")
.aggregate(function=ff.AggregateFunction.SUM, window=timedelta(days=7))
)
customer_risk_view = ff.FeatureView( # Feature view: groups features behind a serving contract
name="customer_risk_feature_view",
entity="customer",
features=[customer_total_amount_7d],
inference_store="demo_redis",
)
Definitions files and ff apply
The Python definitions file is the source of truth for what the graph should look like. The file uses Python to declare resources, not to run commands against Feature Form. When you run ff apply, Feature Form imports the file, collects those resources, and treats them as the workspace's desired state. Feature Form compares that set with the current graph and, if the change is accepted, commits a new graph version.
By default, ff apply replaces the workspace's current graph with the resources defined in the file. Any existing resource not in the file becomes a candidate for removal. To apply a partial set and leave missing resources untouched, run ff apply --merge instead.
For an end-to-end walkthrough of authoring a definitions file and applying it, see the Quickstart. For the full apply lifecycle and editing loop, see Define and deploy features and Update features.
Secrets and secret references
Feature Form never stores credentials in any form — not plaintext, not hashed, not encrypted. A provider configuration carries a secret reference that points at a separately-registered secret backend, and Feature Form resolves the reference at the moment the credential is needed. Because of this, secret providers are registered first — every provider that follows points back at one through its secret reference.
Keeping credentials out of the graph has two important consequences:
- The graph is safe to inspect and export. Nothing in it contains a usable credential. You can hand the graph to another team, version it, or paste it into a ticket without leaking secrets.
- The process that resolves a reference is whichever process actually needs the credential. In a deployed environment, that's almost always the Feature Form server, not your local CLI shell. A reference such as
env:PG_PASSWORDreads from the server's process environment, not yours.
Every new workspace is created with a built-in env secret provider, which makes env: references work out of the box for local development. Production deployments typically register a Vault, Kubernetes-secrets, or AWS Secrets Manager backend instead, because the env backend offers no rotation, no audit, and exposes values in process listings.
To register a secret provider for a workspace, see Configure secret providers.
Providers
A provider is the workspace's connection to an external system. It carries the host, port, credentials reference, and any configuration Feature Form needs to talk to that system. Resources in the graph reference providers by name, so you must register a provider in the workspace before applying any resource that uses it.
Every provider fills one or more roles, which describe the kind of work it can do for the workspace:
| Role | What it does |
|---|---|
offline-store |
Holds batch data and materialized datasets the graph reads from. |
online-store |
Serves materialized feature values to applications at low latency. |
compute |
Runs transformations and materialization jobs. |
streaming |
Connects the workspace to streaming sources. |
One provider often fills more than one role. Postgres, for example, is commonly registered as both offline-store and compute because the same instance that holds datasets can run SQL transformations against them. The documented integrations and their typical roles:
| Provider | Typical roles |
|---|---|
| Postgres | offline-store, compute |
| Redis | online-store |
| S3 | offline-store |
| Spark | compute |
| Iceberg catalog | offline-store |
The role model is what lets a graph stay portable: a feature definition doesn't care that compute happens to be Postgres in dev and Spark in prod, only that some provider fills the compute role.
To register providers in a workspace, see Register providers.
Feature views and serving
A feature view is the resource that everything else in the graph eventually feeds. Applications query it to get the latest features for an entity.
A feature view declares:
- The entity used as the lookup key (for example,
customer). - The list of features it exposes.
- The online provider that holds the materialized values — typically Redis.
- A materialization engine that produces those values from offline data.
For example:
customer_risk_view = ff.FeatureView(
name="customer_risk_feature_view",
entity="customer",
features=[customer_total_amount_7d, customer_transaction_count_7d],
inference_store="demo_redis",
)
Feature view requirements
Before applications can read from a feature view:
- The online provider it points to must be registered and reachable.
- The graph version that introduced the feature view must be committed.
- Materialization must have populated values for the entities you want to query.
If any of those are missing, the read fails immediately rather than returning stale data.
Serving interfaces
Applications can read feature values through any of three interfaces:
- A gRPC service (
ServingService.ServeandServingService.GetServingMetadata). - A REST endpoint (
POST /api/v1/serve). - A Python client (
client.serve(...)).
To serve from a feature view in an application, see Serve features. To inspect datasets, training sets, or feature views directly, see Query data.
Next steps
- Quickstart — one end-to-end walkthrough that exercises every concept on this page.
- Manage workspaces — create, inspect, update, and delete workspaces.
- Register providers — connect the workspace to Postgres, Redis, S3, Spark, or an Iceberg catalog, and register secret backends.
- Define and deploy features — author a definitions file and run
ff apply. - Update features — iterate on a graph after the first apply.
- Serve features — read from a feature view in an application.
- Query data — inspect datasets, training sets, and feature views directly.