Skip to content

Commit dbcb9bf

Browse files
Draft changes to add remote online store to feast.
Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> Adding the integration test and remote online creator class so that it will fit into existing integration testing framework. Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> Fix after rebase Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> Removing the RemoteOnlineStoreCreator and adding custom integration test case. Incorporating the code review comments. Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> reformatting the code, removing unnecessary braces. Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> Trying to fix the errors reported in make lint-python Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> Ran the command make format-python and trying to see if it fixes the lint errors. Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> increasing the server start timeout to see if it fixes the integration test cases. Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> checking changes after make format-python Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> trying to see if this fixes the PR integrationt test failure. Signed-off-by: Lokesh Rangineni <lokeshemail@email.com> Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> checking in the changes for make format-python Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> Upgrading python version to 3.11, adding support for 3.11 as well. Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> chore: Bump macOS runners to macos-13 (feast-dev#4152) bump macos runner to 13 Signed-off-by: tokoko <togurg14@freeuni.edu.ge> Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> chore: Use pixi to lock python dependencies in a single command (feast-dev#4114) use pixi to lock python dependencies in a single command Signed-off-by: tokoko <togurg14@freeuni.edu.ge> Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> feat: List all feature views (feast-dev#4256) * feature: Adding type to base feature view Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * fixed linter Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * fixed type and meta Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * adding new listing Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updated Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * cleaning up changes Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * reverting FV proto Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * doing simple way Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * added a test Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updated to add warnings Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> --------- Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> feat: Adding vector search for sqlite (feast-dev#4176) * feat: Adding vector search for sqlite Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * adding the sqlite_vss dependency Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * linter Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * latest progress Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * uploading latest progress Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updated function Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * adding configuration Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * adding current progress Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updating requirements files Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * moving to sqlite-vec Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updating sqlite.py Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * checking in progress Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updated test type Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * got the initialization working, nice Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * checking in progress from last night Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * removing unnecessary stuff Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * fixing merge conflicts Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * removing files changed accidentally] Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * uploading current progress...things run but need to update the virtual table insertion Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * linted Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * adding working notes Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * found a bug, original feature_store.py was only grabbing first feature view, adjusted Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * cant use a string have to verify it is a proper FeatureView object Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updated got it working, need to fix some other stuff still Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * working Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * linter Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * fixing some type issues Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * fixed typing and lint issues Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updated dependencies Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * fix for pixi and updating requirements Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * fixed type Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * linter Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * testing sqlite_vec import Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * adding minimal example test Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * lint Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * testing raw sqlite Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * Printing package version * printing version Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updated requirements * rebuilding requirments Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * only going to run this on 3.10 for now Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updated docs for sqlite caveats Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * adding reason Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * skipping Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updated tests Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * removing print Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * added method call Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * added prubt Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * added print Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * removing print Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * adding check in sqlite Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * missed an = Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * still running on 3.11 Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * typo Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * fix Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * fix Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * updated setup and docs Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> * renamed things Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> --------- Signed-off-by: Francisco Javier Arceo <farceo@redhat.com> squashing the last 15 commits to one. Merge branch 'master' into feature/adding-remote-onlinestore-rebase Adding documentation and incorporating code review comment. Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> Adding documentation and incorporating code review comment. Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com> Merge remote-tracking branch 'fork/feature/adding-remote-onlinestore-rebase' into feature/adding-remote-onlinestore-rebase Signed-off-by: Lokesh Rangineni <lokeshforjava@gmail.com>
1 parent 53c282f commit dbcb9bf

File tree

13 files changed

+444
-20
lines changed

13 files changed

+444
-20
lines changed

.github/workflows/pr_integration_tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
strategy:
2424
fail-fast: false
2525
matrix:
26-
python-version: [ "3.9", "3.10", "3.11" ]
26+
python-version: [ "3.11" ]
2727
os: [ ubuntu-latest ]
2828
env:
2929
OS: ${{ matrix.os }}

Makefile

-6
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,6 @@ lock-python-dependencies-all:
7575
pixi run --environment py311 --manifest-path infra/scripts/pixi/pixi.toml "uv pip compile --system --no-strip-extras setup.py --output-file sdk/python/requirements/py3.11-requirements.txt"
7676
pixi run --environment py311 --manifest-path infra/scripts/pixi/pixi.toml "uv pip compile --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.11-ci-requirements.txt"
7777

78-
lock-python-dependencies-all:
79-
pixi run --environment py39 --manifest-path infra/scripts/pixi/pixi.toml "python -m piptools compile -U --output-file sdk/python/requirements/py3.9-requirements.txt"
80-
pixi run --environment py39 --manifest-path infra/scripts/pixi/pixi.toml "python -m piptools compile -U --extra ci --output-file sdk/python/requirements/py3.9-ci-requirements.txt"
81-
pixi run --environment py310 --manifest-path infra/scripts/pixi/pixi.toml "python -m piptools compile -U --output-file sdk/python/requirements/py3.10-requirements.txt"
82-
pixi run --environment py310 --manifest-path infra/scripts/pixi/pixi.toml "python -m piptools compile -U --extra ci --output-file sdk/python/requirements/py3.10-ci-requirements.txt"
83-
8478
benchmark-python:
8579
IS_TEST=True python -m pytest --integration --benchmark --benchmark-autosave --benchmark-save-data sdk/python/tests
8680

docs/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
* [Datastore](reference/online-stores/datastore.md)
9797
* [DynamoDB](reference/online-stores/dynamodb.md)
9898
* [Bigtable](reference/online-stores/bigtable.md)
99+
* [Remote](reference/online-stores/remote.md)
99100
* [PostgreSQL (contrib)](reference/online-stores/postgres.md)
100101
* [Cassandra + Astra DB (contrib)](reference/online-stores/cassandra.md)
101102
* [MySQL (contrib)](reference/online-stores/mysql.md)

docs/reference/online-stores/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,7 @@ Please see [Online Store](../../getting-started/architecture-and-components/onli
6161
{% content-ref url="scylladb.md" %}
6262
[scylladb.md](scylladb.md)
6363
{% endcontent-ref %}
64+
65+
{% content-ref url="remote.md" %}
66+
[remote.md](remote.md)
67+
{% endcontent-ref %}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Remote online store
2+
3+
## Description
4+
5+
This remote online store will let you interact with remote feature server. At this moment this only supports the read operation. You can use this online store and able retrieve online features `store.get_online_features` from remote feature server.
6+
7+
## Examples
8+
9+
The registry is pointing to registry of remote feature store. If it is not accessible then should be configured to use remote registry.
10+
11+
{% code title="feature_store.yaml" %}
12+
```yaml
13+
project: my-local-project
14+
registry: /remote/data/registry.db
15+
provider: local
16+
online_store:
17+
path: http://localhost:6566
18+
type: remote
19+
entity_key_serialization_version: 2
20+
```
21+
{% endcode %}

infra/scripts/pixi/pixi.toml

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ platforms = ["linux-64", "osx-arm64"]
66
[tasks]
77

88
[dependencies]
9-
pip-tools = ">=7.4.1,<7.5"
9+
uv = ">=0.1.39,<0.2"
1010

1111
[feature.py39.dependencies]
1212
python = "~=3.9.0"
1313

1414
[feature.py310.dependencies]
1515
python = "~=3.10.0"
1616

17+
[feature.py311.dependencies]
18+
python = "~=3.11.0"
19+
1720
[environments]
1821
py39 = ["py39"]
19-
py310 = ["py310"]
22+
py310 = ["py310"]
23+
py311 = ["py311"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# Copyright 2021 The Feast Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import json
15+
import logging
16+
from datetime import datetime
17+
from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple
18+
19+
import requests
20+
from pydantic import StrictStr
21+
22+
from feast import Entity, FeatureView, RepoConfig
23+
from feast.infra.online_stores.online_store import OnlineStore
24+
from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto
25+
from feast.protos.feast.types.Value_pb2 import Value as ValueProto
26+
from feast.repo_config import FeastConfigBaseModel
27+
from feast.type_map import python_values_to_proto_values
28+
from feast.value_type import ValueType
29+
30+
logger = logging.getLogger(__name__)
31+
32+
33+
class RemoteOnlineStoreConfig(FeastConfigBaseModel):
34+
"""Remote Online store config for remote online store"""
35+
36+
type: Literal["remote"] = "remote"
37+
"""Online store type selector"""
38+
39+
path: StrictStr = "http://localhost:6566"
40+
""" str: Path to metadata store.
41+
If type is 'remote', then this is a URL for registry server """
42+
43+
44+
class RemoteOnlineStore(OnlineStore):
45+
"""
46+
remote online store implementation wrapper to communicate with feast online server.
47+
"""
48+
49+
def online_write_batch(
50+
self,
51+
config: RepoConfig,
52+
table: FeatureView,
53+
data: List[
54+
Tuple[EntityKeyProto, Dict[str, ValueProto], datetime, Optional[datetime]]
55+
],
56+
progress: Optional[Callable[[int], Any]],
57+
) -> None:
58+
pass
59+
60+
def online_read(
61+
self,
62+
config: RepoConfig,
63+
table: FeatureView,
64+
entity_keys: List[EntityKeyProto],
65+
requested_features: Optional[List[str]] = None,
66+
) -> List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]]:
67+
assert isinstance(config.online_store, RemoteOnlineStoreConfig)
68+
config.online_store.__class__ = RemoteOnlineStoreConfig
69+
70+
req_body = self._construct_online_read_api_json_request(
71+
entity_keys, table, requested_features
72+
)
73+
response = requests.post(
74+
f"{config.online_store.path}/get-online-features", data=req_body
75+
)
76+
if response.status_code == 200:
77+
logger.debug("Able to retrieve the online features from feature server.")
78+
response_json = json.loads(response.text)
79+
event_ts = self._get_event_ts(response_json)
80+
# Iterating over results and converting the API results in column format to row format.
81+
result_tuples: List[
82+
Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]
83+
] = []
84+
for feature_value_index in range(len(entity_keys)):
85+
feature_values_dict: Dict[str, ValueProto] = dict()
86+
for index, feature_name in enumerate(
87+
response_json["metadata"]["feature_names"]
88+
):
89+
if (
90+
requested_features is not None
91+
and feature_name in requested_features
92+
):
93+
if (
94+
response_json["results"][index]["statuses"][
95+
feature_value_index
96+
]
97+
== "PRESENT"
98+
):
99+
message = python_values_to_proto_values(
100+
[
101+
response_json["results"][index]["values"][
102+
feature_value_index
103+
]
104+
],
105+
ValueType.UNKNOWN,
106+
)
107+
feature_values_dict[feature_name] = message[0]
108+
else:
109+
feature_values_dict[feature_name] = ValueProto()
110+
111+
result_tuples.append((event_ts, feature_values_dict))
112+
return result_tuples
113+
else:
114+
error_msg = f"Unable to retrieve the online store data using feature server API. Error_code={response.status_code}, error_message={response.reason}"
115+
logger.error(error_msg)
116+
raise RuntimeError(error_msg)
117+
118+
def _construct_online_read_api_json_request(
119+
self,
120+
entity_keys: List[EntityKeyProto],
121+
table: FeatureView,
122+
requested_features: Optional[List[str]] = None,
123+
):
124+
api_requested_features = []
125+
if requested_features is not None:
126+
for requested_feature in requested_features:
127+
api_requested_features.append(f"{table.name}:{requested_feature}")
128+
129+
entity_values = []
130+
entity_key = ""
131+
for row in entity_keys:
132+
entity_key = row.join_keys[0]
133+
entity_values.append(
134+
getattr(row.entity_values[0], row.entity_values[0].WhichOneof("val"))
135+
)
136+
137+
req_body = json.dumps(
138+
{
139+
"features": api_requested_features,
140+
"entities": {entity_key: entity_values},
141+
}
142+
)
143+
return req_body
144+
145+
def _check_if_feature_requested(self, feature_name, requested_features):
146+
for requested_feature in requested_features:
147+
if feature_name in requested_feature:
148+
return True
149+
return False
150+
151+
def _get_event_ts(self, response_json) -> datetime:
152+
event_ts = ""
153+
if len(response_json["results"]) > 1:
154+
event_ts = response_json["results"][1]["event_timestamps"][0]
155+
return datetime.fromisoformat(event_ts.replace("Z", "+00:00"))
156+
157+
def update(
158+
self,
159+
config: RepoConfig,
160+
tables_to_delete: Sequence[FeatureView],
161+
tables_to_keep: Sequence[FeatureView],
162+
entities_to_delete: Sequence[Entity],
163+
entities_to_keep: Sequence[Entity],
164+
partial: bool,
165+
):
166+
pass
167+
168+
def teardown(
169+
self,
170+
config: RepoConfig,
171+
tables: Sequence[FeatureView],
172+
entities: Sequence[Entity],
173+
):
174+
pass

sdk/python/feast/repo_config.py

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"hazelcast": "feast.infra.online_stores.contrib.hazelcast_online_store.hazelcast_online_store.HazelcastOnlineStore",
6565
"ikv": "feast.infra.online_stores.contrib.ikv_online_store.ikv.IKVOnlineStore",
6666
"elasticsearch": "feast.infra.online_stores.contrib.elasticsearch.ElasticSearchOnlineStore",
67+
"remote": "feast.infra.online_stores.remote.RemoteOnlineStore",
6768
}
6869

6970
OFFLINE_STORE_CLASS_FOR_TYPE = {

sdk/python/tests/conftest.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
create_basic_driver_dataset,
3333
create_document_dataset,
3434
)
35-
from tests.integration.feature_repos.integration_test_repo_config import ( # noqa: E402
36-
IntegrationTestRepoConfig,
35+
from tests.integration.feature_repos.integration_test_repo_config import (
36+
IntegrationTestRepoConfig, # noqa: E402
3737
)
3838
from tests.integration.feature_repos.repo_configuration import ( # noqa: E402
3939
AVAILABLE_OFFLINE_STORES,
@@ -45,8 +45,8 @@
4545
construct_universal_feature_views,
4646
construct_universal_test_data,
4747
)
48-
from tests.integration.feature_repos.universal.data_sources.file import ( # noqa: E402
49-
FileDataSourceCreator,
48+
from tests.integration.feature_repos.universal.data_sources.file import (
49+
FileDataSourceCreator, # noqa: E402
5050
)
5151
from tests.integration.feature_repos.universal.entities import ( # noqa: E402
5252
customer,
@@ -173,7 +173,15 @@ def simple_dataset_2() -> pd.DataFrame:
173173

174174
def start_test_local_server(repo_path: str, port: int):
175175
fs = FeatureStore(repo_path)
176-
fs.serve("localhost", port, no_access_log=True)
176+
fs.serve(
177+
host="localhost",
178+
port=port,
179+
no_access_log=True,
180+
keep_alive_timeout=30,
181+
type_="http",
182+
workers=1,
183+
registry_ttl_sec=30,
184+
)
177185

178186

179187
@pytest.fixture

sdk/python/tests/integration/feature_repos/repo_configuration.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,7 @@
136136

137137
AVAILABLE_ONLINE_STORES: Dict[
138138
str, Tuple[Union[str, Dict[Any, Any]], Optional[Type[OnlineStoreCreator]]]
139-
] = {
140-
"sqlite": ({"type": "sqlite"}, None),
141-
}
139+
] = {"sqlite": ({"type": "sqlite"}, None)}
142140

143141
# Only configure Cloud DWH if running full integration tests
144142
if os.getenv("FEAST_IS_LOCAL_TEST", "False") != "True":
@@ -155,7 +153,6 @@
155153
AVAILABLE_ONLINE_STORES["datastore"] = ("datastore", None)
156154
AVAILABLE_ONLINE_STORES["snowflake"] = (SNOWFLAKE_CONFIG, None)
157155
AVAILABLE_ONLINE_STORES["bigtable"] = (BIGTABLE_CONFIG, None)
158-
159156
# Uncomment to test using private Rockset account. Currently not enabled as
160157
# there is no dedicated Rockset instance for CI testing and there is no
161158
# containerized version of Rockset.
@@ -489,7 +486,6 @@ def construct_test_environment(
489486
"arn:aws:iam::402087665549:role/lambda_execution_role",
490487
),
491488
)
492-
493489
else:
494490
feature_server = LocalFeatureServerConfig(
495491
feature_logging=FeatureLoggingConfig(enabled=True)

0 commit comments

Comments
 (0)