Replies: 1 comment
|
Your loop analysis is right, but the arithmetic on the probe is off in a way that changes the fix.
def health_check_threshold(job_type: str, heartrate: int) -> int | float:
grace_multiplier = 2.1
if job_type == "SchedulerJob":
health_check_threshold_value = conf.getint("scheduler", "scheduler_health_check_threshold")
...
else:
health_check_threshold_value = heartrate * grace_multiplierThe 2.1x grace multiplier other job types get is skipped for the scheduler. It compares Second: a single slow loop can't restart your pod. The chart's default is livenessProbe:
initialDelaySeconds: 10
timeoutSeconds: 20
failureThreshold: 5
periodSeconds: 60That's 5 consecutive failures 60s apart — five straight minutes in which every probe sees a >30s-stale heartbeat. That isn't one big expansion, it's sustained back-to-back long loops. Worth confirming before you tune anything: if pods are dying more often than once per ~5 min, you're chasing the wrong thing. One more that bites people — Unless you've set For the fix, you don't have to throttle anything. Raise the threshold so it reflects your worst-case loop rather than your average: config:
scheduler:
scheduler_health_check_threshold: '300'That's the legitimate fix for a false positive — you're telling the probe what "hung" actually means for this workload. Keep it under the 5x60s window or you've just moved the goalpost. Then look hard at idle_in_this_run = not num_queued_tis and not num_finished_events
if not is_unit_test and idle_in_this_run:
time.sleep(min(self._scheduler_idle_sleep_time, next_event or 0))With work pending it comes straight back around, no sleep. What you gain is a heartbeat write between chunks instead of only after all 128. With 2 replicas that lever does double duty. A 128-TI critical section holds locks the other scheduler is blocking on, so your two schedulers are partly serializing against each other — likely the same DB contention you noted getting masked. Try 32 and watch the Last one, if your mapped DAGs are large: check |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Hi everyone,
We are currently running Airflow 3 deployed via the official Helm chart in a Kubernetes environment using
KubernetesExecutorwith2scheduler replicas.Recently, we encountered severe scheduler instability when running DAGs with large-scale dynamic task mapping (generating several thousand tasks per run). The main symptom is that during heavy task expansion, the scheduler pod's liveness probe repeatedly fails, causing Kubernetes to restart the scheduler pods while they are actively processing tasks.
⚙️ Environment & Configuration
KubernetesExecutor(workers spawned as individual K8s pods)🔍 Deep Dive: Problem Mechanics & Root Cause
After analyzing the Airflow execution loop (
job.py&scheduler_job_runner.py), we identified the exact flow leading to these false-positive restarts:Because the main loop executes back-to-back without breathing room, there is almost no window for the health check to pass. The pod gets killed by Kubernetes even though it is performing heavy, valid scheduling work.
❓ Key Question: How to Scale Without Throttling?
Our primary goal is to maintain high parallelism and scale. We do NOT want to reduce the number of dynamic tasks or lower the task concurrency limits—we want Airflow to execute as many tasks simultaneously as possible.
With that in mind, how can we resolve this bottleneck and probe failure while maximizing throughput?
Any insights, recommended config settings, or architectural advice from high-volume production setups would be greatly appreciated!
Thanks in advance!
All reactions