Skip to content

Commit cfc038b

Browse files
feat: Enable feature service serving for versioned feature views
When enable_online_feature_view_versioning is on and a FeatureService references a versioned FV, set version_tag on the projection so that online reads resolve to the correct versioned table. Previously the FeatureService path never set version_tag, causing reads from the wrong (unversioned) online store table. Changes: - _get_features(): version-qualify feature refs for FeatureService projections - _get_feature_views_to_use(): capture current_version_number before with_projection() discards it, then set version_tag on the projection - feature_store.py: fix mypy type narrowing for the gate check - Add integration tests for both code paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 66c280b commit cfc038b

File tree

3 files changed

+113
-11
lines changed

3 files changed

+113
-11
lines changed

sdk/python/feast/feature_store.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,21 +1162,23 @@ def apply(
11621162
}
11631163
for feature_service in services_to_update:
11641164
for projection in feature_service.feature_view_projections:
1165-
fv = fvs_in_batch.get(projection.name)
1166-
if fv is None:
1165+
ref_fv: Optional[BaseFeatureView] = fvs_in_batch.get(
1166+
projection.name
1167+
)
1168+
if ref_fv is None:
11671169
try:
1168-
fv = self.registry.get_any_feature_view(
1170+
ref_fv = self.registry.get_any_feature_view(
11691171
projection.name, self.project
11701172
)
11711173
except FeatureViewNotFoundException:
11721174
continue
1173-
if (
1174-
getattr(fv, "current_version_number", None) is not None
1175-
and fv.current_version_number > 0
1176-
):
1175+
cur_ver: Optional[int] = getattr(
1176+
ref_fv, "current_version_number", None
1177+
)
1178+
if cur_ver is not None and cur_ver > 0:
11771179
raise ValueError(
11781180
f"Feature service '{feature_service.name}' references feature view "
1179-
f"'{projection.name}' which is at version v{fv.current_version_number}. "
1181+
f"'{projection.name}' which is at version v{cur_ver}. "
11801182
f"To use versioned feature views in feature services, set "
11811183
f"'enable_online_feature_view_versioning: true' under 'registry' "
11821184
f"in feature_store.yaml."

sdk/python/feast/utils.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,17 @@ def _get_features(
12371237

12381238
# Build feature reference list
12391239
for projection in feature_service_from_registry.feature_view_projections:
1240+
if getattr(registry, "enable_online_versioning", False):
1241+
try:
1242+
fv = registry.get_any_feature_view(
1243+
projection.name, project, allow_cache
1244+
)
1245+
ver = getattr(fv, "current_version_number", None)
1246+
if ver is not None and ver > 0:
1247+
projection = copy.copy(projection)
1248+
projection.version_tag = ver
1249+
except Exception:
1250+
pass
12401251
_feature_refs.extend(
12411252
[f"{projection.name_to_use()}:{f.name}" for f in projection.features]
12421253
)
@@ -1323,18 +1334,27 @@ def _get_feature_views_to_use(
13231334
else:
13241335
fv = registry.get_any_feature_view(name, project, allow_cache)
13251336
# Gate: feature services must not resolve to versioned FVs when online versioning is off
1337+
cur_ver: Optional[int] = getattr(fv, "current_version_number", None)
13261338
if (
13271339
isinstance(features, FeatureService)
1328-
and getattr(fv, "current_version_number", None) is not None
1329-
and fv.current_version_number > 0
1340+
and cur_ver is not None
1341+
and cur_ver > 0
13301342
and not getattr(registry, "enable_online_versioning", False)
13311343
):
13321344
raise ValueError(
13331345
f"Feature service references feature view '{name}' which is at version "
1334-
f"v{fv.current_version_number}, but online versioning is disabled. "
1346+
f"v{cur_ver}, but online versioning is disabled. "
13351347
f"Set 'enable_online_feature_view_versioning: true' under 'registry' "
13361348
f"in feature_store.yaml."
13371349
)
1350+
# For FeatureService refs: resolve the active version when online versioning is on
1351+
if (
1352+
isinstance(features, FeatureService)
1353+
and cur_ver is not None
1354+
and cur_ver > 0
1355+
and getattr(registry, "enable_online_versioning", False)
1356+
):
1357+
version_num = cur_ver
13381358

13391359
if isinstance(fv, OnDemandFeatureView):
13401360
od_fvs_to_use.append(

sdk/python/tests/integration/registration/test_versioning.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,3 +1084,83 @@ def test_feature_service_with_unversioned_fv_succeeds(
10841084
features=[fv],
10851085
)
10861086
store.apply([entity, fv, fs]) # Should not raise
1087+
1088+
def test_feature_service_serves_versioned_fv_when_flag_on(
1089+
self, versioned_fv_and_entity
1090+
):
1091+
"""With online versioning on, FeatureService projections carry the correct version_tag."""
1092+
from feast.utils import _get_feature_views_to_use
1093+
1094+
entity, fv_v0, fv_v1 = versioned_fv_and_entity
1095+
1096+
with tempfile.TemporaryDirectory() as tmpdir:
1097+
store = self._make_store(tmpdir, enable_versioning=True)
1098+
1099+
# Apply v0 then v1 to create version history
1100+
store.apply([entity, fv_v0])
1101+
store.apply([entity, fv_v1])
1102+
1103+
# Create and apply a feature service referencing the versioned FV
1104+
fs = FeatureService(
1105+
name="driver_service",
1106+
features=[fv_v1],
1107+
)
1108+
store.apply([fs])
1109+
1110+
# Retrieve the registered feature service
1111+
registered_fs = store.registry.get_feature_service(
1112+
"driver_service", "test_project"
1113+
)
1114+
1115+
fvs, _ = _get_feature_views_to_use(
1116+
registry=store.registry,
1117+
project="test_project",
1118+
features=registered_fs,
1119+
allow_cache=False,
1120+
hide_dummy_entity=False,
1121+
)
1122+
1123+
assert len(fvs) == 1
1124+
assert fvs[0].projection.version_tag == 1
1125+
assert fvs[0].projection.name_to_use() == "driver_stats@v1"
1126+
1127+
def test_feature_service_feature_refs_include_version_when_flag_on(
1128+
self, versioned_fv_and_entity
1129+
):
1130+
"""With online versioning on, _get_features() produces version-qualified refs."""
1131+
from feast.utils import _get_features
1132+
1133+
entity, fv_v0, fv_v1 = versioned_fv_and_entity
1134+
1135+
with tempfile.TemporaryDirectory() as tmpdir:
1136+
store = self._make_store(tmpdir, enable_versioning=True)
1137+
1138+
# Apply v0 then v1 to create version history
1139+
store.apply([entity, fv_v0])
1140+
store.apply([entity, fv_v1])
1141+
1142+
# Create and apply a feature service referencing the versioned FV
1143+
fs = FeatureService(
1144+
name="driver_service",
1145+
features=[fv_v1],
1146+
)
1147+
store.apply([fs])
1148+
1149+
# Retrieve the registered feature service
1150+
registered_fs = store.registry.get_feature_service(
1151+
"driver_service", "test_project"
1152+
)
1153+
1154+
refs = _get_features(
1155+
registry=store.registry,
1156+
project="test_project",
1157+
features=registered_fs,
1158+
allow_cache=False,
1159+
)
1160+
1161+
# All refs should be version-qualified
1162+
for ref in refs:
1163+
assert "@v1:" in ref, f"Expected version-qualified ref, got: {ref}"
1164+
1165+
# Check specific ref format
1166+
assert "driver_stats@v1:trips_today" in refs

0 commit comments

Comments
 (0)