Skip to content

feat: Bring Your Own Spark - SparkApplication - #6550

Merged
ntkathole merged 16 commits into
feast-dev:masterfrom
aniketpalu:feat/spark-application-compute-engine
Jul 17, 2026
Merged

feat: Bring Your Own Spark - SparkApplication#6550
ntkathole merged 16 commits into
feast-dev:masterfrom
aniketpalu:feat/spark-application-compute-engine

Conversation

@aniketpalu

@aniketpalu aniketpalu commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

Core changes

Upstream (feature_store.py):

  • Refactored `materialize()` and `materialize_incremental()` to collect all `MaterializationTask`s and pass them to the engine in a single `batch_engine.materialize(registry, tasks)` call. This enables batching engines to process all feature views in one job. Existing engines are unaffected — `ComputeEngine.materialize()` base class loops tasks internally via `_materialize_one()`.
  • Added public `get_provider()` method on `FeatureStore` (wraps existing private `_get_provider()`).
    New engine (spark_application/):
  • `config.py`: Pydantic config with Spark resource specs, K8s options, validation (`env` entries must have `name`, `staging_location` warning).
  • `compute.py`: Creates a K8s Secret with `feature_store.yaml` + task list, builds SparkApplication CR, polls for completion, retrieves driver logs on failure.
  • `job.py`: Maps all 14 SparkApplication states to `MaterializationJobStatus`, with exponential backoff retry on transient API errors.
  • `main.py`: Driver script that runs inside the pod — loads config from Secret, materializes each feature view via `provider.materialize_single_feature_view()`. Supports concurrent FV processing via ThreadPoolExecutor + FAIR scheduler.
  • `Dockerfile`: Based on `apache/spark:4.0.1`, installs `feast[redis]==0.64.0`, includes Hadoop AWS JARs for S3A support.

Design decisions

  • Two config rewrites in driver pod: `batch_engine` → `spark.engine` (prevents recursive CRD creation, uses local SparkSession), `registry` → `remote` when `registry_address` is set (pod can't access server filesystem).
  • Driver calls `provider.materialize_single_feature_view()`, not `store.materialize()` — avoids FeatureViewState transition conflicts (MATERIALIZING→MATERIALIZING is invalid) and duplicate OpenLineage/MLflow emissions.
  • SQLite online store rejected at init — data written inside a pod is lost on termination.
  • Kueue integration via `queue_name` config field → `kueue.x-k8s.io/queue-name` label.

Validated on

  • OpenShift AI (ROSA) with Spark Operator v2.5.0, MinIO (S3A), Redis
  • 5 feature views, 4800 rows each, 2 executors: 2.4x speedup with concurrent mode

Test plan

  • 12 unit tests: config defaults, SQLite rejection, registry validation, config rewrites, CR structure, state mapping (14 states), cleanup 404 handling, timeout behavior, job naming
  • Existing tests unaffected: 29 feature_view_state + 22 local_feature_store tests pass
  • Integration test on cluster with real Spark Operator"

Which issue(s) this PR fixes:

Checks

  • I've made sure the tests are passing.
  • My commits are signed off (git commit -s)
  • My PR title follows conventional commits format

Testing Strategy

  • Unit tests
  • Integration tests
  • Manual tests
  • Testing is not required for this change

Misc

@aniketpalu
aniketpalu requested a review from a team as a code owner June 24, 2026 08:57
Comment thread sdk/python/feast/infra/compute_engines/spark_application/main.py Fixed
Comment thread sdk/python/feast/infra/compute_engines/spark_application/main.py Fixed
Comment thread sdk/python/feast/infra/compute_engines/spark_application/main.py Fixed
Comment thread sdk/python/feast/infra/compute_engines/spark_application/main.py Fixed
Comment thread sdk/python/feast/infra/compute_engines/spark_application/main.py Fixed
Comment thread sdk/python/feast/infra/compute_engines/spark_application/main.py Fixed
@aniketpalu
aniketpalu marked this pull request as draft June 24, 2026 11:18
Comment thread sdk/python/feast/infra/compute_engines/spark_application/main.py Fixed
@aniketpalu
aniketpalu marked this pull request as ready for review July 3, 2026 07:53
aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 14, 2026
aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 14, 2026
Keep feast-dev#6550 batching in materialize loops; skip FV state transition when
_force_local so async /materialize-async (already MATERIALIZING) works.

Co-authored-by: Cursor <cursoragent@cursor.com>
aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 14, 2026
aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 14, 2026
Keep feast-dev#6550 batching; skip FV state transition when _force_local so
/materialize-async (already MATERIALIZING) works with SparkApplication.

Co-authored-by: Cursor <cursoragent@cursor.com>
aniketpalu and others added 7 commits July 15, 2026 16:42
…aterialization

Adds a new batch compute engine that submits materialization jobs as
SparkApplication CRDs via the Kubeflow Spark Operator. One 'feast materialize'
call creates one SparkApplication pod that processes all feature views using
distributed Spark, rather than running in-process on the Feast server.
Key changes:
- Refactor materialize()/materialize_incremental() to pass all tasks to the
  engine in a single batch call instead of looping per feature view. Existing
  engines are unaffected (base class loops tasks internally via _materialize_one).
- Add public get_provider() method on FeatureStore.
- New spark_application engine: config, compute, job, driver script, Dockerfile.
- 12 unit tests covering config, validation, CR structure, state mapping,
  timeout, cleanup, and job naming.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
- Pod calls apply_materialization via gRPC after each successful FV,
  setting state to AVAILABLE_ONLINE. Server reads FV state post-completion
  to determine per-FV success/failure in batched SparkApplication runs.
- registry_address is now mandatory (simplified from complex path heuristic).
- Dockerfile rewritten to install feast from source (matches K8s engine pattern).
- Unit tests updated: 15/15 pass (3 new tests for _build_per_fv_jobs).
- E2E validated: 5 FVs x 9600 rows, 5 executors, all AVAILABLE_ONLINE.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
… stores

- Config delivery: Secret → ConfigMap. Operator's ClusterRole already has
  full ConfigMap CRUD — avoids widening RBAC for Secrets in ODH.
  Matches KubernetesComputeEngine pattern.
- Removed registry_address config field. Pod inherits server's registry
  config (SQL, Snowflake) directly and writes apply_materialization()
  to the same database. Eliminates TLS certificate mounting complexity.
- Reject file-based offline stores (dask, file, duckdb) and registries
  (file) at __init__(), same as existing sqlite/faiss online store
  rejection. SparkApplication pod has ephemeral filesystem.
- Dockerfile: added PYTHONPATH/SPARK_HOME for PySpark, added pymysql.
- 20/20 unit tests pass.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
@aniketpalu
aniketpalu force-pushed the feat/spark-application-compute-engine branch from 9578721 to 7a27217 Compare July 15, 2026 11:12
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
@jyejare

jyejare commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

@aniketpalu lint checks are failing

@jyejare jyejare left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the core architecture is solid, there are several critical issues around thread safety, error handling, and resource management that need addressing before merge.

Comment thread sdk/python/feast/feature_store.py Outdated
@@ -424,6 +424,113 @@ def _get_provider(self) -> Provider:
# TODO: Bake self.repo_path into self.config so that we dont only have one interface to paths

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Race condition in state management

The state transition and rollback logic has potential race conditions when multiple materialization jobs run concurrently. The previous_states dictionary and feature view state updates are not atomic, which could lead to inconsistent states or lost rollbacks.

Suggested:

Suggested change
# TODO: Bake self.repo_path into self.config so that we dont only have one interface to paths
# Use locks or atomic operations for state management
import threading
state_lock = threading.Lock()
with state_lock:
for fv, job in zip(regular_fvs, jobs):
fv_status = job.status()
if fv_status == MaterializationJobStatus.ERROR:
failed_fvs.append(fv)
if first_error is None and job.error():
first_error = job.error()
else:
succeeded_fvs.append(fv)
if failed_fvs:
self._rollback_fv_states(failed_fvs, previous_states)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The threading race condition described isn't present because previous_states is a local variable (line 2551) and all loops (for fv, job in zip(...)) are sequential Python for loops, not threaded. Each materialize() call creates its own dict.

However, reviewing this code path revealed a shared-object bug in our engine's _build_per_fv_jobs. Previously, all succeeded FVs received the same SparkApplicationMaterializationJob reference. During _wait_for_completion, polling sets _error on that object when the SparkApp transitions to FAILED. Later, when feature_store.py calls job.status() for each FV, they all hit the if self._error is not None: return ERROR early-return, even for the FVs that actually succeeded and wrote AVAILABLE_ONLINE to the registry.

Comment thread sdk/python/feast/infra/compute_engines/spark_application/main.py Outdated
Comment thread sdk/python/feast/infra/compute_engines/spark_application/compute.py
Comment thread sdk/python/feast/infra/compute_engines/spark_application/job.py
Comment thread sdk/python/feast/infra/compute_engines/spark_application/compute.py
Comment on lines +56 to +66
"staging_location is configured but only used for "
"get_historical_features (not yet supported by this engine). "
"It will be ignored for materialize operations.",
stacklevel=2,
)
for i, entry in enumerate(self.env):
if "name" not in entry:
raise ValueError(
f"env[{i}] is missing required 'name' key. "
f"Each env entry must have at least 'name' and 'value'. Got: {entry}"
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Improve configuration validation

The env validation only checks for 'name' field but doesn't validate that 'value' exists or that the overall structure is correct for Kubernetes environment variables.

Suggested:

Suggested change
"staging_location is configured but only used for "
"get_historical_features (not yet supported by this engine). "
"It will be ignored for materialize operations.",
stacklevel=2,
)
for i, entry in enumerate(self.env):
if "name" not in entry:
raise ValueError(
f"env[{i}] is missing required 'name' key. "
f"Each env entry must have at least 'name' and 'value'. Got: {entry}"
)
for i, entry in enumerate(self.env):
if not isinstance(entry, dict):
raise ValueError(
f"env[{i}] must be a dict, got {type(entry).__name__}: {entry}"
)
if "name" not in entry:
raise ValueError(
f"env[{i}] is missing required 'name' key. "
f"Each env entry must have at least 'name' and 'value'. Got: {entry}"
)
if "value" not in entry and "valueFrom" not in entry:
raise ValueError(
f"env[{i}] must have either 'value' or 'valueFrom'. Got: {entry}"
)

Comment thread sdk/python/feast/infra/compute_engines/spark_application/main.py Outdated
- Remove redundant get_provider(); callers use .provider property
- Replace SparkSession monkey-patch with per-thread session binding
- Smart retry: only 5xx/429; fail fast on 401/403 with RBAC hint
- Retry ConfigMap + SparkApplication creation (transient K8s errors)
- Validate env entries: require name + value or valueFrom (K8s EnvVar)
- Independent per-FV job status via CompletedMaterializationJob
- Exit 1 on any FV failure so SparkApp CR reflects partial failure
- Cleanup logs include kubectl delete command for manual recovery
- Lint: pragma allowlist on test fixture URL

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
@codecov-commenter

codecov-commenter commented Jul 16, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 42.05776% with 321 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.00%. Comparing base (14e0a83) to head (cbd4051).

Files with missing lines Patch % Lines
...st/infra/compute_engines/spark_application/main.py 0.00% 108 Missing ⚠️
sdk/python/feast/feature_store.py 20.00% 98 Missing and 2 partials ⚠️
...infra/compute_engines/spark_application/compute.py 56.08% 70 Missing and 13 partials ⚠️
...ast/infra/compute_engines/spark_application/job.py 70.12% 18 Missing and 5 partials ⚠️
.../infra/compute_engines/spark_application/config.py 87.75% 3 Missing and 3 partials ⚠️
sdk/python/feast/infra/compute_engines/base.py 83.33% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #6550      +/-   ##
==========================================
- Coverage   46.02%   46.00%   -0.03%     
==========================================
  Files         402      406       +4     
  Lines       47656    48164     +508     
  Branches     6741     6814      +73     
==========================================
+ Hits        21936    22157     +221     
- Misses      24196    24461     +265     
- Partials     1524     1546      +22     
Flag Coverage Δ
go-feature-server 30.58% <ø> (ø)
python-unit 47.32% <42.05%> (-0.05%) ⬇️
Files with missing lines Coverage Δ
sdk/python/feast/repo_config.py 77.38% <ø> (ø)
sdk/python/feast/infra/compute_engines/base.py 78.84% <83.33%> (+0.58%) ⬆️
.../infra/compute_engines/spark_application/config.py 87.75% <87.75%> (ø)
...ast/infra/compute_engines/spark_application/job.py 70.12% <70.12%> (ø)
...infra/compute_engines/spark_application/compute.py 56.08% <56.08%> (ø)
sdk/python/feast/feature_store.py 41.06% <20.00%> (-1.33%) ⬇️
...st/infra/compute_engines/spark_application/main.py 0.00% <0.00%> (ø)

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 14e0a83...cbd4051. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 16, 2026
aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 16, 2026
Keep feast-dev#6550 batching; skip FV state transition when _force_local so
/materialize-async (already MATERIALIZING) works with SparkApplication.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread sdk/python/feast/feature_store.py Outdated
Comment thread sdk/python/feast/feature_store.py
tqdm_builder=lambda length: tqdm(total=length, ncols=100),
)

thread_store.registry.apply_materialization(fv, thread_store.project, start, end)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply_materialization is called twice for each succeeded FV - once by the driver pod, once by the server after the job completes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aniketpalu This still exists

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, the double write was duplicating materialization intervals. The driver pod remains the owner of apply_materialization (needed for per-FV AVAILABLE_ONLINE / partial success). The server batch path now skips the second call when the engine sets applies_materialization=True (SparkApplication only). Other engines still apply on the server as before.

Comment thread sdk/python/feast/infra/compute_engines/spark_application/compute.py Outdated
job = SparkApplicationMaterializationJob(
job_id, self.config.namespace, self.custom_api
)
self._wait_for_completion(job)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be async.

This blocks the Feast server thread for the entire duration of the Spark job (up to job_timeout_seconds, default 1 hour). For a server handling multiple clients, this is a scalability concern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed this is a real scalability concern for the sync path: _wait_for_completion holds the caller until the SparkApplication finishes.

Making the engine poll itself async would redesign the ComputeEngine.materialize(). The intended non-blocking path is remote materialization in #6590 (POST /materialize-async + registry polling). We'll address true submit-without-wait server behavior there so the async path does not hold a worker thread for the full Spark job.

Would it be okay to leaving this as-is in #6550 and tracked against #6590?

Comment thread sdk/python/feast/infra/compute_engines/spark_application/job.py Outdated
Comment thread sdk/python/feast/infra/compute_engines/spark_application/compute.py Outdated
Restore master's per-FV materialize path for engines that do not
support batching; keep SparkApplication on the existing batch path
via ComputeEngine.supports_batch.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 16, 2026
Take supports_batch isolation; keep _force_local skip for remote async
materialize on both batch and per-FV paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
… cleanup

Replace __end_date__ sentinel with _MaterializationDateRange; fail fast
if engine job count mismatches; map UNKNOWN SparkApp state to WAITING;
always cleanup ConfigMap/CR after wait.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Defer kubeconfig load until materialize/cleanup so feast apply can
construct the engine without a cluster.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Comment thread sdk/python/feast/feature_store.py Outdated
# Roll back state to previous value on failure.
# batch_engine is on PassthroughProvider (concrete); same access as
# _submit_and_process_materialization_jobs via untyped provider.
if getattr(provider, "batch_engine").supports_batch:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fragile

batch_engine = getattr(provider, "batch_engine", None)
if batch_engine and getattr(batch_engine, "supports_batch", False):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Pod already writes watermarks via applies_materialization; server
batch path skips the second call to avoid duplicate intervals.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Use null-safe getattr so materialize falls back to the per-FV path
when batch_engine or supports_batch is absent.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 17, 2026
Null-safe supports_batch, skip duplicate apply_materialization,
lazy K8s client, review hardening.

@ntkathole ntkathole left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good

@ntkathole
ntkathole merged commit dcd496f into feast-dev:master Jul 17, 2026
29 of 30 checks passed
aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 18, 2026
Bring in feast-dev#6550 SparkApplication (supports_batch / _materialize_fvs_batch).
Resolve feature_store.py by keeping master batch structure and threading
_force_local through transition/rollback/batch helpers so async remote-mat
does not double-set MATERIALIZING or wrongly roll back state.

Co-authored-by: Cursor <cursoragent@cursor.com>
aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 18, 2026
Refresh with current feast-dev#6590 tip (includes master merge with feast-dev#6550).
Keep spark_application Dockerfile postgres extra for E2E.

Co-authored-by: Cursor <cursoragent@cursor.com>
franciscojavierarceo pushed a commit that referenced this pull request Jul 20, 2026
# [0.65.0](v0.64.0...v0.65.0) (2026-07-20)

### Bug Fixes

* add debug logging for FIPS mode detection fallback ([6c1b24e](6c1b24e))
* Build embedded UI from local source ([#6525](#6525)) ([3500349](3500349))
* Bump decommissioned Snowflake Python UDF runtime from 3.9 to 3.10 ([#6606](#6606)) ([#6608](#6608)) ([10341e4](10341e4))
* configure FIPS-compliant gRPC cipher suites for offline server ([6bc80a2](6bc80a2))
* Correct Flink PyArrow dependency constraints ([#6604](#6604)) ([70a9751](70a9751))
* Fix ValueError in signal handling for Trino worker threads ([#6428](#6428)) ([506d919](506d919))
* Fixed monitoring page issues ([7946018](7946018))
* Make pytest config compatible with newer pytest ([#5779](#5779)) ([a57ea33](a57ea33))
* Replace comma with space in DynamoDB-incompatible label tag value ([51e3a16](51e3a16))
* Resolve UI build warnings ([#6529](#6529)) ([abe92af](abe92af))
* Unblock nightly UI build ([#6570](#6570)) ([f296d4b](f296d4b))
* Use LONGBLOB for SQL registry proto columns on MySQL ([#6566](#6566)) ([7e4beb2](7e4beb2))

### Features

* Add click-to-zoom lightbox for blog post images ([#6575](#6575)) ([1cb23fd](1cb23fd))
* Add dark mode support to website and blog ([#6589](#6589)) ([7358fb8](7358fb8))
* Add OnlineStore for Aerospike ([#6532](#6532)) ([9cd35e1](9cd35e1))
* Add OpenLineage Consumer to Feast - receive, store, and visualize cross-producer lineage ([#6549](#6549)) ([a834126](a834126))
* Add registry list feature views by updated since ([#6092](#6092)) ([#6093](#6093)) ([006c606](006c606))
* Add ScyllaDB online store with vector search ([#6508](#6508)) ([1669661](1669661))
* Added compute and jobs UI ([ba2c05c](ba2c05c))
* Added Iceberg REST Catalog data source support ([e0a8573](e0a8573))
* Bring Your Own Spark - SparkApplication ([#6550](#6550)) ([dcd496f](dcd496f))
* **cassandra:** Add multi-DC support via per-datacenter execution profiles ([#6434](#6434)) ([0de9196](0de9196))
* Enhanced data source creation as a visual catalog with type-specific forms ([#6557](#6557)) ([d6acbba](d6acbba))
* Enhanced datasets UI functionality ([de11152](de11152))
* Implement RegistryServer.Proto RPC with RBAC-filtered response ([#6558](#6558)) ([#6552](#6552)) ([0d02614](0d02614))
* New zoned timestamp feature type ([#6536](#6536)) ([#6537](#6537)) ([eb042f0](eb042f0))
* **operator:** Auto-create RBAC for spark_application batch engine ([#6597](#6597)) ([f487b37](f487b37))
* **operator:** integrate cluster TLS profile for OCP 5.0 compliance ([43263a6](43263a6))
* Permissions CRUD UI and OIDC auth integration in UI ([6511da1](6511da1))
* Retrieve historical features from BigQuery without entity_df ([#6569](#6569)) ([cd5f6bb](cd5f6bb)), closes [#6558](#6558) [#6552](#6552)
* **spark:** SparkSource query+path and pre-computed offline read for BatchFeatureView ([#6440](#6440)) ([4dc8757](4dc8757))

### BREAKING CHANGES

* total_timeout_ms is renamed to batch_total_timeout_ms. Config files using the old name must be updated. No default value change.

Docs updated (reference + perf-tuning guide) with a short explainer on the per-attempt vs total deadline distinction. Two new unit tests pin the policy wiring: socket_timeout_ms propagates to all three scopes, and is omitted (not injected as None) when unset.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* refactor(aerospike): use MAP_KEY_ORDERED, KEY_DIGEST, and instance-scoped client

Cheap-win cleanups flagged in review, all touching the same small patch of write-path and lifecycle code.

* Map CDTs are now created with MAP_KEY_ORDERED. map_get_by_key / map_remove_by_key on an ordered map are O(log N) in the map size instead of O(N); matters on reads of wide feature views and on the update() background scan (which walks every record in the project's set).

* Writes drop POLICY_KEY_SEND and rely on the client default (POLICY_KEY_DIGEST). The serialized entity key is no longer stored alongside each record, saving per-record storage the read path never consumes (batch_operate preserves request order; results are paired back by zip in online_read).

* _client moves from a class attribute to an instance attribute (set in __init__). Previously two AerospikeOnlineStore instances could share the cached client through class state until one wrote self._client. With the instance attribute the state is always per-instance from construction.

* Drop MongoDB references from class docstrings and comments (they referred to how the storage layout was derived rather than documenting current behavior). Also rewrite the _build_batch_writes docstring to describe the policies applied on the write path.

Unit test assertions for the write-path record are updated: bw.policy is now None (client default applies) and map ops carry map_policy={'map_order': MAP_KEY_ORDERED}. All three docker-backed integration tests still pass end-to-end (cross-FV upsert, update() background scan, full feature-store round-trip), so the read/write shape survives the ordering and policy changes against a real server.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* feat(aerospike): add per-FV namespace/set overrides and prewriting hook

Adds three configuration knobs to AerospikeOnlineStoreConfig:

- namespace_overrides: pin individual feature views to a different
  Aerospike namespace (e.g. RAM-only vs. SSD-backed) without splitting
  the project across stores.
- set_overrides: place a feature view in its own set so admin ops on
  it (truncate, scan-based deletes during `feast apply`) do not touch
  records of other views.
- prewriting_hook: import-string-resolved callable invoked once per
  online_write_batch with the rows about to be written, returning the
  rows that actually go on the wire. Resolved and cached on first use;
  returning [] short-circuits the wire call.

Read, write, update and teardown paths all honour the per-FV ns/set
resolution. update() groups dropped feature views by their resolved
(ns, set) pair and issues one background scan per group. teardown()
truncates every unique (ns, set) pair the project may have written to,
including the store-level default.

Adds 22 unit tests for the new behaviour and updates 3 existing call
sites of _build_batch_writes for the new namespace= parameter. Adds a
sample hook module under examples/online_store/aerospike_overrides_and_hooks/
and corresponding sections in docs/reference/online-stores/aerospike.md.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* test: update aerospike image tag

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* chore: sync README template and secrets baseline after master merge

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* chore: fix secrets baseline line number for v1 operator types

Adding aerospike to the feast-operator enum shifted the allowlisted
SecretRef entry in api/v1/featurestore_types.go by one line.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* docs: update aerospike docs

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* fix(aerospike): wire batch max_retries and fix empty projection handling

Copilot review feedback on PR #6532:

- Add max_retries to the batch client policy (batch_operate/batch_write path)
- Treat empty projected feature maps as present FV slots (is not None)
- Return {} from _normalize_projected_features([]) instead of None
- Fix projection unit test mock/assertions
- Correct prewriting_hook config docstring

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* style(aerospike): format online_read docs assignment for ruff

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* chore: update pixi.lock for aerospike optional extra

Regenerate the v6 lockfile with Pixi v0.63.1 after adding the aerospike extra to pyproject.toml.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* fix(aerospike): add client init lock and batch chunking

Guard lazy client creation with a lock to avoid connection leaks under concurrent first use, and chunk batch reads/writes by batch_max_records so large materializations stay under Aerospike server batch limits.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>
aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 28, 2026
* feat: Add SparkApplicationComputeEngine for Kubernetes-native batch materialization
Adds a new batch compute engine that submits materialization jobs as
SparkApplication CRDs via the Kubeflow Spark Operator. One 'feast materialize'
call creates one SparkApplication pod that processes all feature views using
distributed Spark, rather than running in-process on the Feast server.
Key changes:
- Refactor materialize()/materialize_incremental() to pass all tasks to the
  engine in a single batch call instead of looping per feature view. Existing
  engines are unaffected (base class loops tasks internally via _materialize_one).
- Add public get_provider() method on FeatureStore.
- New spark_application engine: config, compute, job, driver script, Dockerfile.
- 12 unit tests covering config, validation, CR structure, state mapping,
  timeout, cleanup, and job naming.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

* feat: Add per-FV result reporting and clean up Dockerfile
- Pod calls apply_materialization via gRPC after each successful FV,
  setting state to AVAILABLE_ONLINE. Server reads FV state post-completion
  to determine per-FV success/failure in batched SparkApplication runs.
- registry_address is now mandatory (simplified from complex path heuristic).
- Dockerfile rewritten to install feast from source (matches K8s engine pattern).
- Unit tests updated: 15/15 pass (3 new tests for _build_per_fv_jobs).
- E2E validated: 5 FVs x 9600 rows, 5 executors, all AVAILABLE_ONLINE.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

* Minor lint & formatting change

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

* feat: Switch to ConfigMap, remove registry_address, reject file-based stores
- Config delivery: Secret → ConfigMap. Operator's ClusterRole already has
  full ConfigMap CRUD — avoids widening RBAC for Secrets in ODH.
  Matches KubernetesComputeEngine pattern.
- Removed registry_address config field. Pod inherits server's registry
  config (SQL, Snowflake) directly and writes apply_materialization()
  to the same database. Eliminates TLS certificate mounting complexity.
- Reject file-based offline stores (dask, file, duckdb) and registries
  (file) at __init__(), same as existing sqlite/faiss online store
  rejection. SparkApplication pod has ephemeral filesystem.
- Dockerfile: added PYTHONPATH/SPARK_HOME for PySpark, added pymysql.
- 20/20 unit tests pass.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

* Minor formatting

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

* fix: address PR feast-dev#6550 review — retry, validation, per-FV status
- Remove redundant get_provider(); callers use .provider property
- Replace SparkSession monkey-patch with per-thread session binding
- Smart retry: only 5xx/429; fail fast on 401/403 with RBAC hint
- Retry ConfigMap + SparkApplication creation (transient K8s errors)
- Validate env entries: require name + value or valueFrom (K8s EnvVar)
- Independent per-FV job status via CompletedMaterializationJob
- Exit 1 on any FV failure so SparkApp CR reflects partial failure
- Cleanup logs include kubectl delete command for manual recovery
- Lint: pragma allowlist on test fixture URL

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

* fix: isolate batch materialization to supports_batch engines
Restore master's per-FV materialize path for engines that do not
support batching; keep SparkApplication on the existing batch path
via ComputeEngine.supports_batch.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

* fix: address ntkathole review — dates dataclass, jobs check, UNKNOWN, cleanup
Replace __end_date__ sentinel with _MaterializationDateRange; fail fast
if engine job count mismatches; map UNKNOWN SparkApp state to WAITING;
always cleanup ConfigMap/CR after wait.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

* fix: lazy-init K8s client for spark_application engine
Defer kubeconfig load until materialize/cleanup so feast apply can
construct the engine without a cluster.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

* chore: Refresh pixi.lock after pyproject.toml dependency changes

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

* fix: skip duplicate apply_materialization for SparkApplication
Pod already writes watermarks via applies_materialization; server
batch path skips the second call to avoid duplicate intervals.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

* fix: harden supports_batch check against missing batch_engine
Use null-safe getattr so materialize falls back to the per-FV path
when batch_engine or supports_batch is absent.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>

---------

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
aniketpalu added a commit to aniketpalu/feast that referenced this pull request Jul 28, 2026
Bring in feast-dev#6550 SparkApplication (supports_batch / _materialize_fvs_batch).
Resolve feature_store.py by keeping master batch structure and threading
_force_local through transition/rollback/batch helpers so async remote-mat
does not double-set MATERIALIZING or wrongly roll back state.

Co-authored-by: Cursor <cursoragent@cursor.com>
aniketpalu pushed a commit to aniketpalu/feast that referenced this pull request Jul 28, 2026
# [0.65.0](feast-dev/feast@v0.64.0...v0.65.0) (2026-07-20)

### Bug Fixes

* add debug logging for FIPS mode detection fallback ([6c1b24e](feast-dev@6c1b24e))
* Build embedded UI from local source ([feast-dev#6525](feast-dev#6525)) ([3500349](feast-dev@3500349))
* Bump decommissioned Snowflake Python UDF runtime from 3.9 to 3.10 ([feast-dev#6606](feast-dev#6606)) ([feast-dev#6608](feast-dev#6608)) ([10341e4](feast-dev@10341e4))
* configure FIPS-compliant gRPC cipher suites for offline server ([6bc80a2](feast-dev@6bc80a2))
* Correct Flink PyArrow dependency constraints ([feast-dev#6604](feast-dev#6604)) ([70a9751](feast-dev@70a9751))
* Fix ValueError in signal handling for Trino worker threads ([feast-dev#6428](feast-dev#6428)) ([506d919](feast-dev@506d919))
* Fixed monitoring page issues ([7946018](feast-dev@7946018))
* Make pytest config compatible with newer pytest ([feast-dev#5779](feast-dev#5779)) ([a57ea33](feast-dev@a57ea33))
* Replace comma with space in DynamoDB-incompatible label tag value ([51e3a16](feast-dev@51e3a16))
* Resolve UI build warnings ([feast-dev#6529](feast-dev#6529)) ([abe92af](feast-dev@abe92af))
* Unblock nightly UI build ([feast-dev#6570](feast-dev#6570)) ([f296d4b](feast-dev@f296d4b))
* Use LONGBLOB for SQL registry proto columns on MySQL ([feast-dev#6566](feast-dev#6566)) ([7e4beb2](feast-dev@7e4beb2))

### Features

* Add click-to-zoom lightbox for blog post images ([feast-dev#6575](feast-dev#6575)) ([1cb23fd](feast-dev@1cb23fd))
* Add dark mode support to website and blog ([feast-dev#6589](feast-dev#6589)) ([7358fb8](feast-dev@7358fb8))
* Add OnlineStore for Aerospike ([feast-dev#6532](feast-dev#6532)) ([9cd35e1](feast-dev@9cd35e1))
* Add OpenLineage Consumer to Feast - receive, store, and visualize cross-producer lineage ([feast-dev#6549](feast-dev#6549)) ([a834126](feast-dev@a834126))
* Add registry list feature views by updated since ([feast-dev#6092](feast-dev#6092)) ([feast-dev#6093](feast-dev#6093)) ([006c606](feast-dev@006c606))
* Add ScyllaDB online store with vector search ([feast-dev#6508](feast-dev#6508)) ([1669661](feast-dev@1669661))
* Added compute and jobs UI ([ba2c05c](feast-dev@ba2c05c))
* Added Iceberg REST Catalog data source support ([e0a8573](feast-dev@e0a8573))
* Bring Your Own Spark - SparkApplication ([feast-dev#6550](feast-dev#6550)) ([dcd496f](feast-dev@dcd496f))
* **cassandra:** Add multi-DC support via per-datacenter execution profiles ([feast-dev#6434](feast-dev#6434)) ([0de9196](feast-dev@0de9196))
* Enhanced data source creation as a visual catalog with type-specific forms ([feast-dev#6557](feast-dev#6557)) ([d6acbba](feast-dev@d6acbba))
* Enhanced datasets UI functionality ([de11152](feast-dev@de11152))
* Implement RegistryServer.Proto RPC with RBAC-filtered response ([feast-dev#6558](feast-dev#6558)) ([feast-dev#6552](feast-dev#6552)) ([0d02614](feast-dev@0d02614))
* New zoned timestamp feature type ([feast-dev#6536](feast-dev#6536)) ([feast-dev#6537](feast-dev#6537)) ([eb042f0](feast-dev@eb042f0))
* **operator:** Auto-create RBAC for spark_application batch engine ([feast-dev#6597](feast-dev#6597)) ([f487b37](feast-dev@f487b37))
* **operator:** integrate cluster TLS profile for OCP 5.0 compliance ([43263a6](feast-dev@43263a6))
* Permissions CRUD UI and OIDC auth integration in UI ([6511da1](feast-dev@6511da1))
* Retrieve historical features from BigQuery without entity_df ([feast-dev#6569](feast-dev#6569)) ([cd5f6bb](feast-dev@cd5f6bb)), closes [feast-dev#6558](feast-dev#6558) [feast-dev#6552](feast-dev#6552)
* **spark:** SparkSource query+path and pre-computed offline read for BatchFeatureView ([feast-dev#6440](feast-dev#6440)) ([4dc8757](feast-dev@4dc8757))

### BREAKING CHANGES

* total_timeout_ms is renamed to batch_total_timeout_ms. Config files using the old name must be updated. No default value change.

Docs updated (reference + perf-tuning guide) with a short explainer on the per-attempt vs total deadline distinction. Two new unit tests pin the policy wiring: socket_timeout_ms propagates to all three scopes, and is omitted (not injected as None) when unset.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* refactor(aerospike): use MAP_KEY_ORDERED, KEY_DIGEST, and instance-scoped client

Cheap-win cleanups flagged in review, all touching the same small patch of write-path and lifecycle code.

* Map CDTs are now created with MAP_KEY_ORDERED. map_get_by_key / map_remove_by_key on an ordered map are O(log N) in the map size instead of O(N); matters on reads of wide feature views and on the update() background scan (which walks every record in the project's set).

* Writes drop POLICY_KEY_SEND and rely on the client default (POLICY_KEY_DIGEST). The serialized entity key is no longer stored alongside each record, saving per-record storage the read path never consumes (batch_operate preserves request order; results are paired back by zip in online_read).

* _client moves from a class attribute to an instance attribute (set in __init__). Previously two AerospikeOnlineStore instances could share the cached client through class state until one wrote self._client. With the instance attribute the state is always per-instance from construction.

* Drop MongoDB references from class docstrings and comments (they referred to how the storage layout was derived rather than documenting current behavior). Also rewrite the _build_batch_writes docstring to describe the policies applied on the write path.

Unit test assertions for the write-path record are updated: bw.policy is now None (client default applies) and map ops carry map_policy={'map_order': MAP_KEY_ORDERED}. All three docker-backed integration tests still pass end-to-end (cross-FV upsert, update() background scan, full feature-store round-trip), so the read/write shape survives the ordering and policy changes against a real server.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* feat(aerospike): add per-FV namespace/set overrides and prewriting hook

Adds three configuration knobs to AerospikeOnlineStoreConfig:

- namespace_overrides: pin individual feature views to a different
  Aerospike namespace (e.g. RAM-only vs. SSD-backed) without splitting
  the project across stores.
- set_overrides: place a feature view in its own set so admin ops on
  it (truncate, scan-based deletes during `feast apply`) do not touch
  records of other views.
- prewriting_hook: import-string-resolved callable invoked once per
  online_write_batch with the rows about to be written, returning the
  rows that actually go on the wire. Resolved and cached on first use;
  returning [] short-circuits the wire call.

Read, write, update and teardown paths all honour the per-FV ns/set
resolution. update() groups dropped feature views by their resolved
(ns, set) pair and issues one background scan per group. teardown()
truncates every unique (ns, set) pair the project may have written to,
including the store-level default.

Adds 22 unit tests for the new behaviour and updates 3 existing call
sites of _build_batch_writes for the new namespace= parameter. Adds a
sample hook module under examples/online_store/aerospike_overrides_and_hooks/
and corresponding sections in docs/reference/online-stores/aerospike.md.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* test: update aerospike image tag

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* chore: sync README template and secrets baseline after master merge

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* chore: fix secrets baseline line number for v1 operator types

Adding aerospike to the feast-operator enum shifted the allowlisted
SecretRef entry in api/v1/featurestore_types.go by one line.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* docs: update aerospike docs

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* fix(aerospike): wire batch max_retries and fix empty projection handling

Copilot review feedback on PR feast-dev#6532:

- Add max_retries to the batch client policy (batch_operate/batch_write path)
- Treat empty projected feature maps as present FV slots (is not None)
- Return {} from _normalize_projected_features([]) instead of None
- Fix projection unit test mock/assertions
- Correct prewriting_hook config docstring

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* style(aerospike): format online_read docs assignment for ruff

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* chore: update pixi.lock for aerospike optional extra

Regenerate the v6 lockfile with Pixi v0.63.1 after adding the aerospike extra to pyproject.toml.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>

* fix(aerospike): add client init lock and batch chunking

Guard lazy client creation with a lock to avoid connection leaks under concurrent first use, and chunk batch reads/writes by batch_max_records so large materializations stay under Aerospike server batch limits.

Signed-off-by: Valentyn Kahamlyk <valentin.kagamlyk@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants