Hi all,

In the cyclic TaskGroup thread [1], it was suggested that any dependency
from a task inside a TaskGroup to a task outside it, except from the
group's exit, should be deprecated in 3.4 and removed in 3.5, because
Task Loops would invalidate those dependencies. That is a much bigger
change than group-level cycles, so I'm moving it to its own thread.

My position: new TaskGroup features, such as Task Loops (AIP-111) [2]
and Dynamic Task Groups (AIP-113) [3], should not break existing Dags
that use plain TaskGroups this way. Any boundary rule those features
need should apply only to the groups that opt into them.

WHAT THIS IS ABOUT

A task-level edge that leaves a TaskGroup from a task other than its
exit, or enters it at a task other than its entry:

    with TaskGroup("load"):
        extract = ...
        transform = ...
        publish = ...
        extract >> transform >> publish

    extract >> audit    # audit is outside the group

Group-level cycles (a >> ext >> b, with a and b in the same group) are
not part of this. The other thread covers those.

WHY THIS IS A VALID AND COMMON PATTERN

- Our docs have always described TaskGroups as grouping, not execution
  semantics. The 2.x docs said: "TaskGroups are purely a UI grouping
  concept. All tasks within the TaskGroup still behave as any other
  tasks outside of the TaskGroup." [4] That sentence left the 3.x docs
  only because the SubDAG section around it was removed (#41390). The
  3.x overview still says TaskGroups "let you visually group tasks in
  the UI". AIP-34 also says TaskGroups make no change to how the
  scheduler works.

- Our best-practices page depends on it. The watcher pattern [5] says
  the watcher "needs also to be a downstream task for all other tasks
  in the Dag", and the edges have to be direct because trigger rules
  only look at direct parents. Any Dag that uses the watcher together
  with a TaskGroup has non-exit tasks feeding a task outside the
  group. 11 of our own provider system-test Dags do this today.

- We fixed the Graph view for exactly this pattern in June. #67714 [6]
  reported a Dag where tasks in the middle of a @task_group also feed
  a task outside the group, and noted that the Dag executes correctly.
  #67720 [7] fixed the rendering to preserve "the author's explicit
  dependency intent".

- It has been a tested case since TaskGroups were introduced.
  test_task_group.py has had an edge into a non-entry task of another
  group (task5 >> task8) since the original AIP-34 PR (#10153).

- TaskFlow produces it naturally. Passing an outside XComArg into a
  @task_group function creates an edge to whichever task consumes it,
  not necessarily the group's entry. And @task_group forwards whatever
  task the function returns, so downstream tasks bind to that task,
  not necessarily the group's last one.

- Third-party guides teach it. Astronomer's dependencies guide says:
  "You can also set dependencies between task groups, between tasks
  inside and out of task groups, and even between tasks in different
  (nested) task groups", with example code that does this [8].

- Users describe their TaskGroups this way. A user on #73087 wrote that
  their "task groups are logical groupings, per database schema, with
  no bearing on Airflow dependencies" [9].

WHAT THE AIPs ALREADY SAY

Both AIPs are opt-in. AIP-111 (accepted) adds .loop() to a task or
TaskGroup and says no existing Dag is affected. AIP-113 adds a separate
DynamicTaskGroup construct and says "No existing Dag is affected."
Both route downstream edges through a synthesized completion or merge
node. Neither needs plain TaskGroups to change.

PROPOSAL

1. Plain TaskGroups keep today's semantics: any acyclic task-level edge
   across a group boundary stays valid. Group-level cycles are handled
   in the other thread.

2. A looped group or a DynamicTaskGroup may restrict edges across its
   own boundary (for example, only through its entry and its completion
   or merge node). That check runs at parse time, only for that group,
   and the error names the offending edge.

3. AIP-111 and AIP-113 state their boundary rules explicitly, including
   what happens to a direct edge between a loop-body or catalog task
   and a task outside the group.

4. If we later want to restrict plain TaskGroups, that is a separate
   breaking-change proposal, with data on user impact and a deprecation
   period under our policy [10].

Does anyone see a case where loops or dynamic groups need plain
TaskGroups to change?

[1] https://lists.apache.org/thread/sossl7b2w2ftyk4028qrhps2tcdxj2px
[2]
https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=440303747
[3]
https://cwiki.apache.org/confluence/spaces/AIRFLOW/pages/440304823/AIP-113+Dynamic+Task+Groups
[4]
https://airflow.apache.org/docs/apache-airflow/2.10.5/core-concepts/dags.html
[5]
https://airflow.apache.org/docs/apache-airflow/stable/best-practices.html#example-of-watcher-pattern-with-trigger-rules
[6] https://github.com/apache/airflow/issues/67714
[7] https://github.com/apache/airflow/pull/67720
[8] https://www.astronomer.io/docs/learn/managing-dependencies

https://github.com/astronomer/webinar-task-groups/blob/main/dags/example_complex_dependencies_2.py
[9] https://github.com/apache/airflow/pull/73087#issuecomment-5808925697
[10]
https://airflow.apache.org/docs/apache-airflow/stable/release-process.html#deprecation-policy

Thanks,
Dheeraj

Reply via email to