Skip to content

Verify collocation and partial upserts

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

With driver_stats and pricing materialized into Aerospike, you now verify the shared-record layout in three steps: read both feature views through Feast, inspect the underlying Aerospike record, and materialize only driver_stats to observe a targeted Map CDT update.

Read both feature views in one call

Call get_online_features with features from both driver_stats and pricing and confirm that a single response contains all three values.

  1. Create read_features.py:

    read_features.py
    from feast import FeatureStore
    store = FeatureStore(repo_path=".")
    features = store.get_online_features(
    features=[
    "driver_stats:rating",
    "driver_stats:trips_last_7d",
    "pricing:surge_multiplier",
    ],
    entity_rows=[{"driver_id": 1001}],
    ).to_dict()
    print(features)

    FeatureStore(repo_path=".") loads the registry location from feature_store.yaml in the current directory. The registry is Feast’s metadata store for entity, feature view, and service definitions.

    Each string in features uses the "feature_view_name:feature_name" format. This lets you mix features from different views in a single call. Feast resolves each reference to the correct feature view and issues one online_read per view. Both views share the same entity record key, so each read targets the same physical record in Aerospike.

    entity_rows is a list of dicts, one per entity lookup. Each dict must contain the entity’s join key column and value. Feast returns one result row per entry.

  2. Run it:

    Terminal window
    python3 read_features.py
    Expected result
    {'driver_id': [1001], 'rating': [4.91], 'trips_last_7d': [132], 'surge_multiplier': [1.2]}

    Note that a single record includes values from both driver_stats and pricing.

  3. Confirm the multi-view lookup:

    • rating and trips_last_7d are present (from driver_stats).
    • surge_multiplier is present (from pricing).
    • driver_id is [1001].

Inspect the Aerospike record directly

Use the Aerospike Python client to scan the Feast set and confirm that both feature views are stored together on one record per driver.

  1. Create inspect_records.py to read every record in the Feast set directly from Aerospike:

    The Aerospike Python client’s scan.foreach callback receives a three-element tuple: (key, metadata, bins). The key tuple includes a record digest, which is a stable identifier even when scan order changes. The script sorts records by that digest.

    inspect_records.py
    import aerospike
    client = aerospike.client({"hosts": [("127.0.0.1", 3000)]}).connect()
    records = []
    def collect(record_tuple):
    key, _, bins = record_tuple # key, metadata, bins
    digest = bytes(key[3]).hex()
    records.append((digest, bins))
    scan = client.scan("test", "driver_features_latest")
    scan.foreach(collect)
    client.close()
    print(f"records in set: {len(records)}")
    for digest, bins in sorted(records):
    print(f"record digest: {digest}")
    views = sorted(bins["features"].keys())
    print(f"feature views on this record: {views}")
    for view_name in views:
    values = {
    k: round(v, 4) if isinstance(v, float) else v
    for k, v in bins["features"][view_name].items()
    }
    print(f" {view_name}: {values}")
  2. Run it:

    Terminal window
    python3 inspect_records.py
    Expected result
    records in set: 3
    record digest: RECORD_DIGEST_1
    feature views on this record: ['driver_stats', 'pricing']
    driver_stats: {'rating': RATING_1, 'trips_last_7d': TRIPS_1}
    pricing: {'surge_multiplier': SURGE_1}
    record digest: RECORD_DIGEST_2
    feature views on this record: ['driver_stats', 'pricing']
    driver_stats: {'rating': RATING_2, 'trips_last_7d': TRIPS_2}
    pricing: {'surge_multiplier': SURGE_2}
    record digest: RECORD_DIGEST_3
    feature views on this record: ['driver_stats', 'pricing']
    driver_stats: {'rating': RATING_3, 'trips_last_7d': TRIPS_3}
    pricing: {'surge_multiplier': SURGE_3}
  3. Confirm both feature views share each record:

    Three drivers produced three Aerospike records, one per driver. Each record holds both feature views inside its features bin.

    • records in set is 3.
    • Every record lists both driver_stats and pricing under feature views on this record.
    • Each record has a unique digest. Your digest values differ from the placeholders in this example.

Observe a targeted Map CDT update

  1. Create update_driver_stats.py with new driver_stats values and a later timestamp:

    update_driver_stats.py
    import pandas as pd
    driver_ids = [1001, 1002, 1003]
    event_timestamp = pd.Timestamp.now(tz="UTC").floor("s")
    pd.DataFrame({
    "driver_id": driver_ids,
    "event_timestamp": [event_timestamp] * len(driver_ids),
    "rating": [4.95, 4.80, 4.90],
    "trips_last_7d": [140, 101, 215],
    }).to_parquet("data/driver_stats.parquet")
    print("Updated data/driver_stats.parquet")

    event_timestamp is set to the current time, not ten minutes in the past like in generate_data.py. Feast uses the timestamp to decide which rows are newer than the previous materialization run. A timestamp in the past could fall outside the incremental window and be skipped. Using the current time guarantees the rows are picked up.

    The DataFrame contains only driver_stats columns. This Parquet file is the offline source for driver_stats only. The pricing feature view has its own source file and is unaffected by this update.

  2. Run it:

    Terminal window
    python3 update_driver_stats.py
    Expected result
    Updated data/driver_stats.parquet
  3. Materialize driver_stats only:

    Run incremental materialization again, but limit it to driver_stats with --views. Feast re-reads the updated Parquet file and partial-upserts only the driver_stats map key on each driver record. pricing is not part of this run.

    Terminal window
    feast materialize-incremental $(date -u +"%Y-%m-%dT%H:%M:%S") --views driver_stats
    Expected result
    Materializing 1 feature views to 2026-07-14 21:58:00+00:00 into the aerospike online store.
    driver_stats from 2026-07-14 21:54:28+00:00 to 2026-07-14 21:58:00+00:00:

    A DeprecationWarning about meta["ttl"] can appear and does not block the write.

  4. Run inspect_records.py again:

    Terminal window
    python3 inspect_records.py
    Expected result
    records in set: 3
    record digest: RECORD_DIGEST_1
    feature views on this record: ['driver_stats', 'pricing']
    driver_stats: {'rating': UPDATED_RATING_1, 'trips_last_7d': UPDATED_TRIPS_1}
    pricing: {'surge_multiplier': SURGE_1}
    record digest: RECORD_DIGEST_2
    feature views on this record: ['driver_stats', 'pricing']
    driver_stats: {'rating': UPDATED_RATING_2, 'trips_last_7d': UPDATED_TRIPS_2}
    pricing: {'surge_multiplier': SURGE_2}
    record digest: RECORD_DIGEST_3
    feature views on this record: ['driver_stats', 'pricing']
    driver_stats: {'rating': UPDATED_RATING_3, 'trips_last_7d': UPDATED_TRIPS_3}
    pricing: {'surge_multiplier': SURGE_3}

    Match records between the two runs by digest. For each digest, the driver_stats values change while the pricing values match exactly.

  5. Confirm the targeted update:

    • Each digest is present in both runs.
    • driver_stats values changed to match the update.
    • pricing values for each digest are identical to the first inspect run.

You read both feature views through Feast, confirmed they share one Aerospike record per driver, and observed that materializing driver_stats only updated the driver_stats map entries inside each record’s features bin. That targeted write is how the integration keeps independently updated feature views on a shared record.