Apache Airflow / Intermediate: runtime work, control flow and resource budgets / Lesson 11
Pools, concurrency and backpressure
Bound task admission by the resource being protected and distinguish scheduler eligibility from worker capacity.
On this page
Lesson goals
- Explain pool slots, task concurrency and active-run limits.
- Diagnose waiting work without universally increasing capacity settings.
A pool expresses a shared admission budget
A pool limits concurrent task admission for a named resource budget, such as a synthetic reporting database with a small safe workload. Different tasks can share that pool even if they belong to different DAGs. pool_slots lets a heavier task consume more than one budget unit. These units are a policy model, not a direct measurement of CPU cores or database sessions. Choose them from the protected system's sustainable behavior and the task's actual concurrency. One task that launches ten parallel remote queries can violate a supposed one-slot budget unless the policy accounts for its internal fan-out.
Several independent limits can block work
A task can have satisfied dependencies and still wait for a pool, a DAG active-task limit, active-run policy or executor/worker capacity. Increasing only one ceiling may have no effect because another remains binding. max_active_runs protects the number of overlapping DAG runs, while task-level and DAG-level concurrency settings govern different scopes. Mapped instances can also multiply demand. The scheduler and executor provide admission and dispatch evidence; downstream systems provide a separate saturation picture. Keep the scope and unit of each limit in one review table so an operator can tell which budget is actually exhausted.
Compute a weighted pool allocation offline
The local example has four slots, one heavy task using two and two light tasks using one each. All four budget units are occupied, so a further light task does not fit. This is arithmetic over a declared policy rather than a scheduler fairness or priority implementation. A task requiring more slots than the pool's total cannot become admissible simply by waiting for other tasks to finish. Diagnose that impossible request before adding workers. Deferred-task inclusion in pool accounting is configurable, so review the pool's intended policy rather than assuming a suspended task always releases every kind of capacity.
pool_total = 4
occupied_weights = [2, 1, 1]
free = pool_total - sum(occupied_weights)
assert free == 0
assert 1 > free
assert 5 > pool_total
print(free)
0
The pool is full, and a five-slot request can never fit a four-slot total under this policy.
Keep scheduling weight separate from runtime promises
The JSON design record names a synthetic pool and assigns a two-slot weight to a heavy report. It does not create a pool or configure Airflow. Translating it into a deployment needs the documented pool and operator APIs, appropriate permissions and agreement about the budget's meaning. A higher priority can influence which waiting task is preferred but does not create more slots or repair an unavailable worker. Avoid starvation by reviewing long-running heavy work alongside small urgent tasks. Choose a meaningful queue policy and measure its effect in an authorized environment rather than promising latency from a weight alone.
{"pool":"lab-reporting-budget","total_slots":4,"heavy_task_slots":2,"light_task_slots":1,"include_deferred":"review-explicitly"}
This is documentation data, not an Airflow administrative payload or a tested configuration.
Treat waiting as a signal rather than an automatic defect
A bounded queue can be intentional backpressure that protects an external dependency. Removing limits because tasks are waiting can move the failure into database overload, timeouts and retries that amplify demand. Compare arrival rate, completion rate, wait age and destination health before changing admission. A queued state also needs executor-specific evidence; it is not proof that a pool alone is responsible. This lesson runs no pool command, worker or remote query. Its arithmetic verifies one weighted allocation, and the unexecuted design record remains clearly separate from actual scheduler configuration and service-capacity evidence.
Common errors and fixes
A five-slot task waits forever in a four-slot pool.
- Why it happens
- The admission request exceeds the total budget.
- How to fix it
- Review task weight and resource policy through an authorized configuration plan.
- Prevent it next time
- Check every task weight against its pool total.
Adding workers does not release a pool-limited queue.
- Why it happens
- Execution capacity was confused with admission capacity.
- How to fix it
- Identify the binding limit before changing infrastructure.
- Prevent it next time
- Expose pool, DAG and executor limits separately.
Removing a limit causes downstream timeouts and more retries.
- Why it happens
- Intentional backpressure was removed without dependency evidence.
- How to fix it
- Restore a reviewed budget and diagnose sustainable completion capacity.
- Prevent it next time
- Correlate queue age with destination health.
Check your understanding
1. Can a priority increase make a task fit a pool with no free slots?
Show worked solution
No. Priority can affect selection among admissible waiting work but does not manufacture capacity. Confirm available slots, task weight and other concurrency limits. If the pool protects a saturated dependency, increasing its size may make the system less reliable rather than reduce end-to-end completion time.
2. Why must a one-slot task’s internal ten-way remote fan-out be reviewed?
Show worked solution
The slot is a policy unit, and its protective meaning fails if the task consumes far more external concurrency than assumed. Either adjust task behavior or the budget model based on measured resource costs. Document the relationship so future code changes do not silently defeat the pool’s intended protection.
Key takeaways
- Admission budgets and worker capacity are separate limits.
- Backpressure should protect a measured resource contract.