Symptom
A scheduled run didn't do any work, and no task failed. You see one of these:
- The run sits in Queued while downstream data goes stale.
- The run shows as Skipped, sometimes for hours of consecutive schedules.
- Runs from many unrelated jobs queue at the same time.
- A trigger or API call returns
429 Too Many Requests.
These runs usually aren't failures, so failure alerts may stay silent.
Which limit did you hit?
JOB_ID=123456
# Current runs, including queued ones
databricks jobs list-runs --job-id "$JOB_ID" --active-only -o json | \
jq '.[] | {run_id, start_time, state: (.status.state // .state.life_cycle_state), queue: .status.queue_details}'
# The job's concurrency and queue settings
databricks jobs get "$JOB_ID" -o json | \
jq '.settings | {max_concurrent_runs, queue, schedule}'
# Why one finished run ended the way it did
databricks jobs get-run <run-id> -o json | jq '.status'
Older CLI versions may return only the deprecated state object, so upgrade if status is missing.
| What you see | Meaning | Fix |
|---|---|---|
Queue code MAX_CONCURRENT_RUNS_REACHED | This job is at its own max_concurrent_runs | Find the long-running run, or raise the limit if overlap is safe |
Queue code ACTIVE_RUNS_LIMIT_REACHED | The workspace is at its active task run limit | Find what's flooding the workspace, then spread out schedules |
Queue code ACTIVE_RUN_JOB_TASKS_LIMIT_REACHED | Too many active Run job tasks in the workspace | Reduce nested job fan-out |
Termination code MAX_CONCURRENT_RUNS_EXCEEDED | Job at its limit, with queueing off | Fix the long run and enable queueing |
Termination code WORKSPACE_RUN_LIMIT_EXCEEDED | Workspace at its concurrent active run quota | Spread the runs over a longer time window |
Termination code CLUSTER_REQUEST_LIMIT_EXCEEDED | Cluster create, start and upsize requests hit the rate limit | Spread out starts; share job clusters across tasks where you can |
Termination code MAX_JOB_QUEUE_SIZE_EXCEEDED | The queue itself is full | Clear the backlog and find the cause |
Termination code SKIPPED | The run never executed, for example the upstream task failed or a dependency condition wasn't met | Check the upstream task and the run-if condition |
| Every new run skipped, nothing active | max_concurrent_runs is set to 0 | Set it back to a positive value |
How the settings interact
max_concurrent_runsdefaults to1and can't exceed1000. Setting0skips all new runs, which is an easy way to leave a job silently disabled after an incident.- With queueing on, a run that hits a limit waits for up to 48 hours. With queueing off, a run that hits the job's limit is skipped.
- Queueing is on by default only for jobs created in the UI after April 15, 2024. Jobs created earlier or from the API, Terraform or bundles may have it off, so check the setting rather than assume.
- Queueing is set per job. Enabling it on one job doesn't protect the others.
The usual root cause: one stuck run
With max_concurrent_runs: 1, a single hung run blocks every later schedule. Check how long the active run has been going:
databricks jobs list-runs --job-id "$JOB_ID" --active-only -o json | \
jq -r '.[] | [.run_id, (.start_time/1000 | todate), (.status.state // .state.life_cycle_state)] | @tsv'
If the oldest run is far past its normal duration, that's the incident to fix before you change any limits.
Current workspace limits
Fixed limits at the time of writing (check the current limits, as these change):
| Limit (per workspace) | Value |
|---|---|
| Tasks running at once (excludes Run job and For each parent tasks) | 2,000 |
| Run job and For each parent tasks running at once | 750 |
| Jobs created per hour | 10,000 |
/jobs/run-now API calls per second | 20 |
Find the pattern with system tables
Runs dropped in the past week, by job and reason:
SELECT workspace_id, job_id, termination_code,
COUNT(*) AS runs, MAX(period_start_time) AS latest
FROM system.lakeflow.job_run_timeline
WHERE period_start_time >= current_timestamp() - INTERVAL 7 DAYS
AND result_state IS NOT NULL
AND termination_code IN ('SKIPPED', 'MAX_CONCURRENT_RUNS_EXCEEDED',
'WORKSPACE_RUN_LIMIT_EXCEEDED', 'CLUSTER_REQUEST_LIMIT_EXCEEDED',
'MAX_JOB_QUEUE_SIZE_EXCEEDED')
GROUP BY workspace_id, job_id, termination_code
ORDER BY runs DESC;
The busiest scheduled start minutes, which show a top-of-the-hour stampede:
WITH starts AS (
SELECT workspace_id, run_id, MIN(period_start_time) AS started
FROM system.lakeflow.job_run_timeline
WHERE period_start_time >= current_timestamp() - INTERVAL 7 DAYS
AND trigger_type = 'CRON'
GROUP BY workspace_id, run_id
)
SELECT workspace_id, date_trunc('MINUTE', started) AS start_minute, COUNT(*) AS runs
FROM starts
GROUP BY workspace_id, date_trunc('MINUTE', started)
ORDER BY runs DESC
LIMIT 20;
A few notes on reading these results:
result_stateis populated only on a run's final row. Runs longer than an hour are split into hourly rows, so aggregate byrun_id.- A run that never started has
period_start_timeequal toperiod_end_time. queue_duration_secondsonly has values for single-task legacy API jobs. It logs0for other runs, so don't use it to rule out queueing.- System tables lag behind live data. During an incident, use the CLI output above.
Clear a backlog safely
First decide whether the queued runs still need to run. If each run processes the latest data, one run is enough, and the rest just waste compute. If each run has its own parameters, such as a date, cancelling them loses work unless you re-trigger those dates.
Cancel only the queued runs of one job, one at a time:
databricks jobs list-runs --job-id "$JOB_ID" --active-only -o json | \
jq -r '.[] | select((.status.state // .state.life_cycle_state) == "QUEUED") | .run_id' | \
while read -r RUN_ID; do
echo "Cancelling queued run $RUN_ID"
databricks jobs cancel-run "$RUN_ID" --no-wait
done
Caution
Be careful with
databricks jobs cancel-all-runs. It cancels active runs, not just queued ones. With--all-queued-runsand no--job-id, it can cancel queued runs across the whole workspace. Always pass--job-id, and prefer the loop above when a run is mid-write.
Warning
Cancelling a run that's writing data can leave a partial load. Delta commits are atomic per write, but a multi-step job can stop between steps. Check that your tasks are safe to re-run before you cancel anything in the
RUNNINGstate.
Release the queue gradually. If a shared source was down, hundreds of released runs can hit the cluster request rate limit and fail again.
Fix the settings
Set the limit and enable queueing:
databricks jobs update --json '{
"job_id": 123456,
"new_settings": {
"max_concurrent_runs": 1,
"queue": {"enabled": true}
}
}'
Raise max_concurrent_runs above 1 only if overlapping runs are safe. Two runs loading the same target table can duplicate data or cause write conflicts.
Move schedules off the top of the hour. This example starts at 02:07 UTC:
databricks jobs update --json '{
"job_id": 123456,
"new_settings": {
"schedule": {
"quartz_cron_expression": "0 7 2 * * ?",
"timezone_id": "UTC"
}
}
}'
Tip
If the job is managed by Terraform or Asset Bundles, make the change there too. Otherwise the next deploy puts the old value back.
Clients that trigger runs through the API should retry 429 responses with exponential backoff and jitter.
Prevent it next time
- Set
timeout_secondson every job. A timeout stops a hung run from blocking the queue indefinitely. - Alert on duration and on missing outcomes. Use a duration threshold notification, and a scheduled query on
system.lakeflow.job_run_timelinefor the termination codes above. - Enable queueing on every production job, and set it in your job templates so that API-created jobs get it too.
- Stagger schedules. Give each team a minute offset instead of
0 0 * * * ?for everything. - Limit nested fan-out. Run job and For each tasks have their own lower workspace limit.
- Review
max_concurrent_runs: 0after incidents.