Thang Kieu
12/17/2025, 10:46 AMMarvin
12/17/2025, 10:46 AMMarvin
12/17/2025, 10:50 AMprefect worker start --limit N). If N≈100, the next 200 remain Scheduled → Late; they’re picked up only as slots free.
- Work queue/work pool limit: A concurrency limit on the queue/pool may cap the number of runs that can be fetched.
- Deployment concurrency limit: If set lower than needed, extra runs are queued (“AwaitingConcurrencySlot”). Note: if deployment limits were blocking, you’d typically see “AwaitingConcurrencySlot” instead of “Late.”
- Kubernetes capacity/quotas: If the cluster doesn’t have ≥180 CPUs/GB free, k8s will delay scheduling pods. Prefect runs linger in Scheduled and get marked Late until pods can actually start. This often looks like “one new pod starts when one finishes.”
- Image pull or API throttling: Slow image pulls or API rate limits can make worker submissions slow enough that many runs go Late, then start in a slow trickle.
How to pinpoint the bottleneck quickly
Prefect side:
- Check the run state names for the waiting runs:
- “Late” = no worker picked them up in time.
- “AwaitingConcurrencySlot” = blocked by a Prefect concurrency limit (usually deployment-level).
- Check deployment(s): is a concurrency_limit set, and what is the collision strategy?
- Check work pool/queue: any concurrency limit configured?
- Check workers:
- How many Kubernetes workers are running?
- What --limit is each worker using? Combined limit should be ≥ your target concurrent runs.
Kubernetes side:
- Are new pods Pending? kubectl get pods and kubectl describe pod to see events like insufficient CPU/Memory, quota, or taints.
- Check namespace ResourceQuotas/LimitRanges.
- If using a cluster autoscaler, check scale-up latency.
- Check image pull times and registry throttling.
Remediations
- Match capacity to demand:
- Run enough Kubernetes workers and set --limit high enough to cover your desired concurrency (e.g., total ≥180).
- Ensure deployment/work queue/pool concurrency limits (if any) are not lower than your target.
- Verify the cluster actually has resources for 180 pods at 1 CPU/1 GiB each (and room for system overhead).
- If Late is just noise (expected backlog), increase the Late threshold on the server so runs don’t get marked Late so quickly:
- Set PREFECT_API_SERVICES_LATE_RUNS_AFTER_SECONDS (e.g., 300) on your Prefect API/Server.
- If image pulls are the bottleneck, use a node-local registry cache, pre-pull images, or smaller images.
A few clarifying questions to help us narrow it down:
1) Where is the “180 runs concurrency limit” set—deployment, work queue/pool, or worker --limit?
2) How many Kubernetes workers are you running, and what is each worker’s --limit?
3) In the UI, do waiting runs show “Late” or “AwaitingConcurrencySlot”?
4) Do you see many pods Pending in Kubernetes when this happens? If so, what do kubectl describe events say?Thang Kieu
12/17/2025, 12:52 PMMarvin
12/17/2025, 12:52 PMMarvin
12/17/2025, 12:56 PM--limit, that can further cap each worker’s local concurrency.
- If your Kubernetes cluster were the bottleneck, you’d typically see runs progress to Pending/Running while pods sit Pending at the cluster level. Since you’re seeing Late, the bottleneck is before K8s scheduling (i.e., the runs aren’t even being claimed).
Options to fix or improve
- Balance or increase per-pool capacity:
- Spread runs evenly across all six pools (e.g., via multiple deployments pointed at different pools).
- Raise the concurrency limit on the specific pool(s) taking the burst (if your cluster can handle it).
- Ensure both workers per pool are healthy and not started with a very small --limit.
- If “Late” is just expected during bursts and you want less noise, increase the lateness threshold on the Prefect server:
# On your Prefect API/Server
export PREFECT_API_SERVICES_LATE_RUNS_AFTER_SECONDS=300
That means runs won’t be marked Late until 5 minutes after their scheduled time.
- If claim speed is the issue (not the cap), add more workers to the hot pools or reduce worker poll intervals so they claim faster. But the pool limit will still enforce the cap of 30 per pool.
A couple of quick questions to pinpoint it:
- Are the 200 new runs mostly going to the same 1–2 pools?
- Did you set any --limit on the workers?
- In the UI, do those pools show ~30 active runs when the backlog is Late? If yes, that’s the pool cap doing its job.
If you can share one Late run’s deployment and pool and a screenshot of that pool’s active count, I can confirm and suggest the exact adjustment.