Skip to content

fix: Keep entity columns out of StreamFeatureView proto features - #7033

Merged
shuchu merged 2 commits into
feast-dev:masterfrom
vidigoat:fix/stream-feature-view-entity-columns
Nov 10, 2026
Merged

shuchu merged 2 commits into
feast-dev:masterfrom
vidigoat:fix/stream-feature-view-entity-columns

Conversation

@vidigoat

@vidigoat vidigoat commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

StreamFeatureView.to_proto() serializes spec.features from self.schema, and schema includes the entity (join key) columns. FeatureView.to_proto_spec() uses self.features, so every other feature view type keeps join keys only in spec.entity_columns. Because of this mismatch, a stream feature view loaded back from the registry lists its join key as a feature.

Repro with the local provider (file offline store, SQLite online store):

driver = Entity(name="driver", join_keys=["driver_id"], value_type=ValueType.INT64)
src = FileSource(name="src", path=path, timestamp_field="event_timestamp")
push = PushSource(name="push", batch_source=src)
sfv = StreamFeatureView(
    name="driver_sfv", entities=[driver], ttl=timedelta(days=1),
    schema=[Field(name="driver_id", dtype=Int64), Field(name="conv_rate", dtype=Float32)],
    source=push, timestamp_field="event_timestamp",
)
store.apply([driver, src, push, sfv])

[f.name for f in sfv.features]                                         # ['conv_rate']
[f.name for f in store.get_stream_feature_view("driver_sfv").features]  # ['conv_rate', 'driver_id']

svc = FeatureService(name="svc", features=[store.get_stream_feature_view("driver_sfv")])
store.apply([svc])
store.get_historical_features(entity_df=entity_df, features=svc)
# ValueError: The column label 'driver_id' is not unique.

The same spec.features field is read by the Go feature server (NewFeatureViewFromStreamFeatureViewProto) and the UI, so they also see the join key as a feature.

This PR serializes self.features instead of self.schema, the same as FeatureView.to_proto_spec(). from_proto already restores entity_columns from spec.entity_columns, so nothing is lost. With the change, the registry copy above has features ['conv_rate'] and the historical retrieval returns the expected frame.

A registry written by an earlier version keeps the extra column until the next feast apply. That apply reports the stream feature view as updated and rewrites spec.features. I checked this locally: apply on master, then re-apply with this change. If you would rather also filter entity columns out on the read side for registries that are never re-applied, I can add that here.

Merge order: please merge #7034 first (or together with this). Today the extra join key in the registry copy happens to make StreamFeatureView.__eq__ return False; once this PR removes it, the existing __eq__ short-circuit for UDF-less views makes the SQL registry skip writes when only aggregations change. #7034 fixes that equality.

Which issue(s) this PR fixes:

No existing issue. I found this while checking feature view proto round-trips, and searched open and closed issues and PRs for a duplicate before opening this.

Checks

  • I've made sure the tests are passing.
  • My commits are signed off (git commit -s)
  • My PR title follows conventional commits format

Testing Strategy

  • Unit tests
  • Integration tests
  • Manual tests
  • Testing is not required for this change

New test test_stream_feature_view_serialization_excludes_entity_columns_from_features in sdk/python/tests/unit/test_stream_feature_view.py defines the join key in schema. It checks that spec.features and spec.entity_columns are disjoint and that from_proto(to_proto(sfv)) == sfv. It fails on master (['dummy_field', 'driver_id'] == ['dummy_field']) and passes with this change. Manual test: the repro above, end to end, with the local provider.

Misc

Developed with Claude Code (AI) under my direction; the new unit test fails on master and passes with this change.

StreamFeatureView.to_proto() built spec.features from self.schema, which
includes the entity (join key) columns, while FeatureView.to_proto_spec()
uses self.features. A stream feature view read back from the registry
therefore listed its join key as a feature, and a FeatureService built
from it failed historical retrieval with a duplicate join key column.

Serialize self.features, matching FeatureView. Entity columns are still
stored in spec.entity_columns and restored by from_proto.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Vidit Patankar <[email protected]>
@codecov-commenter

codecov-commenter commented Oct 18, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 50.98%. Comparing base (c9b9d72) to head (05a2956).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #7033   +/-   ##
=======================================
  Coverage   50.98%   50.98%           
=======================================
  Files         455      455           
  Lines       56964    56964           
  Branches     8313     8313           
=======================================
  Hits        29044    29044           
  Misses      25935    25935           
  Partials     1985     1985           
Flag Coverage Δ
go-feature-server 30.58% <ø> (ø)
python-unit 52.44% <ø> (ø)
Files with missing lines Coverage Δ
sdk/python/feast/stream_feature_view.py 70.35% <ø> (ø)

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c9b9d72...05a2956. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@shuchu shuchu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm

@shuchu
shuchu merged commit d520584 into feast-dev:master Nov 10, 2026
2 checks passed
franciscojavierarceo pushed a commit that referenced this pull request Nov 10, 2026
# [0.68.0](v0.67.0...v0.68.0) (2026-11-10)

### Bug Fixes

* add watch verb on clusterroles and clusterrolebindings for feast-operator ([#7062](#7062)) ([b6bdda4](b6bdda4))
* Apply on demand feature views in SparkRetrievalJob.to_spark_df() ([#6993](#6993)) ([f416309](f416309))
* Break event timestamp ties by created timestamp in Milvus write batches ([#7084](#7084)) ([bbaca76](bbaca76))
* Break event timestamp ties by created timestamp in the Dask offline store ([#6952](#6952)) ([eb93b08](eb93b08))
* Build the Go feature server image in two stages ([1e5e90c](1e5e90c)), closes [#7060](#7060)
* Fall back to the serialized body when UDF source misses globals ([#6816](#6816)) ([f61079f](f61079f))
* Install patched libbrotli1 to address DSA-6582-1 ([#7096](#7096)) ([d458393](d458393))
* Keep description, tags and nested types of ODFV schema fields ([8ef6e75](8ef6e75))
* Keep entity columns out of StreamFeatureView proto features ([#7033](#7033)) ([d520584](d520584))
* Keep the metrics server running when a Gunicorn worker exits ([#7012](#7012)) ([68c5d54](68c5d54)), closes [#7011](#7011)
* Key registry proto caches on the proto, not its id() ([#7085](#7085)) ([147adad](147adad))
* Log ODFV materialization skips instead of printing them ([#7117](#7117)) ([8fdcee7](8fdcee7))
* Pin build dependencies of source builds to the CI lock ([#7104](#7104)) ([dec2f24](dec2f24))
* Populate spec.project in registry REST feature view responses (#… ([#7037](#7037)) ([e304a84](e304a84))
* Preserve colliding source timestamps in Dask retrieval ([#7071](#7071)) ([1396c88](1396c88))
* Preserve Dask historical requests without eligible features ([#7000](#7000)) ([fff09f7](fff09f7))
* Read None elements of UUID and Decimal lists as None ([#7039](#7039)) ([03f30fe](03f30fe))
* Retire the support of Python3.10 ([#7101](#7101)) ([a56766e](a56766e)), closes [#7104](#7104) [#7089](#7089) [#7106](#7106) [#7105](#7105)
* Serialize UUID and TimeUuid entity keys ([#7032](#7032)) ([0c845a1](0c845a1))
* Stop the Ray engine from hiding materialization errors ([#7026](#7026)) ([7903e2e](7903e2e)), closes [#7024](#7024)
* **ui:** stop treating the monitoring baseline as the latest window ([#7056](#7056)) ([0f01802](0f01802)), closes [#7054](#7054)
* Use date-only bounds for partition filters built from string timestamps ([#7116](#7116)) ([b1f2168](b1f2168))
* Validate entity column types in feast dbt import ([#7067](#7067)) ([abca80c](abca80c)), closes [#5876](#5876)
* Warm up the whole DynamoDB connection pool ([#6985](#6985)) ([8ce548c](8ce548c))

### Features

* Add --gc-freeze option to feast serve ([#7089](#7089)) ([aad0c15](aad0c15))
* Add native complex types and scalar index tags to the Milvus online store ([#7120](#7120)) ([f7068d6](f7068d6))
* Add num_partitions and partition_key_isolation to the Milvus online store ([#7083](#7083)) ([68de84e](68de84e)), closes [#6940](#6940)
* Add Pinecone online store integration ([#6585](#6585)) ([7a84f2a](7a84f2a))
* Add PolarisSource and PolarisProvider for Apache Polaris ([#7006](#7006)) ([386759c](386759c))
* Add row deletion to the Milvus online store ([#7082](#7082)) ([d3e2041](d3e2041))
* Add upstream FeatureView lineage support for PushSource ([#6853](#6853)) ([c9b9d72](c9b9d72))
* Optional nameless pre-computed vectors with a schema fingerprint ([#7077](#7077)) ([9420fac](9420fac))

### Performance Improvements

* Scan Lance sources lazily in the DuckDB offline store ([1299e4e](1299e4e))
shuchu added a commit to shuchu/feast that referenced this pull request Nov 11, 2026
…st-dev#7033)

StreamFeatureView.to_proto() built spec.features from self.schema, which
includes the entity (join key) columns, while FeatureView.to_proto_spec()
uses self.features. A stream feature view read back from the registry
therefore listed its join key as a feature, and a FeatureService built
from it failed historical retrieval with a duplicate join key column.

Serialize self.features, matching FeatureView. Entity columns are still
stored in spec.entity_columns and restored by from_proto.

Signed-off-by: Vidit Patankar <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Shuchu Han <[email protected]>
Signed-off-by: Shuchu Han <[email protected]>
shuchu pushed a commit to shuchu/feast that referenced this pull request Nov 11, 2026
# [0.68.0](feast-dev/feast@v0.67.0...v0.68.0) (2026-11-10)

### Bug Fixes

* add watch verb on clusterroles and clusterrolebindings for feast-operator ([feast-dev#7062](feast-dev#7062)) ([b6bdda4](feast-dev@b6bdda4))
* Apply on demand feature views in SparkRetrievalJob.to_spark_df() ([feast-dev#6993](feast-dev#6993)) ([f416309](feast-dev@f416309))
* Break event timestamp ties by created timestamp in Milvus write batches ([feast-dev#7084](feast-dev#7084)) ([bbaca76](feast-dev@bbaca76))
* Break event timestamp ties by created timestamp in the Dask offline store ([feast-dev#6952](feast-dev#6952)) ([eb93b08](feast-dev@eb93b08))
* Build the Go feature server image in two stages ([1e5e90c](feast-dev@1e5e90c)), closes [feast-dev#7060](feast-dev#7060)
* Fall back to the serialized body when UDF source misses globals ([feast-dev#6816](feast-dev#6816)) ([f61079f](feast-dev@f61079f))
* Install patched libbrotli1 to address DSA-6582-1 ([feast-dev#7096](feast-dev#7096)) ([d458393](feast-dev@d458393))
* Keep description, tags and nested types of ODFV schema fields ([8ef6e75](feast-dev@8ef6e75))
* Keep entity columns out of StreamFeatureView proto features ([feast-dev#7033](feast-dev#7033)) ([d520584](feast-dev@d520584))
* Keep the metrics server running when a Gunicorn worker exits ([feast-dev#7012](feast-dev#7012)) ([68c5d54](feast-dev@68c5d54)), closes [feast-dev#7011](feast-dev#7011)
* Key registry proto caches on the proto, not its id() ([feast-dev#7085](feast-dev#7085)) ([147adad](feast-dev@147adad))
* Log ODFV materialization skips instead of printing them ([feast-dev#7117](feast-dev#7117)) ([8fdcee7](feast-dev@8fdcee7))
* Pin build dependencies of source builds to the CI lock ([feast-dev#7104](feast-dev#7104)) ([dec2f24](feast-dev@dec2f24))
* Populate spec.project in registry REST feature view responses (#… ([feast-dev#7037](feast-dev#7037)) ([e304a84](feast-dev@e304a84))
* Preserve colliding source timestamps in Dask retrieval ([feast-dev#7071](feast-dev#7071)) ([1396c88](feast-dev@1396c88))
* Preserve Dask historical requests without eligible features ([feast-dev#7000](feast-dev#7000)) ([fff09f7](feast-dev@fff09f7))
* Read None elements of UUID and Decimal lists as None ([feast-dev#7039](feast-dev#7039)) ([03f30fe](feast-dev@03f30fe))
* Retire the support of Python3.10 ([feast-dev#7101](feast-dev#7101)) ([a56766e](feast-dev@a56766e)), closes [feast-dev#7104](feast-dev#7104) [feast-dev#7089](feast-dev#7089) [feast-dev#7106](feast-dev#7106) [feast-dev#7105](feast-dev#7105)
* Serialize UUID and TimeUuid entity keys ([feast-dev#7032](feast-dev#7032)) ([0c845a1](feast-dev@0c845a1))
* Stop the Ray engine from hiding materialization errors ([feast-dev#7026](feast-dev#7026)) ([7903e2e](feast-dev@7903e2e)), closes [feast-dev#7024](feast-dev#7024)
* **ui:** stop treating the monitoring baseline as the latest window ([feast-dev#7056](feast-dev#7056)) ([0f01802](feast-dev@0f01802)), closes [feast-dev#7054](feast-dev#7054)
* Use date-only bounds for partition filters built from string timestamps ([feast-dev#7116](feast-dev#7116)) ([b1f2168](feast-dev@b1f2168))
* Validate entity column types in feast dbt import ([feast-dev#7067](feast-dev#7067)) ([abca80c](feast-dev@abca80c)), closes [feast-dev#5876](feast-dev#5876)
* Warm up the whole DynamoDB connection pool ([feast-dev#6985](feast-dev#6985)) ([8ce548c](feast-dev@8ce548c))

### Features

* Add --gc-freeze option to feast serve ([feast-dev#7089](feast-dev#7089)) ([aad0c15](feast-dev@aad0c15))
* Add native complex types and scalar index tags to the Milvus online store ([feast-dev#7120](feast-dev#7120)) ([f7068d6](feast-dev@f7068d6))
* Add num_partitions and partition_key_isolation to the Milvus online store ([feast-dev#7083](feast-dev#7083)) ([68de84e](feast-dev@68de84e)), closes [feast-dev#6940](feast-dev#6940)
* Add Pinecone online store integration ([feast-dev#6585](feast-dev#6585)) ([7a84f2a](feast-dev@7a84f2a))
* Add PolarisSource and PolarisProvider for Apache Polaris ([feast-dev#7006](feast-dev#7006)) ([386759c](feast-dev@386759c))
* Add row deletion to the Milvus online store ([feast-dev#7082](feast-dev#7082)) ([d3e2041](feast-dev@d3e2041))
* Add upstream FeatureView lineage support for PushSource ([feast-dev#6853](feast-dev#6853)) ([c9b9d72](feast-dev@c9b9d72))
* Optional nameless pre-computed vectors with a schema fingerprint ([feast-dev#7077](feast-dev#7077)) ([9420fac](feast-dev@9420fac))

### Performance Improvements

* Scan Lance sources lazily in the DuckDB offline store ([1299e4e](feast-dev@1299e4e))

Signed-off-by: Shuchu Han <[email protected]>
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.

4 participants