Skip to content

refactor: remove unnecessary task table join from run status query#486

Open
npow wants to merge 5 commits into
masterfrom
fix/run-status-no-task-join
Open

refactor: remove unnecessary task table join from run status query#486
npow wants to merge 5 commits into
masterfrom
fix/run-status-no-task-join

Conversation

@npow

@npow npow commented Jun 26, 2026

Copy link
Copy Markdown

Summary

The lateral join to the task table added in #485 is redundant and can be removed:

  • The tasks_heartbeat endpoint (/tasks/{task_id}/heartbeat) already calls update_heartbeat on both the run table and the task table (see metadata_service/api/task.py line 333).
  • Therefore run.last_heartbeat_ts is always kept in sync with the latest task heartbeat.
  • The existing WHEN run.last_heartbeat_ts IS NOT NULL AND ... THEN 'running' check is sufficient — it already covers the case where tasks are actively running.

Net change vs master: removes the dead task_table attribute and MetadataTaskTable import that were already unused before #485 was written.

What this replaces

Supersedes #485. The performance concern raised during that review (40ms × 50 lateral calls, 2s list latency) goes away entirely since there is no join.

Test plan

  • Existing test_run_status_running_with_heartbeat covers the running case via run.last_heartbeat_ts
  • test_run_status_failed_with_heartbeat_expired_and_no_failed_tasks reverted to its original assertion — if run.last_heartbeat_ts is expired, the run is failed regardless of task heartbeats (because in production, task heartbeats also keep run.last_heartbeat_ts fresh; an expired run HB with a fresh task HB is an impossible steady-state scenario)
  • CI green

🤖 Generated with Claude Code

npow and others added 5 commits June 25, 2026 22:35
…artbeat expired

When a run's orchestrator-level heartbeat expires (e.g. after a transient failure
in a foreach coordinator) but individual tasks are still heartbeating, the UI was
incorrectly showing the run as 'failed' even with active tasks.

Fix: add a LEFT JOIN LATERAL that pre-computes max(last_heartbeat_ts) across all
tasks for each run, then use it as a fallback in both the `status` and `finished_at`
CASE expressions before the final ELSE 'failed'. Uses the same RUN_INACTIVE_CUTOFF_TIME
threshold (6 min default) as the run-level heartbeat check for consistency.

The join is computed once per run row and the result referenced from both expressions,
avoiding the O(runs×tasks) cost that an inline correlated EXISTS would incur on list
endpoints (which also call find_records with enable_joins=True).

Reproducer: foreach flow with 200+ tasks where one task hits a transient
infrastructure error; orchestrator heartbeat lapses but remaining tasks continue.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…tics

When a task has an active heartbeat, the run now correctly shows 'running'
even if the run-level heartbeat has expired. Update the test that was
asserting 'failed' in this scenario — it was written under the old
semantics where run heartbeat was the sole signal.

Also apply black formatting to pass pre-commit.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The previous fix was too broad: any run with an expired run heartbeat
would check task heartbeats and potentially show 'running', even for
zombie runs dead for days.

Add a staleness bound (RUN_INACTIVE_CUTOFF_TIME * 10 = 3600s): the task-HB
fallback only fires when the run heartbeat expired recently. A 7-day-old
run heartbeat (604800s >> 3600s) still falls through to 'failed'; a
7-minute-old run heartbeat (420s <= 3600s) gets the task-HB check.

Also revert the test assertion that was incorrectly updated to expect
'running' for the zombie-run scenario — it should remain 'failed'.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…oof of liveness

The staleness bound on the run heartbeat was unnecessary. The lateral JOIN
already constrains to task heartbeats within RUN_INACTIVE_CUTOFF_TIME (360s),
so any task HB that passes the check is genuinely fresh. No scenario exists
where a <6-min-old task HB is wrong evidence that a task is alive.

Restore test assertion to 'running' — the prior 'failed' expectation was
documenting the old buggy behavior that this PR is fixing.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The lateral join added in #485 is redundant: the task heartbeat endpoint
(tasks_heartbeat) already calls update_heartbeat on both the run table and
the task table, so run.last_heartbeat_ts is always kept in sync with the
latest task heartbeat. The existing CASE expression checking
run.last_heartbeat_ts is therefore sufficient.

Also removes the now-unused task_table attribute and MetadataTaskTable import.

Closes #485
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant