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.
-
Create
read_features.py:read_features.py from feast import FeatureStorestore = 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 fromfeature_store.yamlin the current directory. The registry is Feast’s metadata store for entity, feature view, and service definitions.Each string in
featuresuses 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 oneonline_readper view. Both views share the same entity record key, so each read targets the same physical record in Aerospike.entity_rowsis 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. -
Run it:
Terminal window python3 read_features.pyExpected 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_statsandpricing. -
Confirm the multi-view lookup:
ratingandtrips_last_7dare present (fromdriver_stats).surge_multiplieris present (frompricing).driver_idis[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.
-
Create
inspect_records.pyto read every record in the Feast set directly from Aerospike:The Aerospike Python client’s
scan.foreachcallback 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 aerospikeclient = aerospike.client({"hosts": [("127.0.0.1", 3000)]}).connect()records = []def collect(record_tuple):key, _, bins = record_tuple # key, metadata, binsdigest = 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 vfor k, v in bins["features"][view_name].items()}print(f" {view_name}: {values}") -
Run it:
Terminal window python3 inspect_records.pyExpected result records in set: 3record digest: RECORD_DIGEST_1feature 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_2feature 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_3feature views on this record: ['driver_stats', 'pricing']driver_stats: {'rating': RATING_3, 'trips_last_7d': TRIPS_3}pricing: {'surge_multiplier': SURGE_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
featuresbin.records in setis3.- Every record lists both
driver_statsandpricingunderfeature 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
-
Create
update_driver_stats.pywith newdriver_statsvalues and a later timestamp:update_driver_stats.py import pandas as pddriver_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_timestampis set to the current time, not ten minutes in the past like ingenerate_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_statscolumns. This Parquet file is the offline source fordriver_statsonly. Thepricingfeature view has its own source file and is unaffected by this update. -
Run it:
Terminal window python3 update_driver_stats.pyExpected result Updated data/driver_stats.parquet -
Materialize
driver_statsonly:Run incremental materialization again, but limit it to
driver_statswith--views. Feast re-reads the updated Parquet file and partial-upserts only thedriver_statsmap key on each driver record.pricingis not part of this run.Terminal window feast materialize-incremental $(date -u +"%Y-%m-%dT%H:%M:%S") --views driver_statsExpected 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
DeprecationWarningaboutmeta["ttl"]can appear and does not block the write. -
Run
inspect_records.pyagain:Terminal window python3 inspect_records.pyExpected result records in set: 3record digest: RECORD_DIGEST_1feature 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_2feature 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_3feature 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_statsvalues change while thepricingvalues match exactly. -
Confirm the targeted update:
- Each digest is present in both runs.
driver_statsvalues changed to match the update.pricingvalues 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.