Skip to content

Commit a008155

Browse files
fix: Allow serving features while a feature view is MATERIALIZING
The lifecycle serving gate added in v0.64.0 rejects any feature view not in AVAILABLE_ONLINE / STATE_UNSPECIFIED state. Because materialization transitions a feature view to MATERIALIZING in the shared registry, routine incremental materialization interrupts concurrent feature servers with "cannot serve features. Only AVAILABLE_ONLINE feature views can serve." Add an opt-in registry config flag, serve_features_while_materializing (default False), that also permits serving while a feature view is MATERIALIZING, letting servers keep returning last-materialized values during materialization. States that never had online data (CREATED, GENERATED) remain gated. Fixes #6780 Signed-off-by: Alan Gauthier <alan.gauthier@jobteaser.com>
1 parent 1f2584d commit a008155

6 files changed

Lines changed: 88 additions & 1 deletion

File tree

docs/reference/feature-store-yaml.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ registry:
6868
| `path` | string | — | Connection string or file path |
6969
| `schema_mode` | string | `auto` | SQL registry only. `auto`: create tables on startup; `verify`: check tables exist, error if missing; `skip`: no DDL or verification. See [SQL Registry docs](registries/sql.md#schema-management-schema_mode). |
7070
| `mcp.enabled` | bool | `false` | Enable MCP (Model Context Protocol) on the REST registry server |
71+
| `serve_features_while_materializing` | bool | `false` | Keep serving a feature view's last-materialized values while it is in the `MATERIALIZING` state, instead of rejecting online requests during materialization |
72+
73+
When `serve_features_while_materializing` is `true`, online serving continues for a
74+
feature view while `feast materialize` runs against a shared registry (which
75+
transitions the feature view to `MATERIALIZING`). This avoids serving interruptions
76+
from routine incremental materialization; feature views serve their
77+
last-materialized values until materialization completes. States that never had
78+
online data (e.g. `CREATED`, `GENERATED`) remain gated.
7179

7280
When `registry.mcp.enabled` is `true`, the REST registry server exposes registry
7381
metadata (entities, feature views, feature services) as MCP tool endpoints for

sdk/python/feast/infra/registry/registry.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ def __init__(
247247
else False
248248
)
249249

250+
self.serve_features_while_materializing = (
251+
registry_config.serve_features_while_materializing
252+
if registry_config is not None
253+
else False
254+
)
255+
250256
self.cache_mode = (
251257
registry_config.cache_mode if registry_config is not None else "sync"
252258
)

sdk/python/feast/infra/registry/sql.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,9 @@ def __init__(
376376
self.enable_online_versioning = (
377377
registry_config.enable_online_feature_view_versioning
378378
)
379+
self.serve_features_while_materializing = (
380+
registry_config.serve_features_while_materializing
381+
)
379382
super().__init__(
380383
project=project,
381384
cache_ttl_seconds=registry_config.cache_ttl_seconds,

sdk/python/feast/repo_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,15 @@ class RegistryConfig(FeastBaseModel):
203203
online store table and can be queried independently. Version history
204204
tracking in the registry is always active regardless of this setting. """
205205

206+
serve_features_while_materializing: StrictBool = False
207+
""" bool: Allow online serving to continue for a feature view while it is in
208+
the ``MATERIALIZING`` state. When ``feature_store.materialize()`` runs against
209+
a shared registry it transitions the feature view to ``MATERIALIZING`` and
210+
commits that state, which otherwise causes concurrent feature servers to
211+
reject requests until materialization completes. When True, feature views in
212+
the ``MATERIALIZING`` state keep serving their last-materialized values.
213+
Truly-unavailable states (e.g. ``CREATED``, ``GENERATED``) remain gated. """
214+
206215
mcp: Optional[McpRegistryConfig] = None
207216
""" McpRegistryConfig: MCP (Model Context Protocol) configuration for the registry REST server. """
208217

sdk/python/feast/utils.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1333,9 +1333,20 @@ def _append_or_merge_source_fv(fv_with_projection: "FeatureView") -> None:
13331333
if hasattr(fv, "state"):
13341334
from feast.feature_view import FeatureViewState
13351335

1336-
if isinstance(fv.state, FeatureViewState) and fv.state not in (
1336+
servable_states = {
13371337
FeatureViewState.STATE_UNSPECIFIED,
13381338
FeatureViewState.AVAILABLE_ONLINE,
1339+
}
1340+
# When 'serve_features_while_materializing' is enabled, keep serving
1341+
# the last-materialized values while a feature view is MATERIALIZING,
1342+
# so routine incremental materialization against a shared registry does
1343+
# not interrupt concurrent feature servers (see issue #6780).
1344+
if getattr(registry, "serve_features_while_materializing", False):
1345+
servable_states.add(FeatureViewState.MATERIALIZING)
1346+
1347+
if (
1348+
isinstance(fv.state, FeatureViewState)
1349+
and fv.state not in servable_states
13391350
):
13401351
raise ValueError(
13411352
f"Feature view '{name}' is in state '{fv.state.name}' "

sdk/python/tests/unit/test_feature_view_state.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import pytest
66

7+
from feast import utils
78
from feast.data_format import AvroFormat, ParquetFormat
89
from feast.data_source import KafkaSource
910
from feast.entity import Entity
@@ -431,3 +432,52 @@ def test_materialize_disabled_fv_by_name_raises(self, local_feature_store):
431432
end_date=datetime.utcnow(),
432433
)
433434
store.teardown()
435+
436+
437+
# ---------------------------------------------------------------------------
438+
# Serving lifecycle gate: serve_features_while_materializing
439+
# ---------------------------------------------------------------------------
440+
441+
442+
class _StubRegistry:
443+
"""Minimal registry stub exposing what _get_feature_views_to_use needs."""
444+
445+
def __init__(self, fv, serve_features_while_materializing=False):
446+
self._fv = fv
447+
self.serve_features_while_materializing = serve_features_while_materializing
448+
449+
def get_any_feature_view(self, name, project, allow_cache=False):
450+
return self._fv
451+
452+
453+
class TestServeWhileMaterializingGate:
454+
def _fv_in_state(self, state):
455+
fv = _simple_feature_view()
456+
fv.state = state
457+
return fv
458+
459+
def test_materializing_blocked_by_default(self):
460+
registry = _StubRegistry(self._fv_in_state(FeatureViewState.MATERIALIZING))
461+
with pytest.raises(ValueError, match="cannot serve features"):
462+
utils._get_feature_views_to_use(registry, "default", ["test_fv:f1"])
463+
464+
def test_materializing_served_when_flag_enabled(self):
465+
registry = _StubRegistry(
466+
self._fv_in_state(FeatureViewState.MATERIALIZING),
467+
serve_features_while_materializing=True,
468+
)
469+
fvs, _ = utils._get_feature_views_to_use(registry, "default", ["test_fv:f1"])
470+
assert [fv.name for fv in fvs] == ["test_fv"]
471+
472+
def test_available_online_served_regardless_of_flag(self):
473+
registry = _StubRegistry(self._fv_in_state(FeatureViewState.AVAILABLE_ONLINE))
474+
fvs, _ = utils._get_feature_views_to_use(registry, "default", ["test_fv:f1"])
475+
assert [fv.name for fv in fvs] == ["test_fv"]
476+
477+
def test_created_still_blocked_even_with_flag(self):
478+
registry = _StubRegistry(
479+
self._fv_in_state(FeatureViewState.CREATED),
480+
serve_features_while_materializing=True,
481+
)
482+
with pytest.raises(ValueError, match="cannot serve features"):
483+
utils._get_feature_views_to_use(registry, "default", ["test_fv:f1"])

0 commit comments

Comments
 (0)