@@ -448,6 +448,9 @@ def _convert_raw_docs_to_proto(
448448 results .append ((ts , row_features ))
449449 return results
450450
451+ # ------------------------------------------------------------------
452+ # Admin paths (update / teardown)
453+ # ------------------------------------------------------------------
451454 def update (
452455 self ,
453456 config : RepoConfig ,
@@ -457,14 +460,70 @@ def update(
457460 entities_to_keep : Sequence [Entity ],
458461 partial : bool ,
459462 ) -> None :
460- raise NotImplementedError ("AerospikeOnlineStore.update is not implemented yet." )
463+ """Reconcile per-feature-view data when a schema change is applied.
464+
465+ Aerospike has no explicit schema, and records/sets are created lazily
466+ on first write, so there is nothing to do for ``tables_to_keep`` or
467+ either of the entity lists. For ``tables_to_delete`` we strip each
468+ feature-view's slot out of the ``features`` and ``event_ts`` Map
469+ CDTs on every record in the project's set.
470+
471+ This is issued as a single **background scan** with a combined op
472+ list covering all feature views being removed, so the cost is a
473+ single server-side pass regardless of how many feature views are
474+ dropped. The scan runs asynchronously server-side and returns
475+ immediately; this matches the intent of ``feast apply``, after
476+ which the caller stops reading the dropped feature views anyway.
477+ """
478+ if not isinstance (config .online_store , AerospikeOnlineStoreConfig ):
479+ raise RuntimeError (f"{ config .online_store .type = } . It must be aerospike." )
480+ if not tables_to_delete :
481+ return
482+
483+ client = self ._get_client (config )
484+ ns = config .online_store .namespace
485+ set_name = self ._set_name (config )
486+
487+ remove_ops : List [Dict [str , Any ]] = []
488+ for fv in tables_to_delete :
489+ remove_ops .append (
490+ map_ops .map_remove_by_key (
491+ "features" , fv .name , aerospike .MAP_RETURN_NONE
492+ )
493+ )
494+ remove_ops .append (
495+ map_ops .map_remove_by_key (
496+ "event_ts" , fv .name , aerospike .MAP_RETURN_NONE
497+ )
498+ )
499+
500+ scan = client .scan (ns , set_name )
501+ scan .add_ops (remove_ops )
502+ scan .execute_background ()
461503
462504 def teardown (
463505 self ,
464506 config : RepoConfig ,
465507 tables : Sequence [FeatureView ],
466508 entities : Sequence [Entity ],
467509 ) -> None :
468- raise NotImplementedError (
469- "AerospikeOnlineStore.teardown is not implemented yet."
470- )
510+ """Truncate the project's set and close the cached client.
511+
512+ Uses Aerospike's ``truncate(namespace, set, 0)`` — a set-scoped
513+ metadata operation that clears every record in O(1) client time,
514+ cheaper than Mongo's ``collection.drop()``. Passing ``0`` as the
515+ cutoff means "drop everything regardless of last-update time".
516+
517+ Truncate on a non-existent set is a no-op, so calling ``teardown``
518+ on a project that never wrote data is safe.
519+ """
520+ if not isinstance (config .online_store , AerospikeOnlineStoreConfig ):
521+ raise RuntimeError (f"{ config .online_store .type = } . It must be aerospike." )
522+
523+ client = self ._get_client (config )
524+ ns = config .online_store .namespace
525+ set_name = self ._set_name (config )
526+ client .truncate (ns , set_name , 0 )
527+ if self ._client is not None :
528+ self ._client .close ()
529+ self ._client = None
0 commit comments