Skip to content

fix: Report real materialization status for Kubernetes async runs and in the UI - #7153

Open
patelchaitany wants to merge 3 commits into
feast-dev:masterfrom
patelchaitany:feat/compute-engines-running-status
Open

patelchaitany wants to merge 3 commits into
feast-dev:masterfrom
patelchaitany:feat/compute-engines-running-status

Conversation

@patelchaitany

@patelchaitany patelchaitany commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

Materialization status was wrong in two places: the Kubernetes engine reported success before any data was written, and the UI could never show a run in progress. This PR fixes both. Supersedes #7154.

1. Kubernetes engine: wait for the Job when synchronous: false

With synchronous: false, the Kubernetes compute engine created one Job for all pods and returned it right away, while the Job was still WAITING. materialize() treats any returned job that isn't ERROR as done, so it:

  • saved the materialization interval,
  • moved the feature view to AVAILABLE_ONLINE,
  • sent an OpenLineage COMPLETE event,

all before a single row reached the online store. If the pods later failed, Feast never found out. The UI and lineage still showed the run as successful.

Now synchronous: false waits for the Job too, using the same _await_path_materialization loop the synchronous path already uses. synchronous now only decides how pods are grouped:

  • true: run the pods in Jobs of job_batch_size, one Job after another.
  • false: run all pods in a single Job.

Either way, the feature view is marked materialized only after the Job succeeds. ConfigMap cleanup and pod-log printing on failure now run in both modes.

Because every run now goes through this wait loop, KubernetesMaterializationJob.status() has to recognize when a Job has finished. It used to read only conditions[0]. On Kubernetes 1.31+, SuccessCriteriaMet can come before Complete, and FailureTarget before Failed (terminal Job conditions). With no active pods, the status stayed WAITING forever. It now checks every condition for Complete or Failed, and reports RUNNING only while pods are active.

This also adds the update and teardown_infra methods that ComputeEngine requires. Without them, KubernetesComputeEngine(...) raised TypeError: Can't instantiate abstract class. Each run creates and deletes its own Job and ConfigMap, so there is no long-lived infrastructure, and both methods do nothing. The Spark application engine does the same.

Behavior change: feast materialize with synchronous: false now blocks until the Job finishes instead of returning right away.

2. UI: show runs in progress in the Jobs tab

The Compute & Jobs → Jobs tab builds its rows only from saved materialization intervals. An interval is saved only after a run succeeds, so a feature view that was being materialized looked exactly like an idle one. Every row said SUCCEEDED, and the Running counter was always 0.

The registry already knows when a run is in progress: materialize() moves the feature view to MATERIALIZING before it starts and moves it on when it ends. This PR passes that state through to the UI.

  • GET /compute_engines (REST) now returns each feature view's lifecycle state, such as "MATERIALIZING". It is null when the state was never set.
  • useLoadComputeEngine reads the new state field.
  • The Jobs tab adds a RUNNING row for every view in MATERIALIZING, and the Running counter and filter count it. Range start and end show —, because the registry doesn't know the date range until the run finishes.

Screenshots

Both screenshots use the same local Feast repo. driver_hourly_stats has two finished runs and is in the MATERIALIZING state. Both builds run against the real REST API.

Before: the in-progress run doesn't appear, and Running = 0.

Before

After: a RUNNING row appears for driver_hourly_stats, and Running = 1.

After

Which issue(s) this PR fixes:

N/A

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

Unit tests:

  • sdk/python/tests/unit/infra/compute_engines/test_k8s_engine.py builds a real KubernetesComputeEngine with synchronous=False and a real KubernetesMaterializationJob. Only the Kubernetes API is mocked. The Job goes from pending to running, then ends with one of two Kubernetes 1.31+ condition lists:

    • [SuccessCriteriaMet, Complete], which must give SUCCEEDED
    • [FailureTarget, Failed], which must give ERROR

    The test checks that one Job is created with all paths and that materialize waits for the final status. Both cases fail on master. The test is skipped when the kubernetes package isn't installed.

  • sdk/python/tests/unit/api/test_compute_engines_api.py checks that the endpoint returns state: MATERIALIZING, or None when unset.

  • ui/src/pages/compute-engines/Index.test.ts checks two cases. A MATERIALIZING view gets a RUNNING row on top of its finished runs. An AVAILABLE_ONLINE view with no intervals gets no rows.

Manual test: the UI screenshots above. The Kubernetes change has not been tested against a real cluster.

Misc

Failed runs still don't appear in the Jobs tab. The registry doesn't store failures: on error, the view goes back to GENERATED and no interval is saved. That needs a separate change.

The Jobs tab is built only from saved materialization intervals, and an
interval is saved only after a run succeeds. A feature view that is being
materialized right now looked the same as an idle one.

The /compute_engines REST endpoint now returns each feature view's
lifecycle state, and the Jobs tab adds a RUNNING row for views in the
MATERIALIZING state. The date range shows a dash because the registry
does not know the range until the run finishes.

Signed-off-by: Chaitany Patel <[email protected]>
@patelchaitany
patelchaitany requested a review from a team as a code owner November 13, 2026 10:05
@codecov-commenter

codecov-commenter commented Nov 13, 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 91.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.69%. Comparing base (021e9af) to head (48a32ee).
⚠️ Report is 11 commits behind head on master.

Files with missing lines Patch % Lines
...pute_engines/kubernetes/k8s_materialization_job.py 83.33% 1 Missing and 1 partial ⚠️
❗ 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    #7153      +/-   ##
==========================================
+ Coverage   52.32%   52.69%   +0.37%     
==========================================
  Files         459      459              
  Lines       58095    58181      +86     
  Branches     8560     8578      +18     
==========================================
+ Hits        30397    30659     +262     
+ Misses      25614    25433     -181     
- Partials     2084     2089       +5     
Flag Coverage Δ
go-feature-server 30.96% <ø> (ø)
python-unit 54.22% <91.66%> (+0.39%) ⬆️
Files with missing lines Coverage Δ
.../python/feast/api/registry/rest/compute_engines.py 32.38% <ø> (+14.28%) ⬆️
...ast/infra/compute_engines/kubernetes/k8s_engine.py 67.07% <100.00%> (+67.07%) ⬆️
...pute_engines/kubernetes/k8s_materialization_job.py 84.84% <83.33%> (+84.84%) ⬆️

... and 12 files with indirect coverage changes


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 021e9af...48a32ee. 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.

With synchronous=false the Kubernetes compute engine returned a job that
was still WAITING. Materialize then recorded the interval, set the feature
view to AVAILABLE_ONLINE and emitted an OpenLineage COMPLETE event before
any data was written, and pod failures were never reported.

Now both modes wait for the Job to finish. synchronous only controls
whether pods run in batches of job_batch_size or all in one Job.

Also add the update/teardown_infra hooks the ComputeEngine base class
requires, so the engine can be instantiated again. Each run creates and
cleans up its own Job and ConfigMap, so both are no-ops.

Signed-off-by: Chaitany Patel <[email protected]>
@patelchaitany patelchaitany changed the title feat: Show running materialization in the compute engines Jobs tab fix: Report real materialization status for Kubernetes async runs and in the UI Nov 13, 2026

@franciscojavierarceo franciscojavierarceo 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.

the new default wait path depends on terminal Job conditions being recognized. that needs to work before we make every Kubernetes materialization wait.

# feature view to AVAILABLE_ONLINE before any data is written.
total_pods = len(paths)
job = self._await_path_materialization(
paths, feature_view, 0, total_pods, total_pods

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 now sends synchronous=False through a poller whose KubernetesMaterializationJob.status() only examines conditions[0]. on Kubernetes 1.31+, SuccessCriteriaMet can precede Complete, and FailureTarget can precede Failed (Job conditions). with those condition lists and no active pods, the status stays WAITING even after the Job finishes, so this new default path cannot finish recording the materialization and reporting its outcome. we need to recognize terminal conditions anywhere in the list and add success/failure regression cases using real KubernetesMaterializationJob status parsing before routing the default mode through this loop.

KubernetesMaterializationJob.status() only looked at conditions[0]. On
Kubernetes 1.31+ a Job can report SuccessCriteriaMet before Complete, or
FailureTarget before Failed. With no active pods the status then stayed
WAITING forever, so the materialization wait loop never finished.

Check every condition for Complete or Failed, and report RUNNING only
while pods are active.

Signed-off-by: Chaitany Patel <[email protected]>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Devin Review: 2 flags

Not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

This branch has not been deployed

No deployments
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.

4 participants