Skip to content

Speed up repeated latest Dag lookups#69815

Open
viiccwen wants to merge 2 commits into
apache:mainfrom
viiccwen:optimize-latest-dag-cache
Open

Speed up repeated latest Dag lookups#69815
viiccwen wants to merge 2 commits into
apache:mainfrom
viiccwen:optimize-latest-dag-cache

Conversation

@viiccwen

@viiccwen viiccwen commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

closes #69814

Repeated API requests that resolve the latest Dag always need to query the latest serialized row to preserve freshness. However, they also deserialized that row on every request, even when the same Dag version and hash were already cached.

This change reuses the cached deserialized Dag when the queried version ID and hash are unchanged.
It keeps the existing latest-row query, reloads serialized Dags that were updated in place, and refreshes the cache validation timestamp. The cache documentation now distinguishes freshness revalidation from LRU/TTL eviction.

A real-path benchmark measured the ORM latest-row query, Dag deserialization, and task route handler. Each result is the median of three rounds with 50 lookups per round.

Backend Serialized payload Before After Speedup
SQLite 31 KB 3.052 ms 0.437 ms 6.98x
SQLite 153 KB 13.487 ms 0.965 ms 13.98x
SQLite 305 KB 28.765 ms 1.808 ms 15.91x
PostgreSQL 14 31 KB 3.798 ms 1.002 ms 3.79x
PostgreSQL 14 153 KB 16.461 ms 2.093 ms 7.87x
PostgreSQL 14 305 KB 30.390 ms 3.912 ms 7.77x
MySQL 8 31 KB 4.139 ms 1.033 ms 4.01x
MySQL 8 153 KB 15.952 ms 1.933 ms 8.25x
MySQL 8 305 KB 29.914 ms 3.371 ms 8.87x

The benchmark uses a new SQLAlchemy session for each lookup and leaves [core] compress_serialized_dags at its default value (False). It excludes HTTP middleware, authentication, and response encoding, so the table isolates the route-handler path affected by this change rather than claiming the same percentage improvement for total HTTP latency.

Tests cover matching cache hits, validation timestamp refresh, cache metrics, cache misses, and in-place hash changes. The DagBag test file, related API/query-count tests, Ruff checks, pre-commit checks, manual checks, and Apache Airflow documentation build pass.


Was generative AI tooling used to co-author this PR?
  • Yes — Codex (GPT-5.6 Sol)

  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.

@viiccwen
viiccwen requested review from XD-DENG and ashb as code owners July 13, 2026 10:25
@viiccwen viiccwen changed the title feat: speed up repeated latest Dag lookups Speed up repeated latest Dag lookups Jul 13, 2026
@viiccwen

Copy link
Copy Markdown
Contributor Author

cc @guan404ming, @jason810496. 🙌

API requests repeatedly resolve the latest Dag and should benefit from the configured cache without sacrificing freshness.

Signed-off-by: viiccwen <vicwen@apache.org>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes API-server latest Dag resolution by reusing an already-cached deserialized Dag when the database “latest serialized row” query returns the same dag_version_id and dag_hash, avoiding repeated deserialization while still preserving freshness via the latest-row query.

Changes:

  • Reuse cached deserialized Dag in DBDagBag.get_latest_version_of_dag() when version ID + hash match; evict and reload on in-place hash changes.
  • Add unit tests covering cache-hit reuse, miss behavior, and validation timestamp refresh + metrics.
  • Update cache configuration/docs wording to clarify freshness revalidation vs. eviction (LRU/TTL).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
airflow-core/src/airflow/models/dagbag.py Add hash/version-aware reuse path for latest-Dag lookups while keeping the latest-row DB query.
airflow-core/tests/unit/models/test_dagbag.py Add regression/unit tests for cache hit/miss behavior and metrics for latest-Dag lookups.
airflow-core/src/airflow/config_templates/config.yml Clarify cache behavior in config reference text.
airflow-core/docs/faq.rst Clarify API server cache freshness checking vs. eviction/TTL in user documentation.

Comment thread airflow-core/src/airflow/config_templates/config.yml Outdated
Comment thread airflow-core/src/airflow/config_templates/config.yml
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@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.

I'd prefer to wait until @ephraimbuddy confirm on this case.

Comment on lines 237 to 261
def get_latest_version_of_dag(self, dag_id: str, *, session: Session) -> SerializedDAG | None:
"""Get the latest version of a dag by its id."""
from airflow.models.serialized_dag import SerializedDagModel

if not (serdag := SerializedDagModel.get(dag_id, session=session)):
return None

with self._lock:
cached = self._dags.get(serdag.dag_version_id)
if cached is not None:
# The latest-row query already loaded dag_hash, so no revalidation query is needed.
if cached.dag_hash == serdag.dag_hash:
self._dags[serdag.dag_version_id] = cached._replace(last_validated=time.monotonic())
else:
# A serialized Dag can be updated in place without changing its version ID.
self._dags.pop(serdag.dag_version_id, None)
cached = None

if cached is not None:
if self._use_cache:
stats.incr("api_server.dag_bag.cache_hit")
return cached.dag
if self._use_cache:
stats.incr("api_server.dag_bag.cache_miss")
return self._read_dag(serdag)

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.

@ephraimbuddy

Are we intentionally to retrieve the latest Dag instance from Metadata DB without cache for the get_latest_version_of_dag method introduced in #53153 ?

There's a similar method -- get_dag that always read from the cache first.

def get_dag(self, version_id: UUID | str, session: Session) -> SerializedDAG | None:
"""Get a dag by its version id, using cache if enabled."""
return self._get_dag(version_id=version_id, session=session)

Would like to double check the usage and the intention back then, thanks.

@jason810496
jason810496 requested a review from ephraimbuddy July 15, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Latest Dag lookups repeatedly deserialize cached versions

3 participants