Skip to content

fix: keep HybridOnlineStore routing_tag across FeatureViews - #6769

Open
jang-hs wants to merge 1 commit into
feast-dev:masterfrom
jang-hs:fix/hybrid-online-store-routing-tag
Open

fix: keep HybridOnlineStore routing_tag across FeatureViews#6769
jang-hs wants to merge 1 commit into
feast-dev:masterfrom
jang-hs:fix/hybrid-online-store-routing-tag

Conversation

@jang-hs

@jang-hs jang-hs commented Aug 21, 2026

Copy link
Copy Markdown

Fixes #6768

What this fixes

HybridOnlineStore.update() applies the first FeatureView and then fails on the second, no matter how the views are tagged:

ValueError: FeatureView must have a 'tribe' tag to use HybridOnlineStore.

The message reports 'tribe' even when the repo config sets a custom routing_tag, because that string is the fallback in

tag_name = getattr(config.online_store, "routing_tag", "tribe")

Getting the fallback means config.online_store is no longer the HybridOnlineStoreConfig by the time the second view is read. In practice this makes the store unusable for any repo with more than one FeatureView.

Root cause

1. _prepare_repo_conf mutated the caller's RepoConfig. rconfig = config is an alias, not a copy, so rconfig.online_config = ... and the data = rconfig.__dict__ writes all landed on the caller's object — replacing online_store with the selected backend's config. It also injected type into online_store.conf, which belongs to the user's config and is read again on the next lookup.

2. update() and teardown() rebound the config parameter inside their loops. Even with (1) fixed, the next iteration would receive the narrowed config instead of the hybrid one.

online_write_batch() and online_read() rebind as well, but those are single-use per call; fixing (1) is what makes them safe, so they are left as they are to keep the diff small.

The change

  • _prepare_repo_conf builds its result from dict(config.__dict__) and constructs a new dict for the backend conf instead of injecting into the caller's. The returned kwargs are identical to what the mutating version produced — online_config and online_store are both set to the selected backend's conf, exactly as before — so nothing downstream changes.
  • update() and teardown() assign the per-backend RepoConfig to a local store_config rather than rebinding config.

Tests

New unit test at sdk/python/tests/unit/infra/online_store/test_hybrid_online_store.py:

  • test_update_routes_every_feature_view — two views with a custom routing_tag, one per backend; asserts both backends' update() are called once and the caller's routing_tag survives.
  • test_prepare_repo_conf_does_not_mutate_caller_config — asserts the caller's online_store object and its conf dict are untouched.

Both fail on master and pass with this change:

# master
E   ValueError: FeatureView must have a 'tribe' tag to use HybridOnlineStore.
E   AssertionError: assert {'redis_type'...ype': 'redis'} == {'redis_type'...calhost:6379'}
2 failed

# with this change
2 passed

ruff check, ruff format --check, and mypy are clean on both files (the mypy errors that remain in the tree are pre-existing and in other modules).

The existing sdk/python/tests/integration/online_store/test_hybrid_online_store.py covers the write/read path; I could not run it locally as it needs testcontainers/Docker, but this change does not alter the config those paths receive.

HybridOnlineStore.update() applied the first FeatureView and then failed on
the second with

  ValueError: FeatureView must have a 'tribe' tag to use HybridOnlineStore

no matter how the views were tagged. The message reports 'tribe' — the
fallback, not the configured routing_tag — because config.online_store is no
longer the HybridOnlineStoreConfig by the time the second view is read.

Two causes:

1. _prepare_repo_conf mutated the caller's RepoConfig. `rconfig = config` is
   an alias, so both the attribute assignment and the __dict__ writes landed
   on the caller's object and replaced online_store with the selected
   backend's config. It also injected `type` into online_store.conf, which
   belongs to the user's config.

2. update() and teardown() rebound the `config` parameter inside their loops,
   so the next iteration read routing_tag off the narrowed config even once
   the mutation was gone.

Build the returned mapping from a copy, construct a new dict for the backend
conf, and assign the per-backend RepoConfig to a local name in the loops.
Behaviour is otherwise unchanged: the returned kwargs are identical to what
the mutating version produced.

Adds a unit regression test covering both the multi-FeatureView routing and
the caller's config staying intact. Both fail on master.

Signed-off-by: Jade <retrorca@gmail.com>
@jang-hs
jang-hs requested a review from a team as a code owner August 21, 2026 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HybridOnlineStore loses routing_tag after the first FeatureView, so feast apply fails

1 participant