Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a90cb7f
feat: Add version tracking to FeatureView, StreamFeatureView, and OnD…
franciscojavierarceo Mar 12, 2026
f28942b
fix: Address PR review feedback from Devin
franciscojavierarceo Mar 12, 2026
171785e
docs: Add feature view versioning documentation
franciscojavierarceo Mar 13, 2026
f035e96
fix: Address second round of PR review feedback from Devin
franciscojavierarceo Mar 13, 2026
0c12655
fix: Clean up version history on delete and use write_engine consiste…
franciscojavierarceo Mar 13, 2026
d32ed52
docs: Clarify versioning auto-increment behavior and pin/revert flow
franciscojavierarceo Mar 13, 2026
f9e896f
fix: Add pin conflict detection to both file and SQL registries
franciscojavierarceo Mar 13, 2026
2069b22
fix: Address Devin review feedback on versioning
franciscojavierarceo Mar 13, 2026
83393aa
docs: Document concurrent multi-version serving limitations
franciscojavierarceo Mar 13, 2026
94afe6e
feat: Implement version-qualified feature references (@v<N>)
franciscojavierarceo Mar 14, 2026
76d1afc
fix: Resolve mypy type errors in proto_registry_utils.py
franciscojavierarceo Mar 15, 2026
2541e41
feat: Add version metadata to clean @v2 syntax from feature names
franciscojavierarceo Mar 16, 2026
bceb052
fix: Update provider implementations with version metadata parameter
franciscojavierarceo Mar 16, 2026
14b2da0
fix: Add version metadata parameter to all online store implementations
franciscojavierarceo Mar 16, 2026
fd776fc
fix: Resolve mypy type errors in versioning code
franciscojavierarceo Mar 17, 2026
e9c4c68
fix: Address Devin review feedback on versioning
franciscojavierarceo Mar 17, 2026
903bda5
fix: Address additional Devin review feedback
franciscojavierarceo Mar 17, 2026
af47911
Merge branch 'master' into featureview-versioning
franciscojavierarceo Mar 17, 2026
dd31cdb
feat: Make feature view versioning opt-in via registry config
franciscojavierarceo Mar 17, 2026
8809805
fix: Address Devin review feedback on versioning issues
franciscojavierarceo Mar 17, 2026
d23c4bb
fix: Preserve version tag in response column names for multi-version …
franciscojavierarceo Mar 17, 2026
c5d4b49
feat: Handle version race conditions gracefully with retry and forwar…
franciscojavierarceo Mar 18, 2026
2a3e544
feat: Gate feature services that reference versioned feature views
franciscojavierarceo Mar 18, 2026
66c280b
fix: Resolve mypy errors and rename config field for clarity
franciscojavierarceo Mar 18, 2026
cfc038b
feat: Enable feature service serving for versioned feature views
franciscojavierarceo Mar 18, 2026
c9aea43
docs: Update RFC for feature service support and rename CLI command
franciscojavierarceo Mar 18, 2026
221e0ed
feat(ui): Add version display and Versions tab to feature view pages
franciscojavierarceo Mar 19, 2026
3efccbf
style(ui): Fix prettier formatting in feature view components
franciscojavierarceo Mar 19, 2026
6878fb0
updated utcnow
franciscojavierarceo Mar 20, 2026
280daf6
feat: Add version-aware materialization support
franciscojavierarceo Mar 20, 2026
43674ac
fix: Resolve three versioning regressions from review feedback
franciscojavierarceo Mar 20, 2026
01e4e77
feat: Add --no-promote flag to feast apply and fix versioned ref parsing
franciscojavierarceo Mar 23, 2026
1876060
docs: Consolidate versioning docs into alpha reference page
franciscojavierarceo Mar 23, 2026
760c003
docs: Add no_promote to apply_diff_to_registry docstring
franciscojavierarceo Mar 24, 2026
bc986ef
fix: Reject reserved chars in FV names and make version parser resilient
franciscojavierarceo Mar 24, 2026
b2d6c09
Merge branch 'master' into featureview-versioning
franciscojavierarceo Mar 24, 2026
3a73c87
fix: Add ensure_valid() call in Snowflake registry apply_feature_view
franciscojavierarceo Mar 24, 2026
1468bc5
Merge branch 'master' into featureview-versioning
franciscojavierarceo Mar 25, 2026
3c1ddbe
fix: Make version_tag optional in proto and use HasField() for correc…
franciscojavierarceo Mar 25, 2026
8c1259f
fix: Address versioning review feedback (Snowflake, Go server, SQL re…
franciscojavierarceo Mar 26, 2026
ac0348d
Merge branch 'master' into featureview-versioning
franciscojavierarceo Mar 26, 2026
7dfc447
fix: Handle @latest in Go feature server and pre-compile version regex
franciscojavierarceo Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: Add pin conflict detection to both file and SQL registries
Raises FeatureViewPinConflict when a user pins to an older version
while also modifying the feature view definition (schema, source, etc.).
Fixes FeatureView.__copy__() to include description and owner fields,
which was causing false positive conflict detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
  • Loading branch information
franciscojavierarceo and claude committed Mar 13, 2026
commit f9e896ff98163f910e6db7108c3d8e9f7c9d357e
4 changes: 3 additions & 1 deletion docs/getting-started/concepts/feature-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,9 @@ driver_stats = FeatureView(
)
```

When pinning, the other constructor parameters (schema, source, etc.) are ignored — the snapshot's content is used instead. Version history is not modified by a pin; the existing v0, v1, v2, etc. snapshots remain intact.
When pinning, the feature view definition (schema, source, transformations, etc.) must match the currently active definition. If you've also modified the definition alongside the pin, `feast apply` will raise a `FeatureViewPinConflict` error. To apply changes, use `version="latest"`. To revert, only change the `version` parameter.

The snapshot's content replaces the active feature view. Version history is not modified by a pin; the existing v0, v1, v2, etc. snapshots remain intact.

After reverting with a pin, you can go back to normal auto-incrementing behavior by removing the `version` parameter (or setting it to `"latest"`) and running `feast apply` again. If the restored definition differs from the pinned snapshot, a new version will be created.

Expand Down
9 changes: 9 additions & 0 deletions sdk/python/feast/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@ def __init__(self, name, version, project=None):
super().__init__(f"Version {version} of feature view {name} does not exist")


class FeatureViewPinConflict(FeastError):
def __init__(self, name, version):
super().__init__(
f"Cannot pin feature view '{name}' to {version} because the definition has also been modified. "
f"To pin to an older version, only change the 'version' parameter — do not modify other fields. "
f"To apply a new definition, use version='latest' or omit the version parameter."
)


class OnDemandFeatureViewNotFoundException(FeastObjectNotFoundException):
def __init__(self, name, project=None):
if project:
Expand Down
2 changes: 2 additions & 0 deletions sdk/python/feast/feature_view.py
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ def __copy__(self):
sink_source=self.batch_source if self.source_views else None,
enable_validation=self.enable_validation,
version=self.version,
description=self.description,
owner=self.owner,
)

# This is deliberately set outside of the FV initialization as we do not have the Entity objects.
Expand Down
29 changes: 29 additions & 0 deletions sdk/python/feast/infra/registry/registry.py
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
EntityNotFoundException,
FeatureServiceNotFoundException,
FeatureViewNotFoundException,
FeatureViewPinConflict,
FeatureViewVersionNotFound,
PermissionNotFoundException,
ProjectNotFoundException,
Expand Down Expand Up @@ -591,6 +592,34 @@ def apply_feature_view(
version_tag(pin_version),
project,
)

# Check that the user hasn't also modified the definition.
# Compare user's FV (with version="latest") against active FV.
self._prepare_registry_for_changes(project)
try:
active_fv = proto_registry_utils.get_any_feature_view(
self.cached_registry_proto, feature_view.name, project
)
user_fv_copy = feature_view.__copy__()
user_fv_copy.version = "latest"
active_fv.version = "latest"
# Clear metadata that differs due to registry state
user_fv_copy.created_timestamp = active_fv.created_timestamp
user_fv_copy.last_updated_timestamp = active_fv.last_updated_timestamp
user_fv_copy.current_version_number = active_fv.current_version_number
if hasattr(active_fv, "materialization_intervals"):
user_fv_copy.materialization_intervals = (
active_fv.materialization_intervals
)
if user_fv_copy != active_fv:
raise FeatureViewPinConflict(
feature_view.name, version_tag(pin_version)
)
except FeatureViewNotFoundException:
# FV doesn't exist yet — pinning to a version of a non-existent
# FV will fail anyway, let it proceed to the normal error path
pass

proto_class, python_class = self._proto_class_for_type(
record.feature_view_type
)
Expand Down
24 changes: 24 additions & 0 deletions sdk/python/feast/infra/registry/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
EntityNotFoundException,
FeatureServiceNotFoundException,
FeatureViewNotFoundException,
FeatureViewPinConflict,
FeatureViewVersionNotFound,
PermissionNotFoundException,
ProjectNotFoundException,
Expand Down Expand Up @@ -629,6 +630,29 @@ def apply_feature_view(
version_tag(pin_version),
project,
)

# Check that the user hasn't also modified the definition.
# Compare user's FV (with version="latest") against active FV.
try:
active_fv = self._get_any_feature_view(feature_view.name, project)
user_fv_copy = feature_view.__copy__()
user_fv_copy.version = "latest"
active_fv.version = "latest"
# Clear metadata that differs due to registry state
user_fv_copy.created_timestamp = active_fv.created_timestamp
user_fv_copy.last_updated_timestamp = active_fv.last_updated_timestamp
user_fv_copy.current_version_number = active_fv.current_version_number
if hasattr(active_fv, "materialization_intervals"):
user_fv_copy.materialization_intervals = (
active_fv.materialization_intervals
)
if user_fv_copy != active_fv:
raise FeatureViewPinConflict(
feature_view.name, version_tag(pin_version)
)
except FeatureViewNotFoundException:
pass

snap_type, snap_proto_bytes = snapshot
proto_class, python_class = self._proto_class_for_type(snap_type)
snap_proto = proto_class.FromString(snap_proto_bytes)
Expand Down
42 changes: 37 additions & 5 deletions sdk/python/tests/integration/registration/test_versioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest

from feast.entity import Entity
from feast.errors import FeatureViewVersionNotFound
from feast.errors import FeatureViewPinConflict, FeatureViewVersionNotFound
from feast.feature_view import FeatureView
from feast.field import Field
from feast.infra.registry.registry import Registry
Expand Down Expand Up @@ -97,8 +97,8 @@ def test_pin_to_v0(self, registry, make_fv):
fv2 = make_fv(description="updated")
registry.apply_feature_view(fv2, "test_project", commit=True)

# Pin to v0
fv_pin = make_fv(version="v0")
# Pin to v0 (definition must match active FV, only version changes)
fv_pin = make_fv(description="updated", version="v0")
registry.apply_feature_view(fv_pin, "test_project", commit=True)

# Verify active entry has v0's content
Expand All @@ -123,8 +123,8 @@ def test_apply_after_pin_creates_new_version(self, registry, make_fv):
fv2 = make_fv(description="v1 desc")
registry.apply_feature_view(fv2, "test_project", commit=True)

# Pin to v0
fv_pin = make_fv(version="v0")
# Pin to v0 (definition must match active FV, only version changes)
fv_pin = make_fv(description="v1 desc", version="v0")
registry.apply_feature_view(fv_pin, "test_project", commit=True)

# Apply new content (should create new version)
Expand All @@ -135,6 +135,38 @@ def test_apply_after_pin_creates_new_version(self, registry, make_fv):
# Should have v0, v1, and potentially more versions
assert len(versions) >= 2

def test_pin_with_modified_definition_raises(self, registry, make_fv):
# Create v0
fv1 = make_fv(description="original")
registry.apply_feature_view(fv1, "test_project", commit=True)

# Create v1
fv2 = make_fv(description="updated")
registry.apply_feature_view(fv2, "test_project", commit=True)

# Attempt to pin to v0 while also changing description
fv_pin = make_fv(description="sneaky change", version="v0")
with pytest.raises(FeatureViewPinConflict):
registry.apply_feature_view(fv_pin, "test_project", commit=True)

def test_pin_without_modification_succeeds(self, registry, make_fv):
# Create v0
fv1 = make_fv(description="original")
registry.apply_feature_view(fv1, "test_project", commit=True)

# Create v1
fv2 = make_fv(description="updated")
registry.apply_feature_view(fv2, "test_project", commit=True)

# Pin to v0 with same definition as active (only version changes)
fv_pin = make_fv(description="updated", version="v0")
registry.apply_feature_view(fv_pin, "test_project", commit=True)

# Verify active entry has v0's content
active_fv = registry.get_feature_view("driver_stats", "test_project")
assert active_fv.description == "original"
assert active_fv.version == "v0"

def test_version_in_proto_roundtrip(self, registry, make_fv):
fv = make_fv(version="v3")
# Manually set version number for testing
Expand Down
Loading