Skip to content

Batch Team lookups when syncing Dag bundles to the database - #73154

Open
Eason09053360 wants to merge 7 commits into
apache:mainfrom
Eason09053360:batch-team-lookup-in-bundle-sync
Open

Eason09053360 wants to merge 7 commits into
apache:mainfrom
Eason09053360:batch-team-lookup-in-bundle-sync

Conversation

@Eason09053360

@Eason09053360 Eason09053360 commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Why

DagBundlesManager.sync_bundles_to_db runs on every Dag processor start and queried team once per configured team_name and once per stored bundle, through the lazy DagBundleModel.teams relationship. Deactivated bundles are never deleted, so these queries only grow.

What

  • Read stored owners with the existing DagBundleModel.get_team_names, and configured teams with one Team.name.in_(...). Since teams is no longer loaded, the deactivation loop clears it only on bundles that have a team.
  • A new test pins the team queries at one on a fresh DB and two once bundles are stored, including after deactivation.
  • On MySQL the old lookup was case-insensitive (team.name is a plain String), so Team-A in the bundle config matched team-a. That now fails, as airflow teams sync already does. Happy to add a fallback.

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 5.5)

Generated-by: Claude Code (Opus 5.5) following the guidelines

Resolving bundle ownership cost one query per configured bundle, and
reading the stored bundles' team relationship lazy-loaded one more each.
Both scale with the number of configured bundles and run on every Dag
processor start, while the surrounding code in the same function already
reads its rows in a single query.
Reading the stored ownership through the helper the model already exposes
keeps this function consistent with the other callers that resolve bundle
teams, rather than growing a second way to do the same thing.

Clearing the team of a bundle that has none is a no-op, but the assignment
still had to load the relationship to find out. Deactivated bundles are
never removed from dag_bundle, so that load was paid again on every
subsequent sync and grew with the number of bundles ever configured.
This was referenced Sep 25, 2026
@Eason09053360

Copy link
Copy Markdown
Contributor Author

cc @vincbeck friendly pin.
Already switched to DagBundleModel.get_team_names as you suggested, and the description now flags one MySQL case-sensitivity change. Could you take another look when you have a moment?

vincbeck
vincbeck previously approved these changes Sep 28, 2026
Multi-team is off in most deployments, yet every bundle sync still read
the team ownership of every stored bundle. With the mode off, the bundle
config cannot name a team and the Dag processor already ignores bundle
ownership, which is why its own lookup through the same helper returns
early.

While multi-team is off, team links left over from an earlier multi-team
setup are no longer cleared; the first sync after the mode is turned back
on reconciles them.
With multi-team off the bundle config cannot name a team, so building the
set of configured team names on every sync was work that could never find
anything. Keeping it under the same multi-team check as the stored
ownership read leaves no team handling to run in that mode.
team.name compares case-insensitively on MySQL, and the old per-team
equality lookup relied on it: a bundle config naming Team-A resolved to
the stored team-a. The batched lookup still matches that row, but its
result is keyed by the stored name, so the config value missed it and the
sync rejected the config with "Team 'Team-A' does not exist". Migration
0131 lower-cases stored team names, so configs written before it hit this
on upgrade.
The stored ownership is what the sync compares against to take a team
off a bundle. Skipping the read when multi-team is off left ownership
from an earlier multi-team setup in place, and requires_access_dag still
passes that team to the auth manager, so SimpleAuthManager denied those
Dags to every non-admin user. One query per sync is cheap next to that.
On MySQL team.name compares case-insensitively, so the batched IN lookup
already returns the stored row for a config value that differs only in
case. Finding that row in the result removes the extra query per such
bundle on every sync. Stored names are lower case and unique, as teams
create, teams sync and migration 0131 enforce, so casefolded keys cannot
collide. On case-sensitive backends a differently-cased name now also
resolves when another bundle configures the stored spelling.

@jason810496 jason810496 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the improvement, LGTM overall, waiting or @vincbeck for the final call.

team: Team | None = None
if config.team_name:
team = session.scalars(select(Team).where(Team.name == config.team_name)).one_or_none()
team = teams_by_name.get(config.team_name.casefold())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure would it make more sense to normalize at the _bundle_config level?

@jason810496 jason810496 added the multi-team - aip-67 Issues related to multi-team (AIP-67) label Nov 7, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:dag-processor multi-team - aip-67 Issues related to multi-team (AIP-67)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants