Apologies for missing these in the above email:
I propose adding a mapping-valued pool_requirements argument to operators
and TaskFlow decorators:
BashOperator(
task_id="transfer",
bash_command="run-transfer",
pool_requirements={
"source_database": 1,
"destination_database": 2,
},
)
Existing declarations would remain valid and retain their current behavior:
BashOperator(
task_id="existing_task",
bash_command="run-job",
pool="database",
pool_slots=2,
)
Internally, the existing form would be equivalent to one requirement:
{"database": 2}
A mapping avoids the length and ordering problems that would come with
separate pools and pool_slots lists.
On Fri, Sep 11, 2026 at 6:21 PM Ramachandra Nalam <[email protected]>
wrote:
> Hi Airflow community,
>
> I would like to start a discussion about allowing a single task to require
> slots from more than one Airflow pool. This follows issue #13975:
>
> https://github.com/apache/airflow/issues/13975
>
> The issue already has maintainer feedback that this change should go
> through the AIP process because it affects core scheduling and must remain
> correct with multiple schedulers. I agree. This email is intended to start
> that discussion before any production implementation PR is opened.
>
> Problem
>
> A task currently has one pool and one pool_slots value. That works when
> the task uses one constrained resource, but it cannot describe a task that
> needs several independently limited resources at the same time.
>
> For example, a transfer task may need one connection from a source
> database pool and two connections from a destination database pool. Other
> tasks may use either database independently. A synthetic pool for the
> source-and-destination combination would not share capacity correctly with
> those tasks, and creating a pool for every possible resource combination
> does not scale.
>
> Proposed user interface
>
> I propose adding a mapping-valued pool_requirements argument to operators
> and TaskFlow decorators:
>
> Existing declarations would remain valid and retain their current behavior:
>
> Internally, the existing form would be equivalent to one requirement:
>
> A mapping avoids the length and ordering problems that would come with
> separate pools and pool_slots lists.
>
> Scheduling semantics
>
> A task instance should move to queued only when every required pool:
>
> - Exists
> - Is compatible with the task’s DAG team
> - Has the requested capacity
>
> The scheduler should check the complete requirement set inside its
> existing pool-locking critical section. It should never reserve one pool
> while waiting for another.
>
> Once the task passes all pool, DAG, task, executor, and team checks, the
> scheduler can queue it and debit every required pool in the same scheduling
> decision. A failed check or rolled-back transaction must leave no partial
> allocation.
>
> Slot occupancy should be calculated separately for each pool. This matters
> for include_deferred: a deferred task may continue to occupy one required
> pool while releasing another if the two pools have different settings.
>
> Storage and compatibility
>
> The current scalar pool and pool_slots columns are used by task-instance
> creation, pool statistics, dependency checks, scheduler filtering, APIs,
> history, and the UI. Replacing them in one release would require a large
> backfill and could make mixed-version operation unsafe.
>
> My proposed compatibility model is to:
>
> - Add normalized task-instance pool-requirement rows
> - Retain the scalar columns as a compatibility projection
> - Write normalized rows for new or refreshed task instances
> - Read the existing scalar values as one requirement when normalized
> rows are absent
> - Reject or feature-gate multiple-pool DAGs until every active
> scheduler understands the new representation
>
> This avoids a synchronous full-table backfill of task_instance. Pool
> accounting can combine normalized rows with legacy rows that do not yet
> have a normalized representation, without double counting them.
>
> Scope
>
> The change would cover:
>
> - Task SDK
> - DAG serialization
> - Mapped operators
> - Task-instance creation and retry history
> - Pool accounting
> - PoolSlotsAvailableDep
> - Scheduler candidate filtering
> - Public API responses and filters
> - UI task details
> - Metrics
> - Upgrade documentation
>
> The first version would not support:
>
> - Dynamic pool acquisition during task execution
> - Automatic requirements derived from Connections
> - Alternative requirements such as “A or B”
> - Preemption
> - A new fairness policy
>
> Multi-resource tasks can make the scheduler’s existing starvation behavior
> more visible. AIP-100 is already discussing scheduler queueing starvation
> and priorities:
>
>
> https://cwiki.apache.org/confluence/spaces/AIRFLOW/pages/406618462/AIP-100%2BEliminate%2BScheduler%2BQueueing%2BStarvation%2BOn%2BConcurrency%2BLimits
>
> I think this proposal can proceed independently if it defines one reusable
> pool-eligibility path that later scheduler work can call. I do not propose
> changing priority or fairness behavior as part of multiple-pool support.
>
> Questions for the community
>
> 1.
>
> Is pool_requirements: Mapping[str, int] the right public API, or would
> a mapping-valued pools argument be clearer?
> 2.
>
> Does the normalized storage plus legacy fallback approach fit
> Airflow’s migration and compatibility expectations?
> 3.
>
> Should an existing task-instance pool filter match any required pool,
> or should multiple-pool filtering use a new filter during the compatibility
> period?
> 4.
>
> Should Airflow impose a small hard limit on the number of pools one
> task can require? I would benchmark limits before proposing a final number.
> 5.
>
> Is a temporary feature gate sufficient protection against older
> schedulers enforcing only the scalar compatibility pool?
> 6.
>
> Should the implementation proceed alongside AIP-100, or wait for its
> scheduler-priority decisions?
>
> Related work
>
> Two open PRs touch adjacent pool behavior but do not implement this
> feature:
>
> -
>
> #69770 changes post-deferral pool occupancy:
> https://github.com/apache/airflow/pull/69770
> -
>
> #71220 proposes a cluster-wide include_deferred override:
> https://github.com/apache/airflow/pull/71220
>
> If the direction makes sense, I will turn the result of this discussion
> into a formal Confluence AIP. I will also prepare a proof of concept for
> the occupancy query and scheduler filtering, with PostgreSQL, MySQL,
> SQLite, and concurrent-scheduler tests, before proposing production
> implementation PRs.
>
> AI disclosure: I used OpenAI Codex to research and draft this proposal.
>
> Thanks,
> Ramachandra Nalam
> GitHub: @vamsi-klu
>
--
Thanks and Regards,
Ramachandra Nalam