Skip to content

Commit d82dcf2

Browse files
committed
feat: scaffold Aerospike online store
Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>
1 parent abe92af commit d82dcf2

4 files changed

Lines changed: 164 additions & 0 deletions

File tree

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ dependencies = [
4848
]
4949

5050
[project.optional-dependencies]
51+
aerospike = [
52+
"aerospike>=19.0.0,<20.0.0",
53+
]
5154
aws = ["boto3>=1.38.27", "fsspec>=2024.1.0", "aiobotocore>=2"]
5255
azure = [
5356
"azure-storage-blob>=0.37.0",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .aerospike import AerospikeOnlineStore, AerospikeOnlineStoreConfig
2+
3+
__all__ = ["AerospikeOnlineStore", "AerospikeOnlineStoreConfig"]
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
from __future__ import annotations
2+
3+
from datetime import datetime
4+
from logging import getLogger
5+
from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple, Union
6+
7+
from pydantic import SecretStr
8+
9+
try:
10+
import aerospike # noqa: F401
11+
except ImportError as e:
12+
from feast.errors import FeastExtrasDependencyImportError
13+
14+
raise FeastExtrasDependencyImportError("aerospike", str(e))
15+
16+
from feast.entity import Entity
17+
from feast.feature_view import FeatureView
18+
from feast.infra.online_stores.online_store import OnlineStore
19+
from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto
20+
from feast.protos.feast.types.Value_pb2 import Value as ValueProto
21+
from feast.repo_config import FeastConfigBaseModel, RepoConfig
22+
23+
logger = getLogger(__name__)
24+
25+
26+
class AerospikeOnlineStoreConfig(FeastConfigBaseModel):
27+
"""Aerospike configuration.
28+
29+
Aerospike does not have a URI analogue; connections are established via a
30+
seed list of ``(host, port)`` or ``(host, port, tls_name)`` tuples. See the
31+
Aerospike Python client reference for the meaning of additional policies and
32+
TLS options surfaced below, and use ``client_kwargs`` for anything not
33+
explicitly modelled here.
34+
"""
35+
36+
type: Literal["aerospike"] = "aerospike"
37+
"""Online store type selector"""
38+
39+
hosts: List[Union[Tuple[str, int], Tuple[str, int, str]]] = [("localhost", 3000)]
40+
"""Aerospike seed nodes.
41+
42+
Each entry is either ``(host, port)`` or ``(host, port, tls_name)`` when TLS
43+
is enabled. At least one seed node is required.
44+
"""
45+
46+
namespace: str = "feast"
47+
"""Aerospike namespace. Must be pre-configured on the cluster — namespaces
48+
cannot be created at runtime."""
49+
50+
set_name_template: str = "{project}_{collection_suffix}"
51+
"""Template for the per-project Aerospike set name. Available substitutions:
52+
``{project}`` and ``{collection_suffix}``."""
53+
54+
collection_suffix: str = "latest"
55+
"""Suffix used by ``set_name_template`` to distinguish sets belonging to the
56+
same project (e.g. a future multi-version layout)."""
57+
58+
user: Optional[str] = None
59+
"""Optional username for Aerospike Enterprise authentication."""
60+
61+
password: Optional[SecretStr] = None
62+
"""Optional password for Aerospike Enterprise authentication."""
63+
64+
auth_mode: Literal["internal", "external", "pki"] = "internal"
65+
"""Authentication mode. ``internal`` for CE/EE user/password, ``external``
66+
for LDAP/Kerberos, ``pki`` for certificate-based auth."""
67+
68+
tls: Optional[Dict[str, Any]] = None
69+
"""TLS configuration, passed through verbatim to the Aerospike client.
70+
See the Aerospike Python client ``tls`` policy options."""
71+
72+
ttl_seconds: Optional[int] = None
73+
"""Record-level TTL, applied to every write. ``None`` uses the namespace
74+
default, ``0`` means never expire (mapped to the client's ``-1`` sentinel).
75+
No per-feature-view override in v1."""
76+
77+
write_timeout_ms: int = 1_000
78+
"""Per-call write timeout in milliseconds."""
79+
80+
read_timeout_ms: int = 250
81+
"""Per-call read timeout in milliseconds."""
82+
83+
total_timeout_ms: int = 2_000
84+
"""Total (including retries) timeout in milliseconds."""
85+
86+
max_retries: int = 2
87+
"""Maximum number of automatic retries on transient errors."""
88+
89+
client_kwargs: Dict[str, Any] = {}
90+
"""Escape hatch for any Aerospike client configuration not surfaced above.
91+
Merged into the client config passed to ``aerospike.client()``."""
92+
93+
94+
class AerospikeOnlineStore(OnlineStore):
95+
"""Aerospike implementation of the Feast :class:`OnlineStore`.
96+
97+
This is a scaffold stub: the full read/write implementation lands in
98+
follow-up commits on this branch. See
99+
``notes/aerospike-online-store-plan.md`` for the storage model, schema,
100+
and delivery plan.
101+
102+
Planned layout (MongoDB-style, one set per project):
103+
104+
* Namespace: ``config.online_store.namespace`` (server-configured)
105+
* Set: ``{project}_{collection_suffix}``
106+
* Key: ``serialize_entity_key(entity_key)`` (bytes)
107+
* Bins:
108+
109+
* ``features`` — Map CDT ``{"<fv>": {"<feature>": <native_value>}}``
110+
* ``event_ts`` — Map CDT ``{"<fv>": <datetime>}``
111+
* ``created_ts`` — top-level datetime
112+
"""
113+
114+
def online_write_batch(
115+
self,
116+
config: RepoConfig,
117+
table: FeatureView,
118+
data: List[
119+
Tuple[EntityKeyProto, Dict[str, ValueProto], datetime, Optional[datetime]]
120+
],
121+
progress: Optional[Callable[[int], Any]],
122+
) -> None:
123+
raise NotImplementedError(
124+
"AerospikeOnlineStore.online_write_batch is not implemented yet."
125+
)
126+
127+
def online_read(
128+
self,
129+
config: RepoConfig,
130+
table: FeatureView,
131+
entity_keys: List[EntityKeyProto],
132+
requested_features: Optional[List[str]] = None,
133+
) -> List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]]:
134+
raise NotImplementedError(
135+
"AerospikeOnlineStore.online_read is not implemented yet."
136+
)
137+
138+
def update(
139+
self,
140+
config: RepoConfig,
141+
tables_to_delete: Sequence[FeatureView],
142+
tables_to_keep: Sequence[FeatureView],
143+
entities_to_delete: Sequence[Entity],
144+
entities_to_keep: Sequence[Entity],
145+
partial: bool,
146+
) -> None:
147+
raise NotImplementedError("AerospikeOnlineStore.update is not implemented yet.")
148+
149+
def teardown(
150+
self,
151+
config: RepoConfig,
152+
tables: Sequence[FeatureView],
153+
entities: Sequence[Entity],
154+
) -> None:
155+
raise NotImplementedError(
156+
"AerospikeOnlineStore.teardown is not implemented yet."
157+
)

sdk/python/feast/repo_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
}
6767

6868
ONLINE_STORE_CLASS_FOR_TYPE = {
69+
"aerospike": "feast.infra.online_stores.aerospike_online_store.AerospikeOnlineStore",
6970
"sqlite": "feast.infra.online_stores.sqlite.SqliteOnlineStore",
7071
"datastore": "feast.infra.online_stores.datastore.DatastoreOnlineStore",
7172
"redis": "feast.infra.online_stores.redis.RedisOnlineStore",

0 commit comments

Comments
 (0)