Skip to content

Configure tiered feature store

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

The tiered layout follows the Aerospike online store reference and the aerospike overrides example in the feast-dev/feast repository.

On this page, you build and register a local tiering demo that mirrors the production YAML routing structure.

Create the feature repository

Create the project directory, feature store configuration with namespace overrides, hot and cold feature view definitions, and synthetic data for both tiers.

  1. Create a project directory:

    Terminal window
    mkdir -p feast-tiered-demo/data
    cd feast-tiered-demo
  2. Create feature_store.yaml with a default namespace and a hot override:

    feature_store.yaml
    project: cost_tiered_features
    registry: data/registry.db
    provider: local
    online_store:
    type: aerospike
    hosts:
    - ["127.0.0.1", 3000]
    namespace: test
    namespace_overrides:
    scoring_velocity: test
    ttl_seconds: 2592000
    read_timeout_ms: 200
    write_timeout_ms: 500
    entity_key_serialization_version: 3

    registry: data/registry.db is Feast’s local metadata store for entities, feature views, and services. ttl_seconds: 2592000 (30 days) is the record-level Aerospike TTL stamped on every write. It retains the cold view for 30 days, but it also gives hot records the same storage TTL. Standard online reads do not apply FeatureView.ttl as a separate Aerospike expiry. read_timeout_ms and write_timeout_ms cap how long the Aerospike client waits before returning an error. provider: local, entity_key_serialization_version: 3, and the hosts list format are explained in Tutorial: Serve real-time Feast features with Aerospike on the Define and materialize page.

    Production clusters use separate namespaces, for example namespace: feast_ssd with scoring_velocity: feast_ram. The Aerospike CE Docker image ships only test, so this demo maps both tiers to test while preserving the override structure used in production.

  3. Create tiered_repo.py with hot and cold feature views plus two services:

    tiered_repo.py
    from datetime import timedelta
    from feast import Entity, FeatureService, FeatureView, Field, FileSource
    from feast.types import Float32, Float64, Int64
    from feast.value_type import ValueType
    user = Entity(
    name="user",
    join_keys=["user_id"],
    value_type=ValueType.STRING,
    )
    velocity_source = FileSource(
    name="scoring_velocity_source",
    path="data/scoring_velocity.parquet",
    timestamp_field="event_timestamp",
    )
    profile_source = FileSource(
    name="user_profile_source",
    path="data/user_profile.parquet",
    timestamp_field="event_timestamp",
    )
    scoring_velocity = FeatureView(
    name="scoring_velocity",
    entities=[user],
    ttl=timedelta(hours=1),
    schema=[
    Field(name="events_1h", dtype=Int64),
    Field(name="spend_1h", dtype=Float64),
    ],
    source=velocity_source,
    tags={"tier": "hot"}, # Descriptive only. Set namespace routing with namespace_overrides in feature_store.yaml.
    )
    user_profile_wide = FeatureView(
    name="user_profile_wide",
    entities=[user],
    ttl=timedelta(days=30),
    schema=[
    Field(name="lifetime_value", dtype=Float64),
    Field(name="affinity_score", dtype=Float32),
    ],
    source=profile_source,
    tags={"tier": "cold"}, # descriptive only
    )
    hot_scoring_service = FeatureService(
    name="hot_scoring_v1",
    features=[scoring_velocity],
    )
    full_scoring_service = FeatureService(
    name="full_scoring_v1",
    features=[scoring_velocity, user_profile_wide],
    )

    Both feature views share the same user entity. Feature views that resolve to the same namespace and set colocate on one Aerospike record per entity. Routing them to different namespaces stores each view on its own record. The namespace each view lands on is controlled by namespace_overrides in feature_store.yaml, not by the tags field. tags is metadata for your own tooling and has no effect on routing.

    scoring_velocity has a one-hour Feast TTL for materialization and historical retrieval. user_profile_wide uses 30 days. Those settings do not create separate online record TTLs. The FeatureService definitions decide which views a model requests. namespace_overrides independently decides where those views are stored.

  4. Create generate_data.py. Wrap generation code in if __name__ == "__main__": so feast apply does not run it when Feast imports project files.

    generate_data.py
    from datetime import timedelta
    import pandas as pd
    def main():
    user_ids = ["user-1", "user-2", "user-3"]
    event_timestamp = pd.Timestamp.now(tz="UTC").floor("s") - timedelta(minutes=10)
    pd.DataFrame({
    "user_id": user_ids,
    "event_timestamp": [event_timestamp] * len(user_ids),
    "events_1h": [12, 8, 20],
    "spend_1h": [240.0, 95.5, 410.0],
    }).to_parquet("data/scoring_velocity.parquet")
    pd.DataFrame({
    "user_id": user_ids,
    "event_timestamp": [event_timestamp] * len(user_ids),
    "lifetime_value": [1200.0, 800.0, 3500.0],
    "affinity_score": [0.82, 0.61, 0.94],
    }).to_parquet("data/user_profile.parquet")
    print("Wrote scoring_velocity and user_profile parquets")
    if __name__ == "__main__":
    main()

    Each DataFrame contains three synthetic users. The script sets event_timestamp ten minutes in the past, then writes one Parquet file for velocity counts and another for wide profile data. Parquet is the columnar format Feast uses for these local batch sources. In production, data warehouse tables or object storage paths replace the local files.

  5. Run it:

    Terminal window
    python3 generate_data.py
    Expected result
    Wrote scoring_velocity and user_profile parquets

Register definitions

feast apply reads the Python definitions in your project directory and records them in the Feast registry at data/registry.db. From that point, commands and SDK calls know your entity, feature views, and services by name. It registers definitions only. It does not copy any feature values into Aerospike, which is what materialization does on the next page.

  1. Register entities, feature views, and services:

    Terminal window
    feast apply
    Expected result
    No project found in the repository. Using project name cost_tiered_features defined in feature_store.yaml
    Applying changes for project cost_tiered_features
    Deploying infrastructure for scoring_velocity
    Deploying infrastructure for user_profile_wide
  2. Confirm registration:

    • Output includes Applying changes for project cost_tiered_features.
    • Output includes Deploying infrastructure for scoring_velocity and Deploying infrastructure for user_profile_wide.

On the next page, you materialize both tiers and read through each FeatureService.