← 输入框|github.com|刷新
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/getting-started/concepts/feature-retrieval.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,31 @@ Applying a feature service does not result in an actual service being deployed.

Feature services enable referencing all or some features from a feature view.

#### Pre-computed feature vectors (`precompute_online`)

For latency-critical online serving, you can enable **pre-computed feature vectors** on a feature service. When `precompute_online=True`, Feast stores all of the service's features for each entity as a single serialized blob in the online store. At read time, this reduces the number of store reads from O(N feature views) to O(1), regardless of how many feature views the service spans.

```python
# A feature service with pre-computed vectors enabled
low_latency_service = FeatureService(
name="low_latency_inference",
features=[driver_stats_fv, vehicle_stats_fv, route_features_fv],
precompute_online=True,
)
```

After running `feast apply`, the pre-computed vectors are automatically built and refreshed whenever you run `feast materialize` or `feast materialize-incremental`. Feast detects which feature services have `precompute_online=True` and rebuilds their vectors for every affected entity after the per-feature-view writes complete. Vectors are also refreshed automatically on `feast push`.

{% hint style="info" %}
`precompute_online` is **opt-in** — it defaults to `False`. When enabled, the pre-computed path is used exclusively for that service; there is no silent fallback to per-feature-view reads. If vectors are missing or stale, the server raises an error, making problems visible immediately.
{% endhint %}

{% hint style="warning" %}
`precompute_online` is not compatible with on-demand feature views (ODFVs) that have `write_to_online_store=False`. ODFVs with `write_to_online_store=True` are supported since their values are materialized.
{% endhint %}

See the [performance tuning guide](../../how-to-guides/online-server-performance-tuning.md#pre-computed-feature-vectors) for benchmarks and detailed configuration.

Retrieving from the online store with a feature service

```python
Expand Down
44 changes: 43 additions & 1 deletion docs/how-to-guides/online-server-performance-tuning.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ When the server processes a `get_online_features()` call, it groups the requeste
**Redis exception:** The Redis online store overrides `get_online_features()` to batch all `HMGET` commands across every feature view into a **single pipeline execution**. Because all feature views for the same entity share one Redis hash key, the number of Redis round trips is always **1**, regardless of how many feature views the request touches. This means the "fewer feature views" guideline is less critical for Redis than for other stores — but consolidating feature views still reduces serialization and protobuf overhead at the application layer.
{% endhint %}

### Feature services are free
### Feature services are free (and can be faster)

A [Feature Service](../getting-started/concepts/feature-retrieval.md) is a named collection of feature references — it's a convenience grouping, not a separate storage or execution unit. Using a feature service adds only a registry lookup (cached) compared to listing features individually. There is no performance penalty for using feature services, and they are the recommended way to define stable, versioned feature sets for production models.

For latency-critical services, feature services also unlock **pre-computed feature vectors** (`precompute_online=True`), which reduce store reads from O(N feature views) to O(1). See the [Pre-computed feature vectors](#pre-computed-feature-vectors) section below for details and benchmarks.

### ODFV overhead is additive

Regular feature views incur only **store read** cost. On-demand feature views add **CPU-bound transformation** cost on top:
Expand Down Expand Up @@ -83,6 +85,45 @@ Requesting just `combined_score` triggers reads from **both** `driver_stats_fv`

---

## Pre-computed feature vectors

When a `get_online_features()` request touches multiple feature views, the server issues a separate store read per feature view. For services spanning 5–15+ feature views, this fan-out dominates latency — even with Redis pipeline batching, the protobuf deserialization and response-building overhead grows linearly with the number of views.

**Pre-computed feature vectors** solve this by storing all of a feature service's features for each entity as a single serialized blob. At read time, the server fetches one blob per entity instead of N reads per feature view, reducing the operation to O(1).

### How it works

1. **Define** a feature service with `precompute_online=True`:

```python
benchmark_service = FeatureService(
name="benchmark_customer_service",
features=[
customer_demographics_fv,
customer_behavioral_profile,
transaction_7d_aggregations,
transaction_30d_aggregations,
transaction_90d_patterns,
atm_usage_30d,
],
precompute_online=True,
)
```

2. **Apply** the feature service: `feast apply`
3. **Materialize** as usual — vectors are built automatically: `feast materialize-incremental $(date -u +"%Y-%m-%dT%H:%M:%S")`
4. **Read** features as usual — the server automatically uses the pre-computed path:

```python
features = store.get_online_features(
features=store.get_feature_service("benchmark_customer_service"),
entity_rows=[{"customer_id": "CUST_000001"}],
full_feature_names=True,
)
```

---

## Worker and connection tuning

The Python feature server uses Gunicorn with async workers. Tuning workers, connections, and timeouts directly impacts throughput and tail latency.
Expand Down Expand Up @@ -1086,4 +1127,5 @@ Reset `skip_dedup` to `false` (or remove it) after the bulk reload. Under normal
- [PostgreSQL Online Store](../reference/online-stores/postgres.md) — Connection pooling and SSL configuration
- [Redis Online Store](../reference/online-stores/redis.md) — Cluster mode, Sentinel, TTL configuration, and batched reads
- [On Demand Feature Views](../reference/beta-on-demand-feature-view.md) — Transformation modes and write-time transforms
- [Feature Services & `precompute_online`](../getting-started/concepts/feature-retrieval.md#pre-computed-feature-vectors-precompute_online) — Concept docs for pre-computed feature vectors
- [feature_store.yaml reference](../reference/feature-repository/feature-store-yaml.md) — Full configuration reference including `materialization` options
4 changes: 4 additions & 0 deletions protos/feast/core/FeatureService.proto
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ message FeatureServiceSpec {

// (optional) if provided logging will be enabled for this feature service.
LoggingConfig logging_config = 7;

// When true, a pre-computed feature vector is maintained per entity for this
// service, enabling single-read online retrieval instead of per-feature-view reads.
bool precompute_online = 8;
}


Expand Down
31 changes: 31 additions & 0 deletions protos/feast/core/PrecomputedFeatureVector.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
syntax = "proto3";
package feast.core;

option go_package = "github.com/feast-dev/feast/go/protos/feast/core";
option java_outer_classname = "PrecomputedFeatureVectorProto";
option java_package = "feast.proto.core";

import "google/protobuf/timestamp.proto";
import "feast/types/Value.proto";

// A pre-computed feature vector stores all features for a FeatureService
// as a single serialized blob per entity, enabling O(1) online retrieval.
message PrecomputedFeatureVector {
// Fully-qualified feature names in deterministic order, e.g. "fv1__feat1".
repeated string feature_names = 1;

// Feature values in the same order as feature_names.
repeated feast.types.Value values = 2;

// Per-feature-view event timestamps for TTL enforcement at read time.
repeated FeatureViewTimestamp fv_timestamps = 3;

// Wall-clock time when this vector was assembled.
google.protobuf.Timestamp precomputed_at = 4;
}

// Event timestamp associated with a specific feature view within the vector.
message FeatureViewTimestamp {
string feature_view_name = 1;
google.protobuf.Timestamp event_timestamp = 2;
}
23 changes: 21 additions & 2 deletions sdk/python/feast/feature_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class FeatureService:
last_updated_timestamp: Optional[datetime] = None
logging_config: Optional[LoggingConfig] = None

precompute_online: bool = False

def __init__(
self,
*,
Expand All @@ -58,18 +60,21 @@ def __init__(
description: str = "",
owner: str = "",
logging_config: Optional[LoggingConfig] = None,
precompute_online: bool = False,
):
"""
Creates a FeatureService object.

Args:
name: The unique name of the feature service.
feature_view_projections: A list containing feature views and feature view
features: A list containing feature views and feature view
projections, representing the features in the feature service.
description (optional): A human-readable description.
tags (optional): A dictionary of key-value pairs to store arbitrary metadata.
owner (optional): The owner of the feature view, typically the email of the
primary maintainer.
precompute_online (optional): When True, a pre-computed feature vector is
maintained per entity for single-read online retrieval.
"""
self.name = name
self._features = features
Expand All @@ -80,6 +85,7 @@ def __init__(
self.created_timestamp = None
self.last_updated_timestamp = None
self.logging_config = logging_config
self.precompute_online = precompute_online
for feature_grouping in self._features:
if isinstance(feature_grouping, BaseFeatureView):
self.feature_view_projections.append(feature_grouping.projection)
Expand Down Expand Up @@ -175,6 +181,7 @@ def __eq__(self, other):
or self.description != other.description
or self.tags != other.tags
or self.owner != other.owner
or self.precompute_online != other.precompute_online
):
return False

Expand Down Expand Up @@ -202,6 +209,7 @@ def from_proto(cls, feature_service_proto: FeatureServiceProto):
logging_config=LoggingConfig.from_proto(
feature_service_proto.spec.logging_config
),
precompute_online=feature_service_proto.spec.precompute_online,
)
fs.feature_view_projections.extend(
[
Expand Down Expand Up @@ -245,9 +253,20 @@ def to_proto(self) -> FeatureServiceProto:
logging_config=self.logging_config.to_proto()
if self.logging_config
else None,
precompute_online=self.precompute_online,
)

return FeatureServiceProto(spec=spec, meta=meta)

def validate(self):
pass
if not self.precompute_online:
return

for fv in self._features:
if isinstance(fv, OnDemandFeatureView) and not fv.write_to_online_store:
raise ValueError(
f"FeatureService '{self.name}' has precompute_online=True but "
f"contains OnDemandFeatureView '{fv.name}' with "
f"write_to_online_store=False. On-demand transforms computed at "
f"serve time cannot be pre-computed."
)
Loading
Loading