|
| 1 | +# SparkApplication Compute Engine |
| 2 | + |
| 3 | +## Description |
| 4 | + |
| 5 | +The **SparkApplication** compute engine runs Feast **batch materialization** on Kubernetes by creating a [Kubeflow Spark Operator](https://github.com/kubeflow/spark-operator) `SparkApplication` custom resource for each materialization job. |
| 6 | + |
| 7 | +Unlike the in-process [`spark.engine`](spark.md) compute engine (which uses a Spark session inside the Feast process), `spark_application` submits work to the Spark Operator. The operator starts a driver pod and executors from your configured image; Feast polls the SparkApplication until it completes. |
| 8 | + |
| 9 | +| Capability | Supported | |
| 10 | +|------------|-----------| |
| 11 | +| `materialize` / `materialize-incremental` | Yes | |
| 12 | +| Multiple feature views in one job | Yes — one SparkApplication per materialize call | |
| 13 | +| `get_historical_features` | Not yet | |
| 14 | +| SparkConnect | Separate approach — not this engine | |
| 15 | + |
| 16 | +### Design |
| 17 | + |
| 18 | +1. Feast creates a ConfigMap with job tasks and a driver copy of `feature_store.yaml`. |
| 19 | +2. Feast creates a `SparkApplication` CR pointing at the driver entrypoint (`main.py` in the image). |
| 20 | +3. Inside the pod, the batch engine type is rewritten to `spark.engine` so materialization uses the Spark session created by `spark-submit` (avoids recursive SparkApplication creation). |
| 21 | +4. The driver writes features to your configured **online store** and updates the **registry** (same network backends as the server). |
| 22 | + |
| 23 | +### Requirements |
| 24 | + |
| 25 | +- Kubeflow Spark Operator installed and watching the target namespace. |
| 26 | +- A container **image** that includes the Feast SDK, PySpark, and clients for your stores. See the reference [Dockerfile](https://github.com/feast-dev/feast/blob/master/sdk/python/feast/infra/compute_engines/spark_application/Dockerfile). |
| 27 | +- **Network-accessible** online store, offline store, and registry. File-based backends are rejected because Spark pods have an ephemeral filesystem: |
| 28 | + |
| 29 | +| Rejected | Examples | Use instead | |
| 30 | +|----------|----------|-------------| |
| 31 | +| File online | `sqlite`, `faiss` | Redis, remote online, etc. | |
| 32 | +| File offline | `dask`, `file`, `duckdb` | `spark`, Postgres, Snowflake, BigQuery, etc. | |
| 33 | +| File registry | `file` | SQL registry, Snowflake | |
| 34 | + |
| 35 | +For distributed reads, configure `offline_store.type: spark` (or another store Spark can read efficiently). |
| 36 | + |
| 37 | +### Kubernetes / Feast Operator notes |
| 38 | + |
| 39 | +When using the Feast Operator: |
| 40 | + |
| 41 | +- Point `spec.batchEngine.configMapRef` at a ConfigMap whose `type` is `spark_application` (see [Guide 6 — Batch Engine & Scheduled Jobs](../../how-to-guides/feast-operator/06-batch-and-jobs.md)). |
| 42 | +- The operator auto-creates RBAC for the `spark_application` batch engine (server and driver service accounts). |
| 43 | +- Set `spec.services.initImage` if init / `feast-apply` containers need the Spark-capable image. |
| 44 | + |
| 45 | +--- |
| 46 | + |
| 47 | +## Example |
| 48 | + |
| 49 | +{% code title="feature_store.yaml" %} |
| 50 | +```yaml |
| 51 | +project: my_project |
| 52 | +registry: |
| 53 | + registry_type: sql |
| 54 | + path: postgresql+psycopg://feast:****@postgres:5432/feast |
| 55 | +online_store: |
| 56 | + type: redis |
| 57 | + connection_string: redis:6379 |
| 58 | +offline_store: |
| 59 | + type: spark |
| 60 | + spark_conf: |
| 61 | + spark.master: local[*] |
| 62 | +batch_engine: |
| 63 | + type: spark_application |
| 64 | + image: my-registry.example.com/feast-spark-driver:latest |
| 65 | + namespace: feast |
| 66 | + spark_version: "4.0.1" |
| 67 | + driver_cores: 1 |
| 68 | + driver_memory: "2g" |
| 69 | + executor_instances: 2 |
| 70 | + executor_cores: 1 |
| 71 | + executor_memory: "2g" |
| 72 | + spark_conf: |
| 73 | + spark.sql.shuffle.partitions: "100" |
| 74 | +``` |
| 75 | +{% endcode %} |
| 76 | +
|
| 77 | +### Feast Operator ConfigMap |
| 78 | +
|
| 79 | +```yaml |
| 80 | +apiVersion: v1 |
| 81 | +kind: ConfigMap |
| 82 | +metadata: |
| 83 | + name: feast-spark-batch-engine |
| 84 | + namespace: feast |
| 85 | +data: |
| 86 | + config: | |
| 87 | + type: spark_application |
| 88 | + image: my-registry.example.com/feast-spark-driver:latest |
| 89 | + namespace: feast |
| 90 | + executor_instances: 2 |
| 91 | + driver_memory: "2g" |
| 92 | + executor_memory: "2g" |
| 93 | +--- |
| 94 | +apiVersion: feast.dev/v1 |
| 95 | +kind: FeatureStore |
| 96 | +metadata: |
| 97 | + name: feast |
| 98 | + namespace: feast |
| 99 | +spec: |
| 100 | + feastProject: my_project |
| 101 | + batchEngine: |
| 102 | + configMapRef: |
| 103 | + name: feast-spark-batch-engine |
| 104 | + configMapKey: config |
| 105 | +``` |
| 106 | +
|
| 107 | +--- |
| 108 | +
|
| 109 | +## Remote materialization |
| 110 | +
|
| 111 | +If the client uses a **remote** online store (`online_store.type: remote`), `FeatureStore.materialize()` delegates to the feature server HTTP API. The server runs the SparkApplication engine. |
| 112 | + |
| 113 | +- Default (`run_async=False`): block until the server finishes sync materialization. |
| 114 | +- `run_async=True`: accept asynchronously (`?async=true`); poll feature-view state in the registry for completion. |
| 115 | +- `force=True` (with `run_async=True`): override stuck `MATERIALIZING` state on the server. |
| 116 | + |
| 117 | +```python |
| 118 | +from datetime import datetime, timedelta |
| 119 | +from feast import FeatureStore |
| 120 | +
|
| 121 | +store = FeatureStore(repo_path=".") # client feature_store.yaml with online_store.type: remote |
| 122 | +
|
| 123 | +store.materialize( |
| 124 | + start_date=datetime.utcnow() - timedelta(days=1), |
| 125 | + end_date=datetime.utcnow(), |
| 126 | +) |
| 127 | +``` |
| 128 | + |
| 129 | +--- |
| 130 | + |
| 131 | +## Configuration reference |
| 132 | + |
| 133 | +| Field | Type | Default | Description | |
| 134 | +|-------|------|---------|-------------| |
| 135 | +| `type` | string | `spark_application` | Engine type key | |
| 136 | +| `image` | string | **required** | Container image for the Spark driver/executors | |
| 137 | +| `image_pull_secrets` | list[str] | `[]` | Image pull secret names | |
| 138 | +| `namespace` | string | `default` | Namespace for SparkApplication and ConfigMap | |
| 139 | +| `service_account` | string | `""` | Driver service account; empty uses platform/operator default | |
| 140 | +| `spark_version` | string | `4.0.1` | Spark version for the CR | |
| 141 | +| `driver_cores` | int | `1` | Driver cores | |
| 142 | +| `driver_memory` | string | `1g` | Driver memory | |
| 143 | +| `executor_instances` | int | `1` | Number of executors | |
| 144 | +| `executor_cores` | int | `1` | Cores per executor | |
| 145 | +| `executor_memory` | string | `1g` | Memory per executor | |
| 146 | +| `spark_conf` | dict | `null` | Extra Spark configuration | |
| 147 | +| `hadoop_conf` | dict | `null` | Extra Hadoop configuration | |
| 148 | +| `env` | list[dict] | `[]` | Driver env vars (`name` + `value` or `valueFrom`) | |
| 149 | +| `env_from` | list[dict] | `[]` | EnvFrom sources | |
| 150 | +| `queue_name` | string | `null` | Optional queue / Kueue label | |
| 151 | +| `job_timeout_seconds` | int | `3600` | Max wait for SparkApplication completion | |
| 152 | +| `poll_interval_seconds` | int | `10` | Status poll interval | |
| 153 | +| `ttl_seconds_after_finished` | int | `3600` | CR TTL after finish | |
| 154 | +| `restart_policy` | string | `Never` | SparkApplication restart policy | |
| 155 | +| `max_retries` | int | `3` | Retries when restart policy allows | |
| 156 | +| `concurrency` | int | `1` | Parallel feature views inside one driver | |
| 157 | +| `labels` | dict | `{}` | Extra labels on the CR | |
| 158 | +| `volumes` / `volume_mounts` | list | `[]` | Extra volumes for the driver | |
| 159 | +| `py_files` | list[str] | `[]` | Additional Python files for Spark | |
| 160 | +| `node_selector` | dict | `null` | Pod node selector | |
| 161 | +| `tolerations` | list | `[]` | Pod tolerations | |
| 162 | +| `staging_location` | string | `null` | Reserved for historical retrieval (ignored for materialize) | |
| 163 | + |
| 164 | +--- |
| 165 | + |
| 166 | +## Troubleshooting |
| 167 | + |
| 168 | +| Symptom | What to check | |
| 169 | +|---------|----------------| |
| 170 | +| SparkApplication Pending / insufficient CPU | Lower resource requests via `spark_conf` (for example `spark.kubernetes.driver.request.cores`) or free cluster capacity | |
| 171 | +| ImagePullBackOff | Image name, tag, and `image_pull_secrets` | |
| 172 | +| 403 on ConfigMap or SparkApplication | RBAC for the Feast server and Spark driver service accounts | |
| 173 | +| Init `ValueError` about file-based stores | Switch online/offline/registry to network backends | |
| 174 | +| Init / feast-apply failures missing Spark deps | Use a Spark-capable image (`initImage` with the Feast Operator) | |
| 175 | + |
| 176 | +--- |
| 177 | + |
| 178 | +## Related |
| 179 | + |
| 180 | +- [Spark compute engine (in-process)](spark.md) |
| 181 | +- [Feast Operator — batch engine ConfigMap](../../how-to-guides/feast-operator/06-batch-and-jobs.md) |
| 182 | +- [Creating a custom compute engine](../../how-to-guides/customizing-feast/creating-a-custom-compute-engine.md) |
0 commit comments