From 221e01e8022ad843ca83946e65b6facfe55815f3 Mon Sep 17 00:00:00 2001 From: Oleg Avdeev Date: Tue, 9 Mar 2021 09:47:20 -0800 Subject: [PATCH 1/6] local read-write path Signed-off-by: Oleg Avdeev --- docs/specs/datastore_online_example.monopic | Bin 0 -> 1961 bytes docs/specs/datastore_online_example.png | Bin 0 -> 108753 bytes docs/specs/online_store_format.md | 24 ++-- sdk/python/feast/feature_store.py | 37 ++++-- sdk/python/feast/infra/gcp.py | 109 ++++++++++++++++-- sdk/python/feast/infra/key_encoding_utils.py | 48 ++++++++ sdk/python/feast/infra/local_sqlite.py | 81 ++++++++++++- sdk/python/feast/infra/provider.py | 43 ++++++- sdk/python/setup.py | 1 + .../tests/cli/online_read_write_test.py | 89 ++++++++++++++ sdk/python/tests/cli/test_cli_local.py | 3 + sdk/python/tests/cli/test_datastore.py | 3 + 12 files changed, 403 insertions(+), 35 deletions(-) create mode 100644 docs/specs/datastore_online_example.monopic create mode 100644 docs/specs/datastore_online_example.png create mode 100644 sdk/python/feast/infra/key_encoding_utils.py create mode 100644 sdk/python/tests/cli/online_read_write_test.py diff --git a/docs/specs/datastore_online_example.monopic b/docs/specs/datastore_online_example.monopic new file mode 100644 index 0000000000000000000000000000000000000000..c01354983e62a04f01a93801f7d29bdf1c891a66 GIT binary patch literal 1961 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 R|9-;&1fH&bF6*2UngEiV&L98) literal 0 HcmV?d00001 diff --git a/docs/specs/online_store_format.md b/docs/specs/online_store_format.md index 8f27503e6c..97d508fb6e 100644 --- a/docs/specs/online_store_format.md +++ b/docs/specs/online_store_format.md @@ -59,20 +59,20 @@ Here's an example of how the entire thing looks like: However, we'll address this issue in future versions of the protocol. -## Cloud Firestore Online Store Format +## Google Datastore Online Store Format -[Firebase data model](https://firebase.google.com/docs/firestore/data-model) is a hierarchy of documents that can contain (sub)-collections. This structure can be multiple levels deep; documents and subcollections are alternating in this hierarchy. +[Datastore data model](https://cloud.google.com/datastore/docs/concepts/entities) is a collection of documents called Entities (not to be confused with Feast Entities). Documents can be organized in a hierarchy using Kinds. -We use the following structure to store feature data in the Firestore: -* at the first level, there is a collection for each Feast project -* second level, in each project-collection, there is a Firebase document for each Feature Table -* third level, in the document for the Feature Table, there is a subcollection called `values` that contain a document per feature row. That document contains the following fields: - * `key` contains entity key as serialized `feast.types.EntityKey` proto - * `values` contains feature name to value map, values serialized as `feast.types.Value` proto - * `event_ts` contains event timestamp (in the native firestore timestamp format) - * `created_ts` contains write timestamp (in the native firestore timestamp format) +We use the following structure to store feature data in Datastore: +* there is a Datastore Entity for each Feast Project, with Kind `Project` +* under that Datastore Entity, there is a Datastore Entity for each Feast Feature Table or View, with Kind `Table`. That contains one additional field, `created_ts` that contains the timestamp when this Datastore Entity was created. +* under that Datastore Entity, there is a Datastore Entity for each Feast Entity Key with Kind `Row`. That contains the following fields: + * `key` contains entity key as serialized `feast.types.EntityKey` proto + * `values` contains feature name to value map, values serialized as `feast.types.Value` proto + * `event_ts` contains event timestamp (in the datastore timestamp format) + * `created_ts` contains write timestamp (in the datastore timestamp format) -Document id for the feature document is computed by hashing entity key using murmurhash3_128 algorithm as follows: +The id for the `Row` Datastore Entity is computed by hashing entity key using murmurhash3_128 algorithm as follows: 1. hash entity names, sorted in alphanumeric order, by serializing them to bytes using the Value Serialization steps below 2. hash the entity values in the same order as corresponding entity names, by serializing them to bytes using the Value Serialization steps below @@ -90,7 +90,7 @@ Other types of entity keys are not supported in this version of the specificatio **Example:** -![Firestore Online Example](firebase_online_example.png) +![Datastore Online Example](datastore_online_example.png) # Appendix diff --git a/sdk/python/feast/feature_store.py b/sdk/python/feast/feature_store.py index 0f76c6eeef..62aed28b99 100644 --- a/sdk/python/feast/feature_store.py +++ b/sdk/python/feast/feature_store.py @@ -11,9 +11,17 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from pathlib import Path from typing import Optional -from feast.repo_config import RepoConfig, load_repo_config +from feast.infra.provider import Provider, get_provider +from feast.registry import Registry +from feast.repo_config import ( + LocalOnlineStoreConfig, + OnlineStoreConfig, + RepoConfig, + load_repo_config, +) class FeatureStore: @@ -21,14 +29,29 @@ class FeatureStore: A FeatureStore object is used to define, create, and retrieve features. """ + config: RepoConfig + def __init__( - self, config_path: Optional[str], config: Optional[RepoConfig], + self, repo_path: Optional[str], config: Optional[RepoConfig], ): - if config_path is None or config is None: - raise Exception("You cannot specify both config_path and config") + if repo_path is not None and config is not None: + raise Exception("You cannot specify both repo_path and config") if config is not None: self.config = config - elif config_path is not None: - self.config = load_repo_config(config_path) + elif repo_path is not None: + self.config = load_repo_config(Path(repo_path)) else: - self.config = RepoConfig() + self.config = RepoConfig( + metadata_store="./metadata.db", + project="default", + provider="local", + online_store=OnlineStoreConfig( + local=LocalOnlineStoreConfig("online_store.db") + ), + ) + + def _get_provider(self) -> Provider: + return get_provider(self.config) + + def _get_registry(self) -> Registry: + return Registry(self.config.metadata_store) diff --git a/sdk/python/feast/infra/gcp.py b/sdk/python/feast/infra/gcp.py index 8c3882bb1e..70a78ee814 100644 --- a/sdk/python/feast/infra/gcp.py +++ b/sdk/python/feast/infra/gcp.py @@ -1,9 +1,16 @@ from datetime import datetime -from typing import List, Optional +from typing import Dict, List, Optional, Tuple + +import mmh3 +from pytz import utc from feast import FeatureTable from feast.infra.provider import Provider from feast.repo_config import DatastoreOnlineStoreConfig +from feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto +from feast.types.Value_pb2 import Value as ValueProto + +from .key_encoding_utils import serialize_entity_key def _delete_all_values(client, key) -> None: @@ -11,7 +18,7 @@ def _delete_all_values(client, key) -> None: Delete all data under the key path in datastore. """ while True: - query = client.query(kind="Value", ancestor=key) + query = client.query(kind="Row", ancestor=key) entities = list(query.fetch(limit=1000)) if not entities: return @@ -21,19 +28,37 @@ def _delete_all_values(client, key) -> None: client.delete(entity.key) +def compute_datastore_entity_id(entity_key: EntityKeyProto) -> str: + """ + Compute Datastore Entity id given Feast Entity Key. + + Remember that Datastore Entity is a concept from the Datastore data model, that has nothing to + do with the Entity concept we have in Feast. + """ + return mmh3.hash_bytes(serialize_entity_key(entity_key)).hex() + + +def _make_tzaware(t: datetime): + """ We assume tz-naive datetimes are UTC """ + if t.tzinfo is None: + return t.replace(tzinfo=utc) + else: + return t + + class Gcp(Provider): - _project_id: Optional[str] + _gcp_project_id: Optional[str] def __init__(self, config: Optional[DatastoreOnlineStoreConfig]): if config: - self._project_id = config.project_id + self._gcp_project_id = config.project_id else: - self._project_id = None + self._gcp_project_id = None def _initialize_client(self): from google.cloud import datastore - if self._project_id is not None: + if self._gcp_project_id is not None: return datastore.Client(self.project_id) else: return datastore.Client() @@ -49,18 +74,18 @@ def update_infra( client = self._initialize_client() for table in tables_to_keep: - key = client.key("FeastProject", project, "FeatureTable", table.name) + key = client.key("Project", project, "Table", table.name) entity = datastore.Entity(key=key) - entity.update({"created_at": datetime.utcnow()}) + entity.update({"created_ts": datetime.utcnow()}) client.put(entity) for table in tables_to_delete: _delete_all_values( - client, client.key("FeastProject", project, "FeatureTable", table.name) + client, client.key("Project", project, "Table", table.name) ) # Delete the table metadata datastore entity - key = client.key("FeastProject", project, "FeatureTable", table.name) + key = client.key("Project", project, "Table", table.name) client.delete(key) def teardown_infra(self, project: str, tables: List[FeatureTable]) -> None: @@ -68,9 +93,69 @@ def teardown_infra(self, project: str, tables: List[FeatureTable]) -> None: for table in tables: _delete_all_values( - client, client.key("FeastProject", project, "FeatureTable", table.name) + client, client.key("Project", project, "Table", table.name) ) # Delete the table metadata datastore entity - key = client.key("FeastProject", project, "FeatureTable", table.name) + key = client.key("Project", project, "Table", table.name) client.delete(key) + + def online_write_batch( + self, + project: str, + table: FeatureTable, + data: List[Tuple[EntityKeyProto, Dict[str, ValueProto], datetime]], + created_ts: datetime, + ) -> None: + from google.cloud import datastore + + client = self._initialize_client() + + for entity_key, features, timestamp in data: + document_id = compute_datastore_entity_id(entity_key) + + key = client.key( + "Project", project, "Table", table.name, "Row", document_id, + ) + with client.transaction(): + entity = client.get(key) + if entity is not None: + if entity["event_ts"] > _make_tzaware(timestamp): + # Do not overwrite feature values computed from fresher data + continue + elif entity["event_ts"] == _make_tzaware(timestamp) and entity[ + "created_ts" + ] > _make_tzaware(created_ts): + # Do not overwrite feature values computed from the same data, but + # computed later than this one + continue + else: + entity = datastore.Entity(key=key) + + entity.update( + dict( + key=entity_key.SerializeToString(), + values={k: v.SerializeToString() for k, v in features.items()}, + event_ts=_make_tzaware(timestamp), + created_ts=_make_tzaware(created_ts), + ) + ) + client.put(entity) + + def online_read( + self, project: str, table: FeatureTable, entity_key: EntityKeyProto + ) -> Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]: + client = self._initialize_client() + + document_id = compute_datastore_entity_id(entity_key) + key = client.key("Project", project, "Table", table.name, "Row", document_id) + value = client.get(key) + if value is not None: + res = {} + for feature_name, value_bin in value["values"].items(): + val = ValueProto() + val.ParseFromString(value_bin) + res[feature_name] = val + return value["event_ts"], res + else: + return None, None diff --git a/sdk/python/feast/infra/key_encoding_utils.py b/sdk/python/feast/infra/key_encoding_utils.py new file mode 100644 index 0000000000..5fc9f118df --- /dev/null +++ b/sdk/python/feast/infra/key_encoding_utils.py @@ -0,0 +1,48 @@ +import struct +from typing import List, Tuple + +from feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto +from feast.types.Value_pb2 import Value as ValueProto +from feast.types.Value_pb2 import ValueType + + +def _serialize_val(value_type, v: ValueProto) -> Tuple[bytes, int]: + if value_type == "string_val": + return v.string_val.encode("utf8"), ValueType.STRING + elif value_type == "bytes_val": + return v.bytes_val, ValueType.BYTES + elif value_type == "int32_val": + return struct.pack(" bytes: + """ + Serialize entity key to a bytestring so it can be used as a lookup key in a hash table. + + We need this encoding to be stable; therefore we cannot just use protobuf serialization + here since it does not guarantee that two proto messages containing the same data will + serialize to the same byte string[1]. + + [1] https://developers.google.com/protocol-buffers/docs/encoding + """ + sorted_keys, sorted_values = zip( + *sorted(zip(entity_key.entity_names, entity_key.entity_values)) + ) + + output: List[bytes] = [] + for k in sorted_keys: + output.append(struct.pack(" str: @@ -17,16 +21,24 @@ class LocalSqlite(Provider): def __init__(self, config: LocalOnlineStoreConfig): self._db_path = config.path + def _get_conn(self): + return sqlite3.connect( + self._db_path, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES + ) + def update_infra( self, project: str, tables_to_delete: List[FeatureTable], tables_to_keep: List[FeatureTable], ): - conn = sqlite3.connect(self._db_path) + conn = self._get_conn() for table in tables_to_keep: conn.execute( - f"CREATE TABLE IF NOT EXISTS {_table_id(project, table)} (key BLOB, value BLOB)" + f"CREATE TABLE IF NOT EXISTS {_table_id(project, table)} (entity_key BLOB, feature_name TEXT, value BLOB, event_ts timestamp, created_ts timestamp, PRIMARY KEY(entity_key, feature_name))" + ) + conn.execute( + f"CREATE INDEX {_table_id(project, table)}_ek ON {_table_id(project, table)} (entity_key);" ) for table in tables_to_delete: @@ -34,3 +46,66 @@ def update_infra( def teardown_infra(self, project: str, tables: List[FeatureTable]) -> None: os.unlink(self._db_path) + + def online_write_batch( + self, + project: str, + table: FeatureTable, + data: List[Tuple[EntityKeyProto, Dict[str, ValueProto], datetime]], + created_ts: datetime, + ) -> None: + conn = self._get_conn() + with conn: + for entity_key, values, timestamp in data: + for feature_name, val in values.items(): + entity_key_bin = serialize_entity_key(entity_key) + conn.execute( + f"""INSERT INTO {_table_id(project, table)} + (entity_key, feature_name, value, event_ts, created_ts) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT (entity_key, feature_name) DO UPDATE + SET value = ?, event_ts = ?, created_ts = ? + WHERE event_ts < ? OR (event_ts = ? AND created_ts < ?) + """, + ( + # INSERT + entity_key_bin, + feature_name, + val.SerializeToString(), + timestamp, + created_ts, + # SET + val.SerializeToString(), + timestamp, + created_ts, + # WHERE + timestamp, + timestamp, + created_ts, + ), + ) + + def online_read( + self, project: str, table: FeatureTable, entity_key: EntityKeyProto + ) -> Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]: + entity_key_bin = serialize_entity_key(entity_key) + + conn = self._get_conn() + cur = conn.cursor() + cur.execute( + f"SELECT feature_name, value, event_ts FROM {_table_id(project, table)} WHERE entity_key = ?", + (entity_key_bin,), + ) + + res = {} + res_ts = None + for feature_name, val_bin, ts in cur.fetchall(): + val = ValueProto() + val.ParseFromString(val_bin) + res[feature_name] = val + res_ts = ts + + if not res: + return None, None + else: + return res_ts, res diff --git a/sdk/python/feast/infra/provider.py b/sdk/python/feast/infra/provider.py index 52689e9543..4bb2cc383b 100644 --- a/sdk/python/feast/infra/provider.py +++ b/sdk/python/feast/infra/provider.py @@ -1,8 +1,11 @@ import abc -from typing import List +from datetime import datetime +from typing import Dict, List, Optional, Tuple from feast import FeatureTable from feast.repo_config import RepoConfig +from feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto +from feast.types.Value_pb2 import Value as ValueProto class Provider(abc.ABC): @@ -34,6 +37,44 @@ def teardown_infra(self, project: str, tables: List[FeatureTable]): """ ... + @abc.abstractmethod + def online_write_batch( + self, + project: str, + table: FeatureTable, + data: List[Tuple[EntityKeyProto, Dict[str, ValueProto], datetime]], + created_ts: datetime, + ) -> None: + """ + Write a batch of feature rows to the online store. This is a low level interface, not + expected to be used by the users directly. + + If a tz-naive timestamp is passed to this method, it is assumed to be UTC. + + Args: + project: Feast project name + table: Feast FeatureTable + data: a list of triplets containing Feature data. Each triplet contains an Entity Key, + a dict containing feature values, and event timestamp for the row. + created_ts: the created timestamp (typically set to current time), same value used for + all rows. + """ + ... + + @abc.abstractmethod + def online_read( + self, project: str, table: FeatureTable, entity_key: EntityKeyProto + ) -> Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]: + """ + Read feature values given an Entity Key. This is a low level interface, not + expected to be used by the users directly. + + Returns: + A tuple of event_ts for the row, and the feature data as a dict from feature names + to values. Values are returned as Value proto message. + """ + ... + def get_provider(config: RepoConfig) -> Provider: if config.provider == "gcp": diff --git a/sdk/python/setup.py b/sdk/python/setup.py index 7660a7bcdd..a58bc4b8b7 100644 --- a/sdk/python/setup.py +++ b/sdk/python/setup.py @@ -46,6 +46,7 @@ "google", "kubernetes==12.0.*", "bindr", + "mmh3", ] # README file from Feast repo root directory diff --git a/sdk/python/tests/cli/online_read_write_test.py b/sdk/python/tests/cli/online_read_write_test.py new file mode 100644 index 0000000000..a78dc331e1 --- /dev/null +++ b/sdk/python/tests/cli/online_read_write_test.py @@ -0,0 +1,89 @@ +from datetime import datetime, timedelta +from pathlib import Path + +from feast.feature_store import FeatureStore +from feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto +from feast.types.Value_pb2 import Value as ValueProto + + +def basic_rw_test(repo_path: Path, project_name: str) -> None: + """ + This is a provider-independent test suite for reading and writing from the online store, to + be used by provider-specific tests. + """ + store = FeatureStore(repo_path=repo_path, config=None) + registry = store._get_registry() + table = registry.get_feature_table(project=project_name, name="driver_locations") + + provider = store._get_provider() + + entity_key = EntityKeyProto( + entity_names=["driver"], entity_values=[ValueProto(int64_val=1)] + ) + + def _driver_rw_test(event_ts, created_ts, write, expect_read): + """ A helper function to write values and read them back """ + write_lat, write_lon = write + expect_lat, expect_lon = expect_read + provider.online_write_batch( + project=project_name, + table=table, + data=[ + ( + entity_key, + { + "lat": ValueProto(double_val=write_lat), + "lon": ValueProto(string_val=write_lon), + }, + event_ts, + ) + ], + created_ts=created_ts, + ) + + _, val = provider.online_read( + project=project_name, table=table, entity_key=entity_key + ) + assert val["lon"].string_val == expect_lon + assert abs(val["lat"].double_val - expect_lat) < 1e-6 + + """ 1. Basic test: write value, read it back """ + + time_1 = datetime.utcnow() + _driver_rw_test( + event_ts=time_1, created_ts=time_1, write=(1.1, "3.1"), expect_read=(1.1, "3.1") + ) + + """ Values with an older event_ts should not overwrite newer ones """ + time_2 = datetime.utcnow() + _driver_rw_test( + event_ts=time_1 - timedelta(hours=1), + created_ts=time_2, + write=(-1000, "OLD"), + expect_read=(1.1, "3.1"), + ) + + """ Values with an new event_ts should overwrite older ones """ + time_3 = datetime.utcnow() + _driver_rw_test( + event_ts=time_1 + timedelta(hours=1), + created_ts=time_3, + write=(1123, "NEWER"), + expect_read=(1123, "NEWER"), + ) + + """ created_ts is used as a tie breaker, using older created_ts here so no overwrite """ + _driver_rw_test( + event_ts=time_1 + timedelta(hours=1), + created_ts=time_3 - timedelta(hours=1), + write=(54321, "I HAVE AN OLDER created_ts SO I LOSE"), + expect_read=(1123, "NEWER"), + ) + + """ created_ts is used as a tie breaker, using older created_ts here so no overwrite """ + _driver_rw_test( + event_ts=time_1 + timedelta(hours=1), + created_ts=time_3 + timedelta(hours=1), + write=(96864, "I HAVE A NEWER created_ts SO I WIN"), + expect_read=(96864, "I HAVE A NEWER created_ts SO I WIN"), + ) diff --git a/sdk/python/tests/cli/test_cli_local.py b/sdk/python/tests/cli/test_cli_local.py index cb3628b953..ede04bf0b2 100644 --- a/sdk/python/tests/cli/test_cli_local.py +++ b/sdk/python/tests/cli/test_cli_local.py @@ -6,6 +6,7 @@ from typing import List from feast import cli +from tests.cli.online_read_write_test import basic_rw_test class CliRunner: @@ -50,5 +51,7 @@ def test_basic(self) -> None: result = runner.run(["apply", str(repo_path)], cwd=repo_path) assert result.returncode == 0 + basic_rw_test(repo_path, "foo") + result = runner.run(["teardown", str(repo_path)], cwd=repo_path) assert result.returncode == 0 diff --git a/sdk/python/tests/cli/test_datastore.py b/sdk/python/tests/cli/test_datastore.py index c2ef7d350e..4a64f670dc 100644 --- a/sdk/python/tests/cli/test_datastore.py +++ b/sdk/python/tests/cli/test_datastore.py @@ -10,6 +10,7 @@ import pytest from feast import cli +from tests.cli.online_read_write_test import basic_rw_test class CliRunner: @@ -59,5 +60,7 @@ def test_basic(self) -> None: result = runner.run(["apply", str(repo_path)], cwd=repo_path) assert result.returncode == 0 + basic_rw_test(repo_path, project_name=self._project_id) + result = runner.run(["teardown", str(repo_path)], cwd=repo_path) assert result.returncode == 0 From f0c9a6a97d31445660d7e0d6d53a87c8c1488653 Mon Sep 17 00:00:00 2001 From: Oleg Avdeev Date: Wed, 10 Mar 2021 16:34:30 -0800 Subject: [PATCH 2/6] commas Signed-off-by: Oleg Avdeev --- docs/specs/online_store_format.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/specs/online_store_format.md b/docs/specs/online_store_format.md index 97d508fb6e..235e0258a7 100644 --- a/docs/specs/online_store_format.md +++ b/docs/specs/online_store_format.md @@ -64,27 +64,27 @@ However, we'll address this issue in future versions of the protocol. [Datastore data model](https://cloud.google.com/datastore/docs/concepts/entities) is a collection of documents called Entities (not to be confused with Feast Entities). Documents can be organized in a hierarchy using Kinds. We use the following structure to store feature data in Datastore: -* there is a Datastore Entity for each Feast Project, with Kind `Project` -* under that Datastore Entity, there is a Datastore Entity for each Feast Feature Table or View, with Kind `Table`. That contains one additional field, `created_ts` that contains the timestamp when this Datastore Entity was created. -* under that Datastore Entity, there is a Datastore Entity for each Feast Entity Key with Kind `Row`. That contains the following fields: +* There is a Datastore Entity for each Feast Project, with Kind `Project`. +* Under that Datastore Entity, there is a Datastore Entity for each Feast Feature Table or View, with Kind `Table`. That contains one additional field, `created_ts` that contains the timestamp when this Datastore Entity was created. +* Under that Datastore Entity, there is a Datastore Entity for each Feast Entity Key with Kind `Row`. That contains the following fields: * `key` contains entity key as serialized `feast.types.EntityKey` proto * `values` contains feature name to value map, values serialized as `feast.types.Value` proto * `event_ts` contains event timestamp (in the datastore timestamp format) - * `created_ts` contains write timestamp (in the datastore timestamp format) + * `created_ts` contains write timestamp (in the datastore timestamp format). The id for the `Row` Datastore Entity is computed by hashing entity key using murmurhash3_128 algorithm as follows: -1. hash entity names, sorted in alphanumeric order, by serializing them to bytes using the Value Serialization steps below -2. hash the entity values in the same order as corresponding entity names, by serializing them to bytes using the Value Serialization steps below +1. Hash entity names, sorted in alphanumeric order, by serializing them to bytes using the Value Serialization steps below. +2. Hash the entity values in the same order as corresponding entity names, by serializing them to bytes using the Value Serialization steps below. Value Serialization: * Store the type of the value (ValueType enum) as little-endian uint32. -* Store the byte length of the serialized value as little-endian uint32 +* Store the byte length of the serialized value as little-endian uint32. * Store the serialized value as bytes: - binary values are serialized as is - string values serialized as utf8 string - int64 and int32 hashed as little-endian byte representation (8 and 4 bytes respectively) - - bool hashed as 0 or 1 byte + - bool hashed as 0 or 1 byte. Other types of entity keys are not supported in this version of the specification, when using Cloud Firestore. From d196a5ea675e756bcd31afc29a8e542eb9cc2872 Mon Sep 17 00:00:00 2001 From: Oleg Avdeev Date: Wed, 10 Mar 2021 18:50:13 -0800 Subject: [PATCH 3/6] bump python version Signed-off-by: Oleg Avdeev --- infra/docker/ci/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/docker/ci/Dockerfile b/infra/docker/ci/Dockerfile index b4f4504b5e..670e9c5ffe 100644 --- a/infra/docker/ci/Dockerfile +++ b/infra/docker/ci/Dockerfile @@ -29,7 +29,7 @@ ENV MAVEN_HOME /usr/share/maven ENV MAVEN_CONFIG "/root/.m2" # Install Make and Python -ENV PYTHON_VERSION 3.6 +ENV PYTHON_VERSION 3.7 RUN apt-get install -y build-essential curl python${PYTHON_VERSION} \ python${PYTHON_VERSION}-dev python${PYTHON_VERSION}-distutils && \ From 2820f4dd427ed252fb86e53145d733cb90e5acc1 Mon Sep 17 00:00:00 2001 From: Oleg Avdeev Date: Thu, 11 Mar 2021 09:17:15 -0800 Subject: [PATCH 4/6] fix dockerfile Signed-off-by: Oleg Avdeev --- infra/docker/ci/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/docker/ci/Dockerfile b/infra/docker/ci/Dockerfile index 670e9c5ffe..77bcb8bf8e 100644 --- a/infra/docker/ci/Dockerfile +++ b/infra/docker/ci/Dockerfile @@ -77,7 +77,7 @@ RUN PROTOC_ZIP=protoc-${PROTOC_VERSION}-linux-x86_64.zip && \ RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash # Install kubectl -RUN apt-get install -y kubectl=1.20.2-00 +RUN apt-get install -y kubectl=1.20.4-00 # Install helm RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 && \ From c99f12ff8db3f08c19ea69607396e6b35219f6dc Mon Sep 17 00:00:00 2001 From: Oleg Avdeev Date: Thu, 11 Mar 2021 10:48:32 -0800 Subject: [PATCH 5/6] fixing tests Signed-off-by: Oleg Avdeev --- sdk/python/feast/infra/local_sqlite.py | 7 +++++++ sdk/python/feast/type_map.py | 2 +- sdk/python/requirements-ci.txt | 1 + sdk/python/tests/dataframes.py | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/sdk/python/feast/infra/local_sqlite.py b/sdk/python/feast/infra/local_sqlite.py index e4c613d175..11c0280f57 100644 --- a/sdk/python/feast/infra/local_sqlite.py +++ b/sdk/python/feast/infra/local_sqlite.py @@ -1,5 +1,6 @@ import os import sqlite3 +import sys from datetime import datetime from typing import Dict, List, Optional, Tuple @@ -20,6 +21,11 @@ class LocalSqlite(Provider): def __init__(self, config: LocalOnlineStoreConfig): self._db_path = config.path + sqlite_ver = tuple(map(int, sqlite3.sqlite_version.split("."))) + if not sqlite_ver >= (3, 24, 0): + raise Exception( + f"SQLite version >= 3.24.0 reqiured (got {sqlite3.sqlite_version}, Python {sys.version}" + ) def _get_conn(self): return sqlite3.connect( @@ -55,6 +61,7 @@ def online_write_batch( created_ts: datetime, ) -> None: conn = self._get_conn() + with conn: for entity_key, values, timestamp in data: for feature_name, val in values.items(): diff --git a/sdk/python/feast/type_map.py b/sdk/python/feast/type_map.py index 611e50dfb2..fe7622bb9d 100644 --- a/sdk/python/feast/type_map.py +++ b/sdk/python/feast/type_map.py @@ -183,7 +183,7 @@ def _pd_datetime_to_timestamp_proto(dtype, value) -> Timestamp: if dtype.__str__() == "datetime64[ns, UTC]": return Timestamp(seconds=int(value.timestamp())) else: - return Timestamp(seconds=np.datetime64(value).astype("int64") // 1000000) + return Timestamp(seconds=np.datetime64(value).astype("int64") // 1000000) # type: ignore def _type_err(item, dtype): diff --git a/sdk/python/requirements-ci.txt b/sdk/python/requirements-ci.txt index 5049778cb2..51524ec50b 100644 --- a/sdk/python/requirements-ci.txt +++ b/sdk/python/requirements-ci.txt @@ -18,6 +18,7 @@ pytest==6.0.0 pytest-lazy-fixture==0.6.3 pytest-timeout==1.4.2 pytest-ordering==0.6.* +numpy==1.19 pytest-mock==1.10.4 PyYAML==5.3.1 great-expectations==0.13.2 diff --git a/sdk/python/tests/dataframes.py b/sdk/python/tests/dataframes.py index b07a557c6c..b5ca91e207 100644 --- a/sdk/python/tests/dataframes.py +++ b/sdk/python/tests/dataframes.py @@ -81,7 +81,7 @@ "user_id": [1001, 1002, 1004], "int32_feature": [np.int32(1), np.int32(2), np.int32(3)], "int64_feature": [np.int64(1), np.int64(2), np.int64(3)], - "float_feature": [np.float(0.1), np.float(0.2), np.float(0.3)], + "float_feature": [np.float32(0.1), np.float32(0.2), np.float32(0.3)], "double_feature": [np.float64(0.1), np.float64(0.2), np.float64(0.3)], "string_feature": ["one", "two", "three"], "bytes_feature": [b"one", b"two", b"three"], From c2ee4684d995f2a45b0be95b6e128140b7493cc6 Mon Sep 17 00:00:00 2001 From: Oleg Avdeev Date: Thu, 11 Mar 2021 15:44:43 -0800 Subject: [PATCH 6/6] don't use fancy sqlite upsert as it is not in python 3.7.5 Signed-off-by: Oleg Avdeev --- sdk/python/feast/infra/local_sqlite.py | 37 ++++++++++++++------------ sdk/python/requirements-ci.txt | 2 +- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/sdk/python/feast/infra/local_sqlite.py b/sdk/python/feast/infra/local_sqlite.py index 11c0280f57..6f4dbaeba8 100644 --- a/sdk/python/feast/infra/local_sqlite.py +++ b/sdk/python/feast/infra/local_sqlite.py @@ -1,6 +1,5 @@ import os import sqlite3 -import sys from datetime import datetime from typing import Dict, List, Optional, Tuple @@ -21,11 +20,6 @@ class LocalSqlite(Provider): def __init__(self, config: LocalOnlineStoreConfig): self._db_path = config.path - sqlite_ver = tuple(map(int, sqlite3.sqlite_version.split("."))) - if not sqlite_ver >= (3, 24, 0): - raise Exception( - f"SQLite version >= 3.24.0 reqiured (got {sqlite3.sqlite_version}, Python {sys.version}" - ) def _get_conn(self): return sqlite3.connect( @@ -66,21 +60,15 @@ def online_write_batch( for entity_key, values, timestamp in data: for feature_name, val in values.items(): entity_key_bin = serialize_entity_key(entity_key) + conn.execute( - f"""INSERT INTO {_table_id(project, table)} - (entity_key, feature_name, value, event_ts, created_ts) - VALUES (?, ?, ?, ?, ?) - ON CONFLICT (entity_key, feature_name) DO UPDATE + f""" + UPDATE {_table_id(project, table)} SET value = ?, event_ts = ?, created_ts = ? - WHERE event_ts < ? OR (event_ts = ? AND created_ts < ?) + WHERE (event_ts < ? OR (event_ts = ? AND created_ts < ?)) + AND (entity_key = ? AND feature_name = ?) """, ( - # INSERT - entity_key_bin, - feature_name, - val.SerializeToString(), - timestamp, - created_ts, # SET val.SerializeToString(), timestamp, @@ -89,6 +77,21 @@ def online_write_batch( timestamp, timestamp, created_ts, + entity_key_bin, + feature_name, + ), + ) + + conn.execute( + f"""INSERT OR IGNORE INTO {_table_id(project, table)} + (entity_key, feature_name, value, event_ts, created_ts) + VALUES (?, ?, ?, ?, ?)""", + ( + entity_key_bin, + feature_name, + val.SerializeToString(), + timestamp, + created_ts, ), ) diff --git a/sdk/python/requirements-ci.txt b/sdk/python/requirements-ci.txt index 51524ec50b..37cefc1b36 100644 --- a/sdk/python/requirements-ci.txt +++ b/sdk/python/requirements-ci.txt @@ -1,3 +1,4 @@ +numpy==1.19 cryptography==3.3.2 flake8 black==19.10b0 @@ -18,7 +19,6 @@ pytest==6.0.0 pytest-lazy-fixture==0.6.3 pytest-timeout==1.4.2 pytest-ordering==0.6.* -numpy==1.19 pytest-mock==1.10.4 PyYAML==5.3.1 great-expectations==0.13.2