DUE TO SPAM, SIGN-UP IS DISABLED. Goto Selfserve wiki signup and request an account.
Status
| State | Draft |
| Discussion Threads | |
| Vote Thread | https://lists.apache.org/thread/x5bd26zfqkkhp7sx7nb0crjlsctf2z5j |
| Vote Result Thread | |
| Progress Tracking (PR/GitHub Project/Issue Label) | https://github.com/apache/airflow/pull/62922/ |
| Date Created | 2026.04.15 |
| Version Released | 3.4.0 |
| Authors | David Blain |
Motivation
Airflow’s existing Dynamic Task Mapping (DTM) provides a powerful way to process collections by expanding tasks into multiple Task Instances. However, this model introduces significant overhead when applied to large datasets:
- Each item becomes a separate Task Instance
- The scheduler must create, track, and persist each instance
- The metadata database experiences increased load
- Worker slots are consumed per item
This approach works well for coarse-grained, independently trackable workloads, but becomes inefficient for:
- High-volume workloads (thousands to millions of items)
- Short lived workloads
- I/O-bound operations (e.g. API calls, SFTP downloads)
- Streaming or paginated data processing
In parallel, deferrable operators—often used with DTM for async workloads—introduce additional constraints:
- Dependence on triggerers
- Triggerers storing events in the metadata database
- Limited scalability compared to workers
- Inability to leverage custom XCom backends
With the introduction of native async support in Python tasks (AIP-98), a new execution pattern becomes possible:
Instead of distributing work across many tasks, process collections within a single task using concurrency (threads or async).
This leads to the introduction of Task Iteration (TI).
TI shifts iteration from the scheduler layer to the execution layer, eliminating scheduling overhead and enabling efficient in-task concurrency.
Additionally, a future pattern—Dynamic Task Batching (DTB)—combines DTM and TI to balance distribution and efficiency.
Considerations
Task Iteration introduces a fundamentally different execution model compared to Dynamic Task Mapping (DTM), which leads to several trade-offs and behavioural differences.
Retry semantics
- DTM:
- Retries are handled per item (per Task Instance)
- State is fully isolated and persisted by the scheduler
- TI:
- Retries are also handled per item within the task execution
- Progress is maintained across completed batches within the same Task Instance
- However, if the worker dies during execution:
- In-flight progress (current batch) may be lost
- Retry state within that batch is not preserved
- Previously completed batches do not need to be reprocessed
This makes Task Iteration partially stateful within a task execution, but not resilient to worker-level failures in the same way as DTM, unless we implement Task State thanks to AIP-103.
Observability
- DTM:
- Full per-item observability in the Airflow UI
- Each item is a separate Task Instance with its own logs and state
- TI:
- Aggregated observability within a single Task Instance
- Per-item visibility is not exposed in the UI
- Reduced observability is a trade-off for significantly improved performance
Execution model
- DTM:
- Work is distributed across multiple workers
- Each item executes independently
- TI:
- All items are processed within a single worker
- Iteration happens in-process
- Supports:
- Sequential execution
- Multi-threading (sync)
- Async multiplexing (shared event loop)
Failure isolation
- DTM:
- Failures are isolated per item
- Only failed items are retried
- TI:
- Failures are handled at the task level
- Items within the current batch may be retried
- In case of task failure, reprocessing scope depends on:
- Completed batches (persisted)
- In-flight batch (may be retried)
Dynamic Task Batching (DTB)
Dynamic Task Batching combines the strengths of both models:
- DTM distributes partitions across workers
- TI processes items within each partition
This provides:
- Reduced scheduler load (compared to full DTM)
- Improved failure isolation (compared to pure TI)
- High throughput within each partition
Summary of trade-offs
| Aspect | Dynamic Tsk Mapping (DTM) | Task Iteration (TI) |
|---|---|---|
| Retry granularity | Per item | Per item (within task) |
| Retry durability | Fully persistent | Partial (batch-level loss on worker failure) |
| Observability | Per item | Aggregated |
| Execution | Distributed | In-process |
| Failure isolation | Strong | Moderate |
What change do you propose to make?
Introduce Task Iteration (TI) as a first-class execution model in Airflow.
This includes:
- Adding an
.iterate()API to tasks/operators - Allowing iteration over iterables (typically XCom results) within a single Task Instance
- Supporting multiple execution strategies within that task:
- Sequential execution
- Multi-threaded execution (for sync tasks)
- Async multiplexing (shared event loop for async tasks)
TI enables:
- Per-item processing within a task
- Per-item retry handling inside the task execution
- Batch-based progress tracking within the task
Additionally, introduce Dynamic Task Batching (DTB) as an extension of Task Iteration (TI):
- Add a
.batch(size=N)API to split an iterable into batches or chunks - Combine with
.iterate()to process each partition within a task
Example:
get_pokemon.batch(size=2).iterate(url=list_pokemon())
This pattern:
- Uses DTM implicitly to distribute partitions across workers
- Uses TI within each partition to process items efficiently
What problem does it solve?
TI and DTB together address several limitations of existing Airflow execution models:
1. Scheduler scalability limitations
DTM:
- One Task Instance per item
- High scheduler and database overhead
TI:
- Single Task Instance
- Minimal scheduler overhead
DTB:
- One Task Instance per partition (instead of per item)
- Reduces scheduler load from O(N) → O(N / batch_size)
2. Inefficient handling of high-volume I/O workloads
DTM and deferrable operators:
- Do not efficiently multiplex I/O
- Introduce overhead per item
TI:
- Enables async multiplexing within a task
DTB:
- Combines distribution + multiplexing
- Enables high throughput at scale
3. Triggerer bottlenecks
Deferrable operators:
- Depend on triggerers
- Store events in metadata DB
TI/DTB:
- Run entirely on workers
- Avoid triggerers
- Leverage worker scalability and custom XCom backends
4. Poor trade-off between scalability and retry granularity
- DTM → good retry granularity, high overhead
- TI → low overhead, coarse failure handling
DTB provides a balance:
- Retry per batch (coarse-grained)
- Efficient execution within each batch
Why is it needed?
TI introduces a third execution model in Airflow:
| Model | Purpose |
|---|---|
| DTM | Distribute work across workers |
| Deferrable operators | Efficient waiting/polling |
| TI | Efficient in-task iteration |
However, TI alone is not always sufficient:
- Too coarse-grained for large workloads (single task retry)
- Limited by single-worker execution
DTB is needed to bridge this gap:
- Combines horizontal scaling (DTM) with efficient iteration (TI)
- Provides a middle ground between:
- Fine-grained (DTM)
- Fully aggregated (TI)
This enables:
- Scalable high-throughput pipelines
- Efficient API pagination at scale
- Large dataset processing with controlled retry boundaries
Are there any downsides to this change?
Task Iteration downsides
- Reduced observability (no per-item UI visibility)
- Execution limited to a single worker
- Partial durability:
- Completed batches are preserved
- In-flight batch progress may be lost if worker dies (unless AIP-103 is used, which will be tested soon)
- Requires batching to avoid memory pressure
Dynamic Task Batching downsides
- More complex mental model (DTM + TI combined)
- Batch size tuning required:
- Too small → behaves like DTM (high overhead)
- Too large → behaves like TI (coarse retries)
- Retry granularity limited to partition level
- Still less observable than pure DTM
Which users are affected by the change?
- Users processing large datasets
- Users working with I/O-bound workloads (APIs, SFTP, databases)
- Users leveraging async hooks or async tasks
- Users experiencing scheduler or triggerer bottlenecks
DTB is particularly relevant for users who:
- Currently use large-scale Dynamic Task Mapping
- Need a balance between scalability and retry granularity
How are users affected by the change? (e.g. DB upgrade required?)
- No database schema changes required
- No breaking changes
- Fully backward compatible
New APIs are optional:
.iterate()for TI.batch(size=N)for DTB
Existing DAGs continue to work unchanged.
What is the level of migration effort (manual and automated) needed for the users to adapt to the breaking changes? (especially in context of Airflow 3)
- None required (no breaking changes)
- Adoption is optional
DAG authors may:
- Replace
.expand()(DTM) with.iterate()for TI - Introduce
.batch(size=N)to adopt DTB - Refactor logic into task-level iteration
- Tune partition sizes based on workload
Example migration:
# Before (DTM) task.expand(item=items) # After (TI) task.iterate(item=items) # After (DTB) task.batch(size=4).iterate(item=items)
Examples
Pokemon REST API with Task Iteration
from airflow.sdk import dag, task
from airflow.providers.http.hooks.http import HttpHook, HttpAsyncHook
from pendulum import datetime
@dag(
start_date=datetime(2025, 1, 1),
schedule=None,
catchup=False,
)
def pokemon_iteration():
@task
def list_pokemon() -> list[str]:
response = HttpHook(
http_conn_id="pokeapi",
method="GET",
).run(
endpoint="api/v2/pokemon?limit=100",
)
return [
pokemon["url"].replace("https://pokeapi.co/", "")
for pokemon in response.json()["results"]
]
@task(
retries=3,
task_concurrency=2,
show_return_value_in_logs=False,
)
async def get_pokemon(url: str):
async with HttpAsyncHook(
http_conn_id="pokeapi",
method="GET",
).session() as session:
response = await session.run(endpoint=url)
return await response.json()
get_pokemon.iterate(
url=list_pokemon(),
)
pokemon_iteration()
Pokemon REST API with Dynamic Task Batching and Task Iteration
from airflow.sdk import dag, task
from airflow.providers.http.hooks.http import HttpHook, HttpAsyncHook
from pendulum import datetime
@dag(
start_date=datetime(2025, 1, 1),
schedule=None,
catchup=False,
)
def pokemon_batched_iteration():
@task
def list_pokemon() -> list[str]:
response = HttpHook(
http_conn_id="pokeapi",
method="GET",
).run(
endpoint="api/v2/pokemon?limit=100",
)
return [
pokemon["url"].replace("https://pokeapi.co/", "")
for pokemon in response.json()["results"]
]
@task(
retries=3,
task_concurrency=2,
show_return_value_in_logs=False,
)
async def get_pokemon(url: str):
async with HttpAsyncHook(
http_conn_id="pokeapi",
method="GET",
).session() as session:
response = await session.run(endpoint=url)
return await response.json()
get_pokemon.batch(size=2).iterate(
url=list_pokemon(),
)
pokemon_batched_iteration()
Comparison
| Pattern | Task Instances | Work Per Task |
|---|---|---|
get_pokemon.expand(url=urls) | 100 | 1 Pokémon |
get_pokemon.iterate(url=urls) | 1 | 100 Pokémon |
get_pokemon.batch(size=2).iterate(url=urls) | 2 | ~50 Pokémon each |
Other considerations?
What defines this AIP as "done"?
https://github.com/orgs/apache/projects/647/views/1
- AIP-104: Task Iteration and Dynamic Task Batching is merged
- Added asynchronous aget_hook method to BaseHook is merged
- Added deadlock detection in CommsDecoder is merged
- Add async XCom accessors for async tasks is merged
- Add a `POST /xcoms/{dag_id}/{run_id}/{task_id}/keys` execution API endpoint is merged
- Intermediate state is saved via AIP-103 Task State Management.



3 Comments
Jens Scheffler
Apr 16, 2026I like the idea and vision to drive this - this allows a few more special cases of many and multiple tasks that are hard to achieve today.
With exactly the description "why using DTM, DTI, DPM" it makes sense allowing the options for execution. Especially I like that no DB changes are needed but only operator enablement is needed.
Jens Scheffler
Jun 06, 2026After rework and renaming to DTB looks even better.
Daniel Standish
Jul 29, 2026Hi I was recently nudged to take a look at AIP-104 and whether AIP-104 and AIP-111 overlap and whether we need them both.
They don't really overlap.
AIP-111 is about running a task or group of tasks repeatedly until condition is met
AIP-104 is about two concepts: running multiple bits of work within a task, and splitting those bits of work into batches before sending them to the tasks
My question is, is it really necessary / worth it. Because tasks can already run multiple bits of work.
@task def hello(input): for i in input: do_something(i)And the "dynamic task batching" component of it (DTB) could be achieved by using dynamic task mapping upstream of the task. just have a task that sets the batches and then send those to however many tasks that you want
There's some hint here in this AIP doc that via another AIP -- AIP-103 -- you could get state management (to track progress through the items) in the event of task failure. But that would equally apply to conventional tasks that do looping or use other concurrent programming techniques
I think it might be helpful if you could add before / after examples in this document – for the same kind of pipeline, how you would implement it before the AIP and how you would implement it after to better illustrate the value.
Also, I think we really cannot overload the word "TI" — that is already used everywhere in airflow so it would be way too confusing to add another thing that is referred to as "TI".