Skip to content

feat: Add version tracking to FeatureView#6101

Merged
franciscojavierarceo merged 42 commits intomasterfrom
featureview-versioning
Mar 26, 2026
Merged

feat: Add version tracking to FeatureView#6101
franciscojavierarceo merged 42 commits intomasterfrom
featureview-versioning

Conversation

@franciscojavierarceo
Copy link
Copy Markdown
Member

@franciscojavierarceo franciscojavierarceo commented Mar 12, 2026

Summary

Adds automatic version tracking to Feast feature views (FeatureView, StreamFeatureView, OnDemandFeatureView). Every feast apply that changes a feature view's schema or UDF creates a versioned snapshot in the registry. Users can list version history, pin serving to a prior version, query specific versions via @v<N> syntax, and stage new versions without promoting them.

Resolves #2728

Key Features

  • Automatic version history — every schema/UDF change is tracked; metadata-only changes (description, tags, TTL) update in place without creating a new version
  • Version pinning — declaratively pin to a historical version: FeatureView(name="driver_stats", version="v2")
  • Version-qualified online reads — query specific versions: driver_stats@v2:trips_today (SQLite online store)
  • Staged publishing (--no-promote)feast apply --no-promote saves a new version without promoting it to active, enabling phased rollouts without a transition window
  • Version-aware materializationfeast materialize --views fv --version v2 targets a specific version's online store table
  • Feature service support — feature services automatically resolve versioned FVs when enable_online_feature_view_versioning is enabled
  • Pin conflict detection — prevents applying a schema change and version pin simultaneously
  • Concurrent apply safety — SQL registry uses unique constraints with retry; file registry is last-write-wins (pre-existing limitation)
  • Full backward compatibilityversion is optional; omitting it is identical to version="latest"

Changes

Proto layer:

  • New FeatureViewVersion.proto with FeatureViewVersionRecord / FeatureViewVersionHistory
  • Added version field to FeatureViewSpec, StreamFeatureViewSpec, OnDemandFeatureViewSpec
  • Added current_version_number to meta messages, version_tag to FeatureViewProjection
  • Added feature_view_version_history to Registry proto

Python SDK — core versioning:

  • version_utils.py — parses "latest", "v2", "version2" (case-insensitive)
  • version parameter on FeatureView, StreamFeatureView, OnDemandFeatureView
  • Version-aware apply_feature_view in file registry, SQL registry (with concurrent apply retry)
  • list_feature_view_versions(name, project) on registries and FeatureStore
  • get_feature_view_by_version(name, project, version_number) for explicit version lookup
  • _schema_or_udf_changed() to distinguish version-significant changes from metadata updates

Python SDK — --no-promote flag:

  • feast apply --no-promote CLI option threaded through apply_total()apply_total_with_repo_instance()store.apply() / store._apply_diffs()registry.apply_feature_view()
  • File registry: saves version snapshot, restores old active proto
  • SQL registry: saves version snapshot, skips active row UPDATE
  • Python SDK: store.apply([...], no_promote=True)

Python SDK — version-qualified reads:

  • _parse_feature_ref() and _strip_version_from_ref() utilities for fv@v<N>:feature parsing
  • Version-aware online feature retrieval with @v<N> syntax
  • Version metadata injection in online feature responses
  • Feature service version resolution with version_tag on projections

Python SDK — feature ref parsing audit & fixes:

  • Fixed ibis offline store prefix matching to use name_to_use() for versioned refs
  • Fixed passthrough_provider.py saved dataset column naming to strip @v<N>
  • Fixed Go ParseFeatureReference to strip @v<N> from feature view name

CLI:

  • feast feature-views versions <name> — list version history
  • feast apply --no-promote — stage versions without promoting
  • feast materialize --views <name> --version v<N> — version-aware materialization

UI:

  • Version display on feature view detail pages (Regular, Stream, OnDemand)
  • New "Versions" tab showing version history with timestamps

Documentation:

  • RFC: docs/rfcs/feature-view-versioning.md — full design, concurrency, feature services, staged publishing
  • Alpha reference: docs/reference/alpha-feature-view-versioning.md
  • Updated feature view concepts page with [Alpha] Versioning section
  • Updated CLI reference, retrieval docs, and SUMMARY.md

Test plan

  • 47 unit tests: version parsing, normalization, proto roundtrip, equality, copy, feature ref parsing, table ID generation
  • 37 integration tests: apply/version creation, idempotency, pinning, pin conflicts, forward declaration, schema vs metadata changes, stream FV UDF changes, version metadata, feature service gates, --no-promote (5 tests)
  • Pre-commit hooks pass (format, lint, detect-secrets)
  • mypy type checking passes on all modified files
  • CI: existing unit tests pass (no regressions)

UI Preview

Screenshot 2026-03-19 at 8 57 59 AM

🤖 Generated with Claude Code

Open with Devin

…emandFeatureView

Every `feast apply` now creates a version snapshot. Users can pin a
feature view to a specific historical version declaratively via
`version="v2"`. By default, the latest version is always served.

- New proto: FeatureViewVersion.proto with version record/history
- Added `version` field to FeatureViewSpec, StreamFeatureViewSpec,
  OnDemandFeatureViewSpec and version metadata to their Meta messages
- New version_utils module for parsing/normalizing version strings
- Version-aware apply_feature_view in both SQL and file registries
- New `list_feature_view_versions` API on FeatureStore and registries
- CLI: `feast feature-views versions <name>` subcommand
- Updated all 14 templates with explicit `version="latest"`
- Unit tests (28) and integration tests (7) for versioning

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@franciscojavierarceo franciscojavierarceo requested a review from a team as a code owner March 12, 2026 20:14
devin-ai-integration[bot]

This comment was marked as resolved.

franciscojavierarceo and others added 2 commits March 12, 2026 16:40
- Fix current_version_number=0 being silently dropped during proto
  deserialization in FeatureView, OnDemandFeatureView (proto3 int32
  default 0 is falsy in Python); use spec.version to disambiguate
- Add current_version_number restoration in StreamFeatureView.from_proto
  (was missing entirely)
- Use timezone-aware UTC datetime in SqlRegistry.list_feature_view_versions
  for consistency with the rest of the codebase
- Add test for v0 proto roundtrip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Versioning section to feature-view.md concept page covering
  automatic snapshots, version pinning, version string formats,
  CLI usage, and Python SDK API
- Add `feast feature-views versions` command to CLI reference

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

- Fix current_version_number roundtrip bug: version="latest" (always
  truthy) caused None to become 0 after proto roundtrip; now check
  that spec.version is not "latest" before treating 0 as intentional
- Use write_engine (not read_engine) for pre/post apply reads in
  SqlRegistry to avoid read replica lag causing missed version snapshots
- Remove redundant version check in StreamFeatureView.__eq__ (parent
  FeatureView.__eq__ already checks it)
- Add else clause to StreamFeatureView.from_proto for consistency
- Add test for latest/None roundtrip preservation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…ntly

- delete_feature_view now also deletes version history records,
  preventing IntegrityError when re-creating a previously deleted FV
- _get_next_version_number uses write_engine instead of read_engine
  to avoid stale version numbers with read replicas

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

- Add step-by-step walkthrough showing how versions auto-increment
  on changes and skip on identical re-applies
- Add CLI example showing the apply/change/apply cycle
- Clarify that pinning ignores constructor params and uses the snapshot
- Explain how to return to auto-incrementing after a pin/revert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

🐛 1 issue in files not directly in the diff

🐛 BatchFeatureView missing version parameter — incomplete transformation (sdk/python/feast/batch_feature_view.py:80-102)

FeatureView, StreamFeatureView, and OnDemandFeatureView all accept a version parameter in their constructors, but BatchFeatureView.__init__ (sdk/python/feast/batch_feature_view.py:80-102) does not. Furthermore, BatchFeatureView.__init__ does not pass version to super().__init__() at sdk/python/feast/batch_feature_view.py:144-158. This means users cannot construct a BatchFeatureView with a pinned version (e.g., BatchFeatureView(..., version="v2") raises TypeError), and any BatchFeatureView created directly always defaults to version="latest". The proto round-trip path works because FeatureView.from_proto sets feature_view.version after construction, but the constructor API is inconsistent with the other feature view types.

View 19 additional findings in Devin Review.

Open in Devin Review

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>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

🐛 1 issue in files not directly in the diff

🐛 BatchFeatureView missing version parameter — incomplete transformation (sdk/python/feast/batch_feature_view.py:80-102)

FeatureView, StreamFeatureView, and OnDemandFeatureView all accept a version parameter in their constructors, but BatchFeatureView.__init__ (sdk/python/feast/batch_feature_view.py:80-102) does not. Furthermore, BatchFeatureView.__init__ does not pass version to super().__init__() at sdk/python/feast/batch_feature_view.py:144-158. This means users cannot construct a BatchFeatureView with a pinned version (e.g., BatchFeatureView(..., version="v2") raises TypeError), and any BatchFeatureView created directly always defaults to version="latest". The proto round-trip path works because FeatureView.from_proto sets feature_view.version after construction, but the constructor API is inconsistent with the other feature view types.

View 23 additional findings in Devin Review.

Open in Devin Review

franciscojavierarceo and others added 3 commits March 13, 2026 15:14
- Add version parameter to BatchFeatureView constructor for consistency
  with FeatureView, StreamFeatureView, and OnDemandFeatureView
- Clean up version history records in file registry delete_feature_view
  to prevent orphaned records on re-creation
- Fix current_version_number proto roundtrip: preserve 0 when
  version="latest" (after first apply) instead of incorrectly returning
  None

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clarify that versioning provides definition management and rollback,
not concurrent multi-version serving. Document recommended approaches
(separate projects or distinct FV names) for A/B testing scenarios.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extends feature view versioning with support for reading features from specific
versions at query time using the syntax: "driver_stats@v2:trips_today"

Core changes:
- Add _parse_feature_ref() to parse version-qualified feature references
- Update all feature reference parsing to use _parse_feature_ref()
- Add get_feature_view_by_version() to BaseRegistry and all implementations
- Add FeatureViewProjection.version_tag for multi-version query support
- Add version-aware _table_id() in SQLite online store (v0→unversioned, v1+→_v{N})
- Add VersionedOnlineReadNotSupported error for unsupported stores

Features:
- "driver_stats:trips" = "driver_stats@latest:trips" (backward compatible)
- "driver_stats@v2:trips" reads from v2 snapshot using _v2 table suffix
- Multiple versions in same query: ["driver@v1:trips", "driver@v2:daily"]
- Version parameter added to all decorator functions for consistency

Backward compatibility:
- Unversioned table serves as v0, only v1+ get _v{N} suffix
- All existing queries work unchanged
- SQLite-only for now, other stores raise clear error

Documentation:
- Updated feature-view.md with @Version syntax examples
- Updated feature-retrieval.md reference format
- Added version examples to how-to guides

Tests: 47 unit + 11 integration tests pass, no regressions

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
@franciscojavierarceo franciscojavierarceo requested a review from a team as a code owner March 14, 2026 18:11
@franciscojavierarceo franciscojavierarceo requested review from ejscribner, robhowley and tokoko and removed request for a team March 14, 2026 18:11
devin-ai-integration[bot]

This comment was marked as resolved.

- Fix type inference issues in get_feature_view_by_version()
- Use distinct variable names for different proto types
- Ensure proper type annotations for BaseFeatureView subclasses
@tokoko
Copy link
Copy Markdown
Collaborator

tokoko commented Mar 15, 2026

I haven't been able to go through the whole thing yet, but iiuc the expectation is that online stores would be expected to handle this by essentially treating fv1@v1 and fv1@v2 as two different feature views, right? (sqlite example infers table name from the version). Just wondering, maybe that's to high a bat for online store implementations. for example, if you have fv1@v1 and fv1@v2 both containing 100 features that differ by a single feature only, the online store would have to keep redundant copies of the other 99 features or do some complicated logic to diff values and deduplicate.

wdyt about concentrating on introducing versioning on feature level instead? tbh, that makes more intuitive sense to me. there's a feature called income that one found a fault with. we enable them to introduce income@v2 and somehow deprecate income@v1. online store can handle income@v1 and income@v2 as separate features, but all the rest of the features are unaffected.

@franciscojavierarceo
Copy link
Copy Markdown
Member Author

I haven't been able to go through the whole thing yet, but iiuc the expectation is that online stores would be expected to handle this by essentially treating fv1@v1 and fv1@v2 as two different feature views, right? (sqlite example infers table name from the version). Just wondering, maybe that's to high a bat for online store implementations. for example, if you have fv1@v1 and fv1@v2 both containing 100 features that differ by a single feature only, the online store would have to keep redundant copies of the other 99 features or do some complicated logic to diff values and deduplicate.

wdyt about concentrating on introducing versioning on feature level instead? tbh, that makes more intuitive sense to me. there's a feature called income that one found a fault with. we enable them to introduce income@v2 and somehow deprecate income@v1. online store can handle income@v1 and income@v2 as separate features, but all the rest of the features are unaffected.

I spent some time reasoning about feature-level versioning with Claude. My initial reaction was that it's too large of a change and it only works today in a broken sense.

By "in a broken sense" I mean that today we don't really version feature views or features, if someone changes the feature view or the feature, we just overwrite it and lose history. Moreover, we essentially force the materialization to be out of sync. Today, that just ignores the behavior silently.

I like how we've implemented it here (i.e., creating a new table and storing the history of the metadata but allowing for callers to specify exact versions and defaulting to the existing behavior) because it is far more explicit about the challenges of materialization consistency issues when you change feature versions.

So, I don't recommend we do feature-level versioning as I worry it makes materialization very unreliable. We can, in fact, declare feature view level versioning because transformations are a collection of features mapped one-to-one with a table.

Implement optional feature view version metadata in API responses to address
the issue where internal @v2 version syntax was leaking into client responses.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Add missing include_feature_view_version_metadata parameter to:
- EmbeddedGoOnlineFeaturesService.get_online_features()
- FooProvider.get_online_features() and get_online_features_async()
- FooProvider.retrieve_online_documents() and retrieve_online_documents_v2()

This resolves CI failures where provider implementations were not updated
with the new parameter from the abstract Provider interface.

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@ntkathole ntkathole left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good - approving, improvements can be done as a follow-up. No regressions and mostly gated behind the flag.

…t zero-value handling

The proto field `version_tag` was a plain `int32`, making 0 and "not set"
indistinguishable. Changed to `optional int32` so `HasField()` works
correctly, and updated `FeatureViewProjection.from_proto` to use it
instead of `> 0` which silently dropped version_tag=0.

Also removes dead issue link from VersionedOnlineReadNotSupported error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gistry)

- Snowflake registry: raise NotImplementedError when no_promote=True
  since versioning is not supported
- Go feature server: return error on versioned refs (@vn) instead of
  silently stripping the version and serving unversioned data
- SQL registry: inline delete logic in delete_feature_view so the FV
  delete and version history cleanup run in a single transaction,
  preventing orphaned rows on partial failure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

franciscojavierarceo and others added 2 commits March 26, 2026 09:56
- Strip @latest suffix (equivalent to no version) instead of passing
  through as part of the FV name, which caused confusing "not found" errors
- Pre-compile version tag regex to package-level var to avoid recompilation
  on every call in the hot path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@HaoXuAI HaoXuAI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@franciscojavierarceo franciscojavierarceo merged commit ed4a4f2 into master Mar 26, 2026
38 of 39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improved feature view and model versioning

5 participants