Skip to content

Commit a90cb7f

Browse files
feat: Add version tracking to FeatureView, StreamFeatureView, and OnDemandFeatureView
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>
1 parent 94ad0e7 commit a90cb7f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1456
-170
lines changed

protos/feast/core/FeatureView.proto

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ message FeatureView {
3636
FeatureViewMeta meta = 2;
3737
}
3838

39-
// Next available id: 18
39+
// Next available id: 19
4040
// TODO(adchia): refactor common fields from this and ODFV into separate metadata proto
4141
message FeatureViewSpec {
4242
// Name of the feature view. Must be unique. Not updated.
@@ -94,6 +94,9 @@ message FeatureViewSpec {
9494

9595
// Whether schema validation is enabled during materialization
9696
bool enable_validation = 17;
97+
98+
// User-specified version pin (e.g. "latest", "v2", "version2")
99+
string version = 18;
97100
}
98101

99102
message FeatureViewMeta {
@@ -105,6 +108,12 @@ message FeatureViewMeta {
105108

106109
// List of pairs (start_time, end_time) for which this feature view has been materialized.
107110
repeated MaterializationInterval materialization_intervals = 3;
111+
112+
// The current version number of this feature view in the version history.
113+
int32 current_version_number = 4;
114+
115+
// Auto-generated UUID identifying this specific version.
116+
string version_id = 5;
108117
}
109118

110119
message MaterializationInterval {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// Copyright 2024 The Feast Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
syntax = "proto3";
18+
package feast.core;
19+
20+
option go_package = "github.com/feast-dev/feast/go/protos/feast/core";
21+
option java_outer_classname = "FeatureViewVersionProto";
22+
option java_package = "feast.proto.core";
23+
24+
import "google/protobuf/timestamp.proto";
25+
26+
message FeatureViewVersionRecord {
27+
string feature_view_name = 1;
28+
string project_id = 2;
29+
int32 version_number = 3;
30+
// "feature_view" | "stream_feature_view" | "on_demand_feature_view"
31+
string feature_view_type = 4;
32+
// serialized FV proto snapshot
33+
bytes feature_view_proto = 5;
34+
google.protobuf.Timestamp created_timestamp = 6;
35+
string description = 7;
36+
// auto-generated UUID for unique identification
37+
string version_id = 8;
38+
}
39+
40+
message FeatureViewVersionHistory {
41+
repeated FeatureViewVersionRecord records = 1;
42+
}

protos/feast/core/OnDemandFeatureView.proto

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ message OnDemandFeatureView {
3636
OnDemandFeatureViewMeta meta = 2;
3737
}
3838

39-
// Next available id: 9
39+
// Next available id: 18
4040
message OnDemandFeatureViewSpec {
4141
// Name of the feature view. Must be unique. Not updated.
4242
string name = 1;
@@ -75,6 +75,8 @@ message OnDemandFeatureViewSpec {
7575
// Aggregation definitions
7676
repeated Aggregation aggregations = 16;
7777

78+
// User-specified version pin (e.g. "latest", "v2", "version2")
79+
string version = 17;
7880
}
7981

8082
message OnDemandFeatureViewMeta {
@@ -83,6 +85,12 @@ message OnDemandFeatureViewMeta {
8385

8486
// Time where this Feature View is last updated
8587
google.protobuf.Timestamp last_updated_timestamp = 2;
88+
89+
// The current version number of this feature view in the version history.
90+
int32 current_version_number = 3;
91+
92+
// Auto-generated UUID identifying this specific version.
93+
string version_id = 4;
8694
}
8795

8896
message OnDemandSource {

protos/feast/core/Registry.proto

Lines changed: 65 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,65 @@
1-
//
2-
// * Copyright 2020 The Feast Authors
3-
// *
4-
// * Licensed under the Apache License, Version 2.0 (the "License");
5-
// * you may not use this file except in compliance with the License.
6-
// * You may obtain a copy of the License at
7-
// *
8-
// * https://www.apache.org/licenses/LICENSE-2.0
9-
// *
10-
// * Unless required by applicable law or agreed to in writing, software
11-
// * distributed under the License is distributed on an "AS IS" BASIS,
12-
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
// * See the License for the specific language governing permissions and
14-
// * limitations under the License.
15-
//
16-
17-
syntax = "proto3";
18-
19-
package feast.core;
20-
option java_package = "feast.proto.core";
21-
option java_outer_classname = "RegistryProto";
22-
option go_package = "github.com/feast-dev/feast/go/protos/feast/core";
23-
24-
import "feast/core/Entity.proto";
25-
import "feast/core/FeatureService.proto";
26-
import "feast/core/FeatureTable.proto";
27-
import "feast/core/FeatureView.proto";
28-
import "feast/core/InfraObject.proto";
29-
import "feast/core/OnDemandFeatureView.proto";
30-
import "feast/core/StreamFeatureView.proto";
31-
import "feast/core/DataSource.proto";
32-
import "feast/core/SavedDataset.proto";
33-
import "feast/core/ValidationProfile.proto";
34-
import "google/protobuf/timestamp.proto";
35-
import "feast/core/Permission.proto";
36-
import "feast/core/Project.proto";
37-
38-
// Next id: 18
39-
message Registry {
40-
repeated Entity entities = 1;
41-
repeated FeatureTable feature_tables = 2;
42-
repeated FeatureView feature_views = 6;
43-
repeated DataSource data_sources = 12;
44-
repeated OnDemandFeatureView on_demand_feature_views = 8;
45-
repeated StreamFeatureView stream_feature_views = 14;
46-
repeated FeatureService feature_services = 7;
47-
repeated SavedDataset saved_datasets = 11;
48-
repeated ValidationReference validation_references = 13;
49-
Infra infra = 10;
50-
// Tracking metadata of Feast by project
51-
repeated ProjectMetadata project_metadata = 15 [deprecated = true];
52-
53-
string registry_schema_version = 3; // to support migrations; incremented when schema is changed
54-
string version_id = 4; // version id, random string generated on each update of the data; now used only for debugging purposes
55-
google.protobuf.Timestamp last_updated = 5;
56-
repeated Permission permissions = 16;
57-
repeated Project projects = 17;
58-
}
59-
60-
message ProjectMetadata {
61-
string project = 1;
62-
string project_uuid = 2;
63-
}
1+
//
2+
// * Copyright 2020 The Feast Authors
3+
// *
4+
// * Licensed under the Apache License, Version 2.0 (the "License");
5+
// * you may not use this file except in compliance with the License.
6+
// * You may obtain a copy of the License at
7+
// *
8+
// * https://www.apache.org/licenses/LICENSE-2.0
9+
// *
10+
// * Unless required by applicable law or agreed to in writing, software
11+
// * distributed under the License is distributed on an "AS IS" BASIS,
12+
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// * See the License for the specific language governing permissions and
14+
// * limitations under the License.
15+
//
16+
17+
syntax = "proto3";
18+
19+
package feast.core;
20+
option java_package = "feast.proto.core";
21+
option java_outer_classname = "RegistryProto";
22+
option go_package = "github.com/feast-dev/feast/go/protos/feast/core";
23+
24+
import "feast/core/Entity.proto";
25+
import "feast/core/FeatureService.proto";
26+
import "feast/core/FeatureTable.proto";
27+
import "feast/core/FeatureView.proto";
28+
import "feast/core/InfraObject.proto";
29+
import "feast/core/OnDemandFeatureView.proto";
30+
import "feast/core/StreamFeatureView.proto";
31+
import "feast/core/DataSource.proto";
32+
import "feast/core/SavedDataset.proto";
33+
import "feast/core/ValidationProfile.proto";
34+
import "google/protobuf/timestamp.proto";
35+
import "feast/core/Permission.proto";
36+
import "feast/core/Project.proto";
37+
import "feast/core/FeatureViewVersion.proto";
38+
39+
// Next id: 19
40+
message Registry {
41+
repeated Entity entities = 1;
42+
repeated FeatureTable feature_tables = 2;
43+
repeated FeatureView feature_views = 6;
44+
repeated DataSource data_sources = 12;
45+
repeated OnDemandFeatureView on_demand_feature_views = 8;
46+
repeated StreamFeatureView stream_feature_views = 14;
47+
repeated FeatureService feature_services = 7;
48+
repeated SavedDataset saved_datasets = 11;
49+
repeated ValidationReference validation_references = 13;
50+
Infra infra = 10;
51+
// Tracking metadata of Feast by project
52+
repeated ProjectMetadata project_metadata = 15 [deprecated = true];
53+
54+
string registry_schema_version = 3; // to support migrations; incremented when schema is changed
55+
string version_id = 4; // version id, random string generated on each update of the data; now used only for debugging purposes
56+
google.protobuf.Timestamp last_updated = 5;
57+
repeated Permission permissions = 16;
58+
repeated Project projects = 17;
59+
FeatureViewVersionHistory feature_view_version_history = 18;
60+
}
61+
62+
message ProjectMetadata {
63+
string project = 1;
64+
string project_uuid = 2;
65+
}

protos/feast/core/StreamFeatureView.proto

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ message StreamFeatureView {
3737
FeatureViewMeta meta = 2;
3838
}
3939

40-
// Next available id: 21
40+
// Next available id: 22
4141
message StreamFeatureViewSpec {
4242
// Name of the feature view. Must be unique. Not updated.
4343
string name = 1;
@@ -102,5 +102,8 @@ message StreamFeatureViewSpec {
102102

103103
// Whether schema validation is enabled during materialization
104104
bool enable_validation = 20;
105+
106+
// User-specified version pin (e.g. "latest", "v2", "version2")
107+
string version = 21;
105108
}
106109

sdk/python/feast/base_feature_view.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class BaseFeatureView(ABC):
5656
projection: FeatureViewProjection
5757
created_timestamp: Optional[datetime]
5858
last_updated_timestamp: Optional[datetime]
59+
version: str
60+
current_version_number: Optional[int]
5961

6062
@abstractmethod
6163
def __init__(
@@ -92,6 +94,10 @@ def __init__(
9294
self.projection = FeatureViewProjection.from_definition(self)
9395
self.created_timestamp = None
9496
self.last_updated_timestamp = None
97+
if not hasattr(self, "version"):
98+
self.version = "latest"
99+
if not hasattr(self, "current_version_number"):
100+
self.current_version_number = None
95101

96102
self.source = source
97103

sdk/python/feast/cli/feature_views.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,47 @@ def feature_view_list(ctx: click.Context, tags: list[str]):
7070
from tabulate import tabulate
7171

7272
print(tabulate(table, headers=["NAME", "ENTITIES", "TYPE"], tablefmt="plain"))
73+
74+
75+
@feature_views_cmd.command("versions")
76+
@click.argument("name", type=click.STRING)
77+
@click.pass_context
78+
def feature_view_versions(ctx: click.Context, name: str):
79+
"""
80+
List version history for a feature view
81+
"""
82+
store = create_feature_store(ctx)
83+
84+
try:
85+
versions = store.list_feature_view_versions(name)
86+
except NotImplementedError:
87+
print("Version history is not supported by this registry backend.")
88+
exit(1)
89+
except Exception as e:
90+
print(e)
91+
exit(1)
92+
93+
if not versions:
94+
print(f"No version history found for feature view '{name}'.")
95+
return
96+
97+
table = []
98+
for v in versions:
99+
table.append(
100+
[
101+
v["version"],
102+
v["feature_view_type"],
103+
str(v["created_timestamp"]),
104+
v["version_id"],
105+
]
106+
)
107+
108+
from tabulate import tabulate
109+
110+
print(
111+
tabulate(
112+
table,
113+
headers=["VERSION", "TYPE", "CREATED", "VERSION_ID"],
114+
tablefmt="plain",
115+
)
116+
)

sdk/python/feast/errors.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ def __init__(self, name, project=None):
128128
super().__init__(f"Feature view {name} does not exist")
129129

130130

131+
class FeatureViewVersionNotFound(FeastObjectNotFoundException):
132+
def __init__(self, name, version, project=None):
133+
if project:
134+
super().__init__(
135+
f"Version {version} of feature view {name} does not exist in project {project}"
136+
)
137+
else:
138+
super().__init__(f"Version {version} of feature view {name} does not exist")
139+
140+
131141
class OnDemandFeatureViewNotFoundException(FeastObjectNotFoundException):
132142
def __init__(self, name, project=None):
133143
if project:

sdk/python/feast/feature_store.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,18 @@ def _get_feature_view(
568568
feature_view.entities = []
569569
return feature_view
570570

571+
def list_feature_view_versions(self, name: str) -> List[Dict[str, Any]]:
572+
"""
573+
List version history for a feature view.
574+
575+
Args:
576+
name: Name of feature view.
577+
578+
Returns:
579+
List of version records.
580+
"""
581+
return self.registry.list_feature_view_versions(name, self.project)
582+
571583
def get_stream_feature_view(
572584
self, name: str, allow_registry_cache: bool = False
573585
) -> StreamFeatureView:

0 commit comments

Comments
 (0)