This is an automated email from the ASF dual-hosted git repository.
kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new b237fd02d25 Update multi-team docs for the 3.3 release (#68728)
b237fd02d25 is described below
commit b237fd02d254f048aa39c3fdee6dcba42ead1f4b
Author: Niko Oliveira <[email protected]>
AuthorDate: Thu Jun 18 16:59:09 2026 -0700
Update multi-team docs for the 3.3 release (#68728)
- Ensure all features landing in 3.3 are documented and tagged with that
version.
- Update/reconcile the experimental warning with the work-in-progress list.
- DAG to Dag.
- Also clarify the difference between the two default cases for asset
event filtering.
---
airflow-core/docs/core-concepts/multi-team.rst | 162 +++++++++++++++++--------
1 file changed, 113 insertions(+), 49 deletions(-)
diff --git a/airflow-core/docs/core-concepts/multi-team.rst
b/airflow-core/docs/core-concepts/multi-team.rst
index 45b6705440f..01c465e5507 100644
--- a/airflow-core/docs/core-concepts/multi-team.rst
+++ b/airflow-core/docs/core-concepts/multi-team.rst
@@ -20,8 +20,9 @@ Multi-Team
==========
.. warning::
- Multi-Team is an :ref:`experimental <experimental>`/incomplete feature
currently in preview. The feature will not be
- fully complete until Airflow 3.3 and may be subject to changes without
warning based on user feedback.
+ Multi-Team is an :ref:`experimental <experimental>` feature still in
preview. Airflow 3.3 delivers a
+ substantial part of the feature, but it is not yet complete: some
functionality is still planned for a
+ future release (3.4+), and behavior may change without warning based on user
feedback.
See the :ref:`Work in Progress <multi-team-work-in-progress>` section below
for details.
Multi-Team Airflow is a feature that enables organizations to run multiple
teams within a single Airflow deployment while providing resource isolation and
team-based access controls. This feature is designed for medium to large
organizations that need to share Airflow infrastructure across multiple teams
while maintaining logical separation of resources.
@@ -562,6 +563,8 @@ When Multi-Team mode is enabled, the scheduler performs
additional logic to dete
Team-scoped Triggerer
---------------------
+.. versionadded:: 3.3.0
+
When Multi-Team mode is enabled, a triggerer should be scoped to each specific
team using the ``--team-name`` CLI argument. A team-scoped triggerer processes
deferred tasks (triggers) belonging to that team's Dags. This allows teams to
run isolated triggerer instances with independent capacity and failure domains.
Configuration
@@ -684,6 +687,17 @@ controls which consumer teams are permitted to receive
events produced by that s
With this configuration, only consuming Dags belonging to ``team_downstream``
or ``team_reporting`` (plus
teamless consumers) will receive asset events produced by the ``produce_data``
task.
+.. note::
+
+ The default value of ``consumer_teams`` is ``None``, which is **not**
equivalent to an empty list:
+
+ - ``None`` (the default, or when the field is omitted): no consumer-team
restriction is applied.
+ Cross-team delivery is then governed solely by each consumer's own
``producer_teams`` opt-in.
+ - ``[]`` (an explicit empty list): restricts delivery to the producer's
**own team** (plus teamless
+ consumers, subject to ``allow_global``). This blocks **all** cross-team
consumers, even one that
+ lists this producer in its ``producer_teams``.
+ - ``["team_x", ...]``: additionally delivers to the listed cross-team
consumers.
+
Per-producer scoping
""""""""""""""""""""
@@ -703,7 +717,7 @@ for the same asset, each task's ``consumer_teams`` applies
independently to the
),
)
- # This task has no consumer restriction (empty list = all consumers
allowed)
+ # This task sets no access_control, so consumer_teams defaults to None (no
consumer-team restriction)
unrestricted_asset = Asset(name="shared_asset",
uri="s3://bucket/shared.csv")
with DAG(dag_id="dag_1", schedule="@daily"):
@@ -756,84 +770,91 @@ The following table describes the complete filtering
logic:
- ``allow_global``
- Result
- Reason
- * - Team A (DAG)
+ * - Team A (Dag)
- Team A
- (any)
- (any)
- (any)
- ✅ Allowed
- Same team
- * - Team A (DAG)
+ * - Team A (Dag)
- Team B
- ``[]``
- - ``[]``
+ - (any)
- (any)
- ❌ Blocked
- Different team, no producer opt-in
- * - Team A (DAG)
+ * - Team A (Dag)
- Team B
- ``["team_a"]``
- - ``[]``
+ - ``None``
- (any)
- ✅ Allowed
- - Producer opt-in, no consumer restriction
- * - Team A (DAG)
+ - Producer opt-in, ``consumer_teams`` unset (no consumer restriction)
+ * - Team A (Dag)
+ - Team B
+ - ``["team_a"]``
+ - ``[]``
+ - (any)
+ - ❌ Blocked
+ - ``consumer_teams=[]`` allows only the producer's own team, blocking all
cross-team even with producer opt-in
+ * - Team A (Dag)
- Team B
- ``["team_a"]``
- ``["team_b"]``
- (any)
- ✅ Allowed
- Both opt-ins satisfied
- * - Team A (DAG)
+ * - Team A (Dag)
- Team B
- ``["team_a"]``
- ``["team_c"]``
- (any)
- ❌ Blocked
- Consumer team not in consumer_teams
- * - Team A (DAG)
+ * - Team A (Dag)
- Team B
- ``[]``
- ``["team_b"]``
- (any)
- ❌ Blocked
- Producer opt-in not satisfied (AND logic)
- * - (no team, DAG)
+ * - (no team, Dag)
- Team B
- (any)
- - ``[]``
+ - ``None``
- ``True``
- ✅ Allowed
- Global producer, allow_global is True
- * - (no team, DAG)
+ * - (no team, Dag)
- Team B
- (any)
- - ``[]``
+ - (any)
- ``False``
- ❌ Blocked
- Global producer blocked by allow_global=False
- * - (no team, DAG)
+ * - (no team, Dag)
- Team B
- (any)
- ``["team_b"]``
- ``True``
- ✅ Allowed
- Global producer, allow_global is True, consumer in list
- * - (no team, DAG)
+ * - (no team, Dag)
- Team B
- (any)
- ``["team_c"]``
- (any)
- ❌ Blocked
- Global producer, but consumer not in consumer_teams
- * - Team A (DAG)
+ * - Team A (Dag)
- (no team)
- (any)
- (any)
- (any)
- ✅ Allowed
- Teamless consumer passes through (unless producer-side
``allow_global=False``)
- * - (no team, DAG)
+ * - (no team, Dag)
- (no team)
- (any)
- (any)
@@ -850,10 +871,10 @@ The following table describes the complete filtering
logic:
* - Team A (API)
- Team B
- ``["team_a"]``
- - ``[]``
+ - ``None``
- (any)
- ✅ Allowed
- - Producer opt-in, no consumer restriction
+ - Producer opt-in, ``consumer_teams`` unset (no consumer restriction)
* - Team A (API)
- Team B
- ``["team_a"]``
@@ -886,13 +907,13 @@ The following table describes the complete filtering
logic:
Key rules:
- **Same team**: Always allowed.
-- **Global (teamless) DAG producer with** ``allow_global=True``: Triggers all
consumers regardless of team (unless ``consumer_teams`` restricts them).
-- **Global (teamless) DAG producer with** ``allow_global=False``: Blocked from
triggering team-bound consumers.
-- **Teamless API user**: Can only trigger teamless consumers. Unlike a
teamless DAG — which is
- deployed by a platform operator and intentionally shared — an API user
without a team has no
+- **Global (teamless) Dag producer with** ``allow_global=True``: Triggers all
consumers regardless of team (unless ``consumer_teams`` restricts them).
+- **Global (teamless) Dag producer with** ``allow_global=False``: Blocked from
triggering team-bound consumers.
+- **Teamless API user**: Can only trigger teamless consumers. Unlike a
teamless Dag, which is
+ deployed by a platform operator and intentionally shared, an API user
without a team has no
verified team affiliation, so their events are restricted to teamless
consumers to
prevent unscoped access to team-bound pipelines.
-- **Teamless consumer**: Accepts events from any source (DAG or API),
regardless of team or ``consumer_teams``, unless ``allow_global=False`` is set
on the producer-side asset.
+- **Teamless consumer**: Accepts events from any source (Dag or API),
regardless of team or ``consumer_teams``, unless ``allow_global=False`` is set
on the producer-side asset.
- **Cross-team via** ``producer_teams``: Allowed when the producer's team is
listed in the asset's ``producer_teams``.
- **Cross-team via** ``consumer_teams``: Allowed when the consumer's team is
listed in the producing task's ``consumer_teams``.
- **Both filters (AND logic)**: When both ``producer_teams`` and
``consumer_teams`` are specified, a consumer must pass both checks to be queued.
@@ -903,7 +924,7 @@ API-Triggered Events
When a user creates an asset event via the REST API, the user's team is
resolved from the auth manager.
The same filtering rules apply, with one distinction: a teamless API user can
only trigger teamless
-consumers, whereas a teamless DAG producer is treated as global and can
trigger any consumer.
+consumers, whereas a teamless Dag producer is treated as global and can
trigger any consumer.
The REST API also accepts an optional ``access_control`` object in the request
body with the following
fields:
@@ -915,27 +936,6 @@ fields:
When Multi-Team mode is disabled, the ``access_control`` parameter is accepted
but ignored.
-Important Considerations
-------------------------
-
-.. _multi-team-work-in-progress:
-
-Work in Progress
-^^^^^^^^^^^^^^^^
-
-Multi-Team mode is currently an experimental feature in preview. It is not yet
fully complete and may be subject to changes without warning based on user
feedback. Some missing functionality includes:
-
-- Dimensional metrics by team
-- Some UI elements may not be fully team-aware
-- Full provider support for executors and secrets backends
-- Command and Secrets based lookup for team based configuration
-- Plugins
-
-Global Uniqueness of Identifiers
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-**Dag IDs, Variable keys, and Connection IDs must be unique across the entire
Airflow deployment**, regardless of which team owns them. This is similar to
how S3 bucket names are globally unique across all AWS accounts. You should
establish naming conventions within your organization to avoid naming conflicts
(e.g. prefix identifiers with the team name)
-
Real-World Example: Cross-Team Data Pipeline
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1020,6 +1020,70 @@ In this setup:
neither listed in ``consumer_teams`` on the producer side nor does it list
``team_ingestion``
in its own ``producer_teams``.
+.. _multi-team-metrics:
+
+Team-based Metrics
+------------------
+
+.. versionadded:: 3.3.0
+
+When Multi-Team mode is enabled, Airflow adds a ``team_name`` tag to many of
its operational metrics,
+identifying the team that owns the resource each metric relates to. This lets
you break activity down per
+team in your metrics backend. The tag is only present for team-owned
resources; global pools, teamless
+Dags, and global components emit the same metrics without a ``team_name`` tag.
+
+.. note::
+
+ The ``team_name`` dimension is emitted as a metric **tag**, so it requires
a tag-aware metrics
+ backend: either StatsD with tagging enabled (for example, the Datadog or
InfluxDB dialects) or
+ OpenTelemetry.
+
+.. note::
+
+ When Multi-Team mode is disabled, metrics are emitted with no
``team_name`` tag whatsoever, exactly
+ as they have always been emitted for a single-team Airflow environment.
+
+The ``team_name`` tag is applied to metrics across the following components:
+
+- **Triggerer**: heartbeat, capacity, and trigger-outcome metrics (for
example, ``triggerer_heartbeat``,
+ ``triggers.running``, ``triggers.succeeded``).
+- **Executors**: executor slot gauges (for example, ``executor.open_slots``,
``executor.queued_tasks``).
+- **Scheduler**: pool slot gauges for team-scoped pools plus task- and
asset-scheduling counters (for
+ example, ``pool.open_slots``, ``scheduler.tasks.killed_externally``,
``asset.triggered_dagruns``).
+- **Dag runs**: dag run timing and lifecycle metrics (for example,
``dagrun.duration.<state>``,
+ ``dagrun.first_task_scheduling_delay``, ``dag.callback_exceptions``).
+- **Task instances**: task start, finish, and outcome counters (for example,
``ti.start``, ``ti.finish``,
+ ``ti_successes``, ``ti_failures``).
+- **Dag processing**: per-file parsing and callback metrics (for example,
``dag_processing.processes``,
+ ``dag_processing.processor_timeouts``,
``dag_processing.callback_only_count``).
+- **Callbacks**: callback execution counters (``callback_success`` /
``callback_failure``, optionally
+ prefixed).
+
+.. note::
+
+ Only metrics emitted by Airflow core carry the ``team_name`` tag in 3.3;
provider-specific metrics
+ were not updated for this release. Provider executors are an exception:
their ``executor.*`` slot
+ gauges are tagged because they inherit them from the core base executor.
+
+Important Considerations
+------------------------
+
+.. _multi-team-work-in-progress:
+
+Work in Progress
+^^^^^^^^^^^^^^^^
+
+Multi-Team mode is currently an experimental feature in preview. It is not yet
fully complete and may be subject to changes without warning based on user
feedback. Some missing functionality that will arrive in a future release
(3.4+) includes:
+
+- Some UI elements may not be fully team-aware
+- Command and Secrets based lookup for team based configuration
+- Plugin support
+
+Global Uniqueness of Identifiers
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+**Dag IDs, Variable keys, and Connection IDs must be unique across the entire
Airflow deployment**, regardless of which team owns them. This is similar to
how S3 bucket names are globally unique across all AWS accounts. You should
establish naming conventions within your organization to avoid naming conflicts
(e.g. prefix identifiers with the team name)
+
Architecture
------------