Skip to content

Commit 29dd99d

Browse files
authored
Bound the scheduler's deserialized Dag cache (#71704)
The scheduler kept every Dag version it deserialized in a mapping that never evicted, so a long-running scheduler grew with the number of versions it had ever seen until it was restarted or OOM killed. Deployments that redeploy Dags frequently accumulate versions fastest and hit this soonest. A least-recently-used cap is the only thing that bounds this outright. An idle timeout would not: the scheduler re-checks an entry on each lookup, which re-arms its expiry, so a timeout reclaims a version only once its runs finish and it stops being requested, leaving memory a function of the concurrently active set rather than a fixed ceiling. Deliberately not configurable here, so the fix stays small enough to cherry-pick. Cache activity currently reports under the existing api_server.dag_bag.* metrics; a scheduler-specific namespace, along with configuration, follows separately. closes: #69001
1 parent 14ab725 commit 29dd99d

3 files changed

Lines changed: 24 additions & 2 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The scheduler's Dag cache is now a bounded LRU of 512 versions, so scheduler memory no longer grows with every Dag version the process has ever seen.

airflow-core/src/airflow/jobs/scheduler_job_runner.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@
155155
TASK_STUCK_IN_QUEUED_RESCHEDULE_EVENT = "stuck in queued reschedule"
156156
""":meta private:"""
157157

158+
SCHEDULER_DAG_CACHE_SIZE = 512
159+
"""
160+
Max deserialized Dag versions the scheduler keeps in memory.
161+
162+
The scheduler reaches its DagBag through the Dag version of each active Dag run, so an
163+
unbounded cache retains every version the process has ever seen and grows for the life of
164+
the process. Sized to sit above the versions-with-runs-in-flight working set of a typical
165+
deployment, so eviction costs a re-fetch only where that working set is genuinely larger.
166+
167+
:meta private:
168+
"""
169+
158170
# Per-tick cap on pending AssetPartitionDagRun rows the scheduler evaluates.
159171
# Bounds the per-tick transaction so executor heartbeats and regular scheduling
160172
# aren't starved; remaining APDRs drain across subsequent ticks.
@@ -370,7 +382,7 @@ def __init__(
370382
if log:
371383
self._log = log
372384

373-
self.scheduler_dag_bag = DBDagBag(load_op_links=False)
385+
self.scheduler_dag_bag = DBDagBag(load_op_links=False, cache_size=SCHEDULER_DAG_CACHE_SIZE)
374386

375387
# Set of (dag_id, asset_name, asset_uri) tuples for trigger policies that
376388
# are permanently unreachable for the rollup window's cardinality — the

airflow-core/tests/unit/jobs/test_scheduler_job.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
from airflow.executors.executor_utils import ExecutorName
6565
from airflow.executors.local_executor import LocalExecutor
6666
from airflow.jobs.job import Job, run_job
67-
from airflow.jobs.scheduler_job_runner import SchedulerJobRunner
67+
from airflow.jobs.scheduler_job_runner import SCHEDULER_DAG_CACHE_SIZE, SchedulerJobRunner
6868
from airflow.models.asset import (
6969
AssetActive,
7070
AssetAliasModel,
@@ -414,6 +414,15 @@ def test_executor_loaded_in_scheduler_job(self, mock_init_executors, mock_defaul
414414
assert scheduler_job.executor == mock_local_executor
415415
assert scheduler_job.executors == [mock_local_executor]
416416

417+
def test_scheduler_dag_bag_is_bounded(self):
418+
"""The scheduler's Dag cache must evict, or it retains every version it has ever seen."""
419+
from cachetools import LRUCache
420+
421+
job_runner = SchedulerJobRunner(Job())
422+
423+
assert isinstance(job_runner.scheduler_dag_bag._dags, LRUCache)
424+
assert job_runner.scheduler_dag_bag._dags.maxsize == SCHEDULER_DAG_CACHE_SIZE
425+
417426
@pytest.mark.parametrize(
418427
"heartrate",
419428
[10, 5],

0 commit comments

Comments
 (0)