Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat: Horizontal scaling support to the Feast operator
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
  • Loading branch information
ntkathole committed Feb 27, 2026
commit 6656ec142d161529f2ceb21b06d7998963871df0
16 changes: 10 additions & 6 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@
{
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
},
{
"path": "detect_secrets.filters.common.is_baseline_file",
"filename": ".secrets.baseline"
},
{
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
"min_level": 2
Expand Down Expand Up @@ -930,7 +934,7 @@
"filename": "infra/feast-operator/api/v1/featurestore_types.go",
"hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
"is_verified": false,
"line_number": 657
"line_number": 692
}
],
"infra/feast-operator/api/v1/zz_generated.deepcopy.go": [
Expand All @@ -939,21 +943,21 @@
"filename": "infra/feast-operator/api/v1/zz_generated.deepcopy.go",
"hashed_secret": "f914fc9324de1bec1ad13dec94a8ea2ddb41fc87",
"is_verified": false,
"line_number": 615
"line_number": 658
},
{
"type": "Secret Keyword",
"filename": "infra/feast-operator/api/v1/zz_generated.deepcopy.go",
"hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
"is_verified": false,
"line_number": 1123
"line_number": 1206
},
{
"type": "Secret Keyword",
"filename": "infra/feast-operator/api/v1/zz_generated.deepcopy.go",
"hashed_secret": "c2028031c154bbe86fd69bef740855c74b927dcf",
"is_verified": false,
"line_number": 1128
"line_number": 1211
}
],
"infra/feast-operator/api/v1alpha1/featurestore_types.go": [
Expand Down Expand Up @@ -1152,7 +1156,7 @@
"filename": "infra/feast-operator/internal/controller/services/services.go",
"hashed_secret": "36dc326eb15c7bdd8d91a6b87905bcea20b637d1",
"is_verified": false,
"line_number": 164
"line_number": 178
}
],
"infra/feast-operator/internal/controller/services/tls_test.go": [
Expand Down Expand Up @@ -1535,5 +1539,5 @@
}
]
},
"generated_at": "2026-02-19T06:53:49Z"
"generated_at": "2026-02-21T16:33:24Z"
}
6 changes: 4 additions & 2 deletions docs/how-to-guides/feast-on-kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ spec:
> _More advanced FeatureStore CR examples can be found in the feast-operator [samples directory](../../infra/feast-operator/config/samples)._

{% hint style="success" %}
Important note: Scaling a Feature Store Deployment should only be done if the configured data store(s) will support it.
**Scaling:** The Feast Operator supports horizontal scaling via static replicas, HPA autoscaling, or external autoscalers like [KEDA](https://keda.sh). Scaling requires DB-backed persistence for all enabled services.

Please check the how-to guide for some specific recommendations on [how to scale Feast](./scaling-feast.md).
See the [Horizontal Scaling with the Feast Operator](./scaling-feast.md#horizontal-scaling-with-the-feast-operator) guide for configuration details, or check the general recommendations on [how to scale Feast](./scaling-feast.md).
{% endhint %}

> _Sample scaling CRs are available at [`v1_featurestore_scaling_static.yaml`](../../infra/feast-operator/config/samples/v1_featurestore_scaling_static.yaml) and [`v1_featurestore_scaling_hpa.yaml`](../../infra/feast-operator/config/samples/v1_featurestore_scaling_hpa.yaml)._
155 changes: 154 additions & 1 deletion docs/how-to-guides/scaling-feast.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,157 @@ However, this process does not scale for large data sets, since it's executed on
Feast supports pluggable [Compute Engines](../getting-started/components/compute-engine.md), that allow the materialization process to be scaled up.
Aside from the local process, Feast supports a [Lambda-based materialization engine](https://rtd.feast.dev/en/master/#alpha-lambda-based-engine), and a [Bytewax-based materialization engine](https://rtd.feast.dev/en/master/#bytewax-engine).

Users may also be able to build an engine to scale up materialization using existing infrastructure in their organizations.
Users may also be able to build an engine to scale up materialization using existing infrastructure in their organizations.

### Horizontal Scaling with the Feast Operator

When running Feast on Kubernetes with the [Feast Operator](./feast-on-kubernetes.md), you can horizontally scale the FeatureStore deployment by adding a `scaling` field to the `services` section of the FeatureStore CR.

**Prerequisites:** Horizontal scaling requires **DB-backed persistence** for all enabled services (online store, offline store, and registry). File-based persistence (SQLite, DuckDB, `registry.db`) is incompatible with multiple replicas because these backends do not support concurrent access from multiple pods.

#### Static Replicas

Set a fixed number of replicas:

```yaml
apiVersion: feast.dev/v1
kind: FeatureStore
metadata:
name: sample-scaling
spec:
feastProject: my_project
services:
scaling:
replicas: 3
onlineStore:
persistence:
store:
type: postgres
secretRef:
name: feast-data-stores
registry:
local:
persistence:
store:
type: sql
secretRef:
name: feast-data-stores
```

#### Autoscaling with HPA

Configure a HorizontalPodAutoscaler to dynamically scale based on metrics:

```yaml
apiVersion: feast.dev/v1
kind: FeatureStore
metadata:
name: sample-autoscaling
spec:
feastProject: my_project
services:
scaling:
autoscaling:
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
onlineStore:
persistence:
store:
type: postgres
secretRef:
name: feast-data-stores
server:
resources:
requests:
cpu: 200m
memory: 256Mi
registry:
local:
persistence:
store:
type: sql
secretRef:
name: feast-data-stores
```

{% hint style="info" %}
When autoscaling is configured, the operator automatically sets the deployment strategy to `RollingUpdate` (instead of the default `Recreate`) to ensure zero-downtime scaling. You can override this by explicitly setting `deploymentStrategy` in the CR.
{% endhint %}

#### Validation Rules

The operator enforces the following rules:
- `replicas` and `autoscaling` are **mutually exclusive** -- you cannot set both.
- Scaling with `replicas > 1` or any `autoscaling` config is **rejected** if any enabled service uses file-based persistence.
- S3 (`s3://`) and GCS (`gs://`) backed registry file persistence is allowed with scaling, since these object stores support concurrent readers.

#### Using KEDA (Kubernetes Event-Driven Autoscaling)

[KEDA](https://keda.sh) is also supported as an external autoscaler. Rather than using the built-in `autoscaling` field, you can create a KEDA `ScaledObject` that targets the Feast deployment directly.

When using KEDA, do **not** set the `scaling.autoscaling` field -- KEDA manages its own HPA. The operator will preserve the replica count set by KEDA since it does not override externally managed replicas.
Comment thread
ntkathole marked this conversation as resolved.
Outdated

There are a few things you must configure manually when using KEDA:

1. **Set the deployment strategy to `RollingUpdate`** -- The operator defaults to `Recreate` when no `scaling` config is present, which causes downtime on scale events. Override it explicitly:

```yaml
apiVersion: feast.dev/v1
kind: FeatureStore
metadata:
name: sample-keda
spec:
feastProject: my_project
services:
deploymentStrategy:
type: RollingUpdate
onlineStore:
persistence:
store:
type: postgres
secretRef:
name: feast-data-stores
registry:
local:
persistence:
store:
type: sql
secretRef:
name: feast-data-stores
```

2. **Ensure DB-backed persistence** -- The operator's persistence validation only applies when the built-in `scaling` field is used. With KEDA, you are responsible for ensuring all enabled services use DB-backed persistence (not SQLite, DuckDB, or local `registry.db`).

3. **Create a KEDA `ScaledObject`** targeting the Feast deployment:

```yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: feast-scaledobject
spec:
scaleTargetRef:
name: feast-sample-keda # must match the Feast deployment name
minReplicaCount: 2
maxReplicaCount: 10
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring.svc:9090
metricName: http_requests_total
query: sum(rate(http_requests_total{service="feast"}[2m]))
threshold: "100"
```

{% hint style="warning" %}
KEDA-created HPAs are not owned by the Feast operator. The operator will not interfere with them, but it also will not clean them up if the FeatureStore CR is deleted. You must manage the KEDA `ScaledObject` lifecycle independently.
{% endhint %}

For the full API reference, see the [FeatureStore CRD reference](../../infra/feast-operator/docs/api/markdown/ref.md).
45 changes: 45 additions & 0 deletions infra/feast-operator/api/v1/featurestore_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package v1

import (
appsv1 "k8s.io/api/apps/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -301,6 +302,40 @@ type FeatureStoreServices struct {
DisableInitContainers bool `json:"disableInitContainers,omitempty"`
// Volumes specifies the volumes to mount in the FeatureStore deployment. A corresponding `VolumeMount` should be added to whichever feast service(s) require access to said volume(s).
Volumes []corev1.Volume `json:"volumes,omitempty"`
// Scaling configures horizontal scaling for the FeatureStore deployment.
// Requires DB-based persistence for all enabled services when replicas > 1 or autoscaling is configured.
Scaling *ScalingConfig `json:"scaling,omitempty"`
}

// ScalingConfig configures horizontal scaling for the FeatureStore deployment.
// +kubebuilder:validation:XValidation:rule="!has(self.replicas) || !has(self.autoscaling)",message="replicas and autoscaling are mutually exclusive."
type ScalingConfig struct {
// Replicas is the static number of pod replicas. Mutually exclusive with autoscaling.
// +kubebuilder:validation:Minimum=1
// +optional
Replicas *int32 `json:"replicas,omitempty"`
// Autoscaling configures a HorizontalPodAutoscaler for the FeatureStore deployment.
// Mutually exclusive with replicas.
// +optional
Autoscaling *AutoscalingConfig `json:"autoscaling,omitempty"`
}

// AutoscalingConfig defines HPA settings for the FeatureStore deployment.
type AutoscalingConfig struct {
// MinReplicas is the lower limit for the number of replicas. Defaults to 1.
// +kubebuilder:validation:Minimum=1
// +optional
MinReplicas *int32 `json:"minReplicas,omitempty"`
// MaxReplicas is the upper limit for the number of replicas. Required.
// +kubebuilder:validation:Minimum=1
MaxReplicas int32 `json:"maxReplicas"`
// Metrics contains the specifications for which to use to calculate the desired replica count.
// If not set, defaults to 80% CPU utilization.
// +optional
Metrics []autoscalingv2.MetricSpec `json:"metrics,omitempty"`
// Behavior configures the scaling behavior of the target.
// +optional
Behavior *autoscalingv2.HorizontalPodAutoscalerBehavior `json:"behavior,omitempty"`
}

// OfflineStore configures the offline store service
Expand Down Expand Up @@ -690,6 +725,16 @@ type FeatureStoreStatus struct {
FeastVersion string `json:"feastVersion,omitempty"`
Phase string `json:"phase,omitempty"`
ServiceHostnames ServiceHostnames `json:"serviceHostnames,omitempty"`
// ScalingStatus reports the current scaling state of the FeatureStore deployment.
ScalingStatus *ScalingStatus `json:"scalingStatus,omitempty"`
}

// ScalingStatus reports the observed scaling state.
type ScalingStatus struct {
Comment thread
ntkathole marked this conversation as resolved.
// CurrentReplicas is the current number of pod replicas.
CurrentReplicas int32 `json:"currentReplicas,omitempty"`
// DesiredReplicas is the desired number of pod replicas.
DesiredReplicas int32 `json:"desiredReplicas,omitempty"`
}

// ServiceHostnames defines the service hostnames in the format of <domain>:<port>, e.g. example.svc.cluster.local:80
Expand Down
Loading