Skip to content

Don't get DAG out of DagBag when we already have it - #35243

Closed
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it
Closed

Don't get DAG out of DagBag when we already have it#35243
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it

Conversation

@ashb

@ashbashb commented Oct 28, 2023

Copy link
Copy Markdown
Member

Two things here:

  1. By the ponit we are looking at the "callbacks" dagrun.dag will already be set, (the or dagbag.get_dag is a safety precaution. It might not be required or worth it)
  2. DagBag already is a cache. We don't need an extra caching layer on top of it.
    ifdag_idnotinself.dags:
    # Load from DB if not (yet) in the bag
    self._add_dag_from_db(dag_id=dag_id, session=session)
    returnself.dags.get(dag_id)

This "soft reverts" #30704 and removes the lru_cache

Two things here:
1. By the ponit we are looking at the "callbacks" `dagrun.dag` will
already be set, (the `or dagbag.get_dag` is a safety precaution. It
might not be required or worth it)
2. DagBag already _is_ a cache. We don't need an extra caching layer on
top of it.
This "soft reverts" #30704 and removes the lru_cache
@boring-cyborgboring-cyborgBot added the area:Scheduler including HA (high availability) scheduler label Oct 28, 2023

@hussein-awalahussein-awala 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.

From the other PR description:

With the caching we were able to increase scheduler performance. Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

We don't use the dagbag cache directly, instead we check if we need to update the dag and reload it from the DB:

# If DAG is in the DagBag, check the following
# 1. if time has come to check if DAG is updated (controlled by min_serialized_dag_fetch_secs)
# 2. check the last_updated and hash columns in SerializedDag table to see if
# Serialized DAG is updated
# 3. if (2) is yes, fetch the Serialized DAG.
# 4. if (2) returns None (i.e. Serialized DAG is deleted), remove dag from dagbag
# if it exists and return None.

So, I wonder if this refresh for some dags is necessary in our case (if so, your PR will be a bug fix) or if we need a local LRU cache to avoid reloading some dags from the DB.
(I'm talking about the revert of the second method _get_next_dagruns_to_examine and not the one which uses dag_run.dag)

)
for dag_run, callback_to_run in callback_tuples:
dag = cached_get_dag(dag_run.dag_id)
dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For instance, just before this loop are these two calls:

dag_runs=self._get_next_dagruns_to_examine(DagRunState.RUNNING, session)
# Bulk fetch the currently active dag runs for the dags we are# examining, rather than making one query per DagRuncallback_tuples=self._schedule_all_dag_runs(guard, dag_runs, session)

Both of those get the dag out of the dagbag which weren't affected by an LRU cache, but every dagrun we have here must have been in the call to _schedule_all_dag_runs.

@ashb

ashb commented Oct 28, 2023

Copy link
Copy Markdown
MemberAuthor

Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

The point is that dagbag.get_dag is already this cache. There were no numbers provided in that PR that show the PR actually makes any difference. My assumption is that this doesn't actually save anything (as not every call to dagbag.get_dag in the scheduler loop was replaced by a cache.

@jscheffl

Copy link
Copy Markdown
Contributor

Yes, in deep I was also scratching my head. Obviously there is a kind of basic caching but also with expiry check. The main driver for the lru_cache was the use in _get_next_dagruns_to_examine for the case if 200+ times the same DAG is queued. Then we don't need to check during one iteration over-and-over whether the DAG changed in between. That was the intend.
But the in-deep-investigation - and therefore the previous PR was made by @AutomationDev85 - who is probably back online on Monday.

@ashb

ashb commented Oct 30, 2023

Copy link
Copy Markdown
MemberAuthor

The difference between an LRU cache and the cache in dagbag is that the later does a datetime.now() call (more or less).

Additionally the change here to dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session) should negate even that call in 99% of cases without needing the extra cache.

@AutomationDev85

Copy link
Copy Markdown
Contributor

During the runtime measurement a few month ago I run into issue that these lines consumed a lot of time. When we schedule DAG with many DAG runs. I was not aware that there is some basic caching but my measurement looked like it was not working for our use case :( Any idea why it was not working if you try to schedule 200 DAG Runs of the same DAG?

@jscheffl

jscheffl commented Oct 30, 2023

Copy link
Copy Markdown
Contributor

While sitting in the train failing to build the Airflow container via breeze I was re-inspecting the code. I believe I now saw the root cause for the performance problem we had and why @AutomationDev85 added the cache around it.

There are multiple dicts used in DagBag to cache the DAG objects and the timestamps and versions. The attribute self.dags_last_fetched stores when last time a DAG was fetched and checks for the standard of 10 seconds to ensure cache is not too old. But in DagBag.get_dag() (current main in https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L207) it always hits the DB with a query if serialized cached value smells like too old. But actually the last check time is only updated if the DAG has been re-parsed in between (see https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L221). Otherwise the date marker when last time checked is not touched. This is inconsistent and means if performance problems hit the DAG parsing (or the DAG parser is running in the scheduler and the scheduler loop hits some performance problems >10 seconds) then this increases DB load.
So when removing the lru cache as by previous PR #30704 then we would need to fix the caching logic and at least update the self.dags_last_fetched[dag_id] to the current time (and not the time of last DAG parsing == sd_last_updated_datetime).

But I feel like the code in this section has grown over time and it took me three times to understand the logic. Comparing to an LRU cache this is looking very complex. Maybe a round of refactoring for DB Caching would be good - Maybe we can add something like LRU cache with a timeout and move the complexity out to a caching utility rather than implementing custom logic in DagBag?

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 5 days if no further activity occurs. Thank you for your contributions.

@github-actionsgithub-actionsBot added the stale Stale PRs per the .github/workflows/stale.yml policy file label Dec 15, 2023
@potiuk
potiuk deleted the dont-get-dag-we-already-have-it branch October 1, 2024 08:16
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Schedulerincluding HA (high availability) schedulerstaleStale PRs per the .github/workflows/stale.yml policy file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@ashb@jscheffl@AutomationDev85@hussein-awala
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Don't get DAG out of DagBag when we already have it by ashb · Pull Request #35243 · apache/airflow · GitHub
Skip to content

Don't get DAG out of DagBag when we already have it - #35243

Closed
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it
Closed

Don't get DAG out of DagBag when we already have it#35243
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it

Conversation

@ashb

@ashbashb commented Oct 28, 2023

Copy link
Copy Markdown
Member

Two things here:

  1. By the ponit we are looking at the "callbacks" dagrun.dag will already be set, (the or dagbag.get_dag is a safety precaution. It might not be required or worth it)
  2. DagBag already is a cache. We don't need an extra caching layer on top of it.
    ifdag_idnotinself.dags:
    # Load from DB if not (yet) in the bag
    self._add_dag_from_db(dag_id=dag_id, session=session)
    returnself.dags.get(dag_id)

This "soft reverts" #30704 and removes the lru_cache

Two things here:
1. By the ponit we are looking at the "callbacks" `dagrun.dag` will
already be set, (the `or dagbag.get_dag` is a safety precaution. It
might not be required or worth it)
2. DagBag already _is_ a cache. We don't need an extra caching layer on
top of it.
This "soft reverts" #30704 and removes the lru_cache
@boring-cyborgboring-cyborgBot added the area:Scheduler including HA (high availability) scheduler label Oct 28, 2023

@hussein-awalahussein-awala 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.

From the other PR description:

With the caching we were able to increase scheduler performance. Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

We don't use the dagbag cache directly, instead we check if we need to update the dag and reload it from the DB:

# If DAG is in the DagBag, check the following
# 1. if time has come to check if DAG is updated (controlled by min_serialized_dag_fetch_secs)
# 2. check the last_updated and hash columns in SerializedDag table to see if
# Serialized DAG is updated
# 3. if (2) is yes, fetch the Serialized DAG.
# 4. if (2) returns None (i.e. Serialized DAG is deleted), remove dag from dagbag
# if it exists and return None.

So, I wonder if this refresh for some dags is necessary in our case (if so, your PR will be a bug fix) or if we need a local LRU cache to avoid reloading some dags from the DB.
(I'm talking about the revert of the second method _get_next_dagruns_to_examine and not the one which uses dag_run.dag)

)
for dag_run, callback_to_run in callback_tuples:
dag = cached_get_dag(dag_run.dag_id)
dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For instance, just before this loop are these two calls:

dag_runs=self._get_next_dagruns_to_examine(DagRunState.RUNNING, session)
# Bulk fetch the currently active dag runs for the dags we are# examining, rather than making one query per DagRuncallback_tuples=self._schedule_all_dag_runs(guard, dag_runs, session)

Both of those get the dag out of the dagbag which weren't affected by an LRU cache, but every dagrun we have here must have been in the call to _schedule_all_dag_runs.

@ashb

ashb commented Oct 28, 2023

Copy link
Copy Markdown
MemberAuthor

Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

The point is that dagbag.get_dag is already this cache. There were no numbers provided in that PR that show the PR actually makes any difference. My assumption is that this doesn't actually save anything (as not every call to dagbag.get_dag in the scheduler loop was replaced by a cache.

@jscheffl

Copy link
Copy Markdown
Contributor

Yes, in deep I was also scratching my head. Obviously there is a kind of basic caching but also with expiry check. The main driver for the lru_cache was the use in _get_next_dagruns_to_examine for the case if 200+ times the same DAG is queued. Then we don't need to check during one iteration over-and-over whether the DAG changed in between. That was the intend.
But the in-deep-investigation - and therefore the previous PR was made by @AutomationDev85 - who is probably back online on Monday.

@ashb

ashb commented Oct 30, 2023

Copy link
Copy Markdown
MemberAuthor

The difference between an LRU cache and the cache in dagbag is that the later does a datetime.now() call (more or less).

Additionally the change here to dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session) should negate even that call in 99% of cases without needing the extra cache.

@AutomationDev85

Copy link
Copy Markdown
Contributor

During the runtime measurement a few month ago I run into issue that these lines consumed a lot of time. When we schedule DAG with many DAG runs. I was not aware that there is some basic caching but my measurement looked like it was not working for our use case :( Any idea why it was not working if you try to schedule 200 DAG Runs of the same DAG?

@jscheffl

jscheffl commented Oct 30, 2023

Copy link
Copy Markdown
Contributor

While sitting in the train failing to build the Airflow container via breeze I was re-inspecting the code. I believe I now saw the root cause for the performance problem we had and why @AutomationDev85 added the cache around it.

There are multiple dicts used in DagBag to cache the DAG objects and the timestamps and versions. The attribute self.dags_last_fetched stores when last time a DAG was fetched and checks for the standard of 10 seconds to ensure cache is not too old. But in DagBag.get_dag() (current main in https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L207) it always hits the DB with a query if serialized cached value smells like too old. But actually the last check time is only updated if the DAG has been re-parsed in between (see https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L221). Otherwise the date marker when last time checked is not touched. This is inconsistent and means if performance problems hit the DAG parsing (or the DAG parser is running in the scheduler and the scheduler loop hits some performance problems >10 seconds) then this increases DB load.
So when removing the lru cache as by previous PR #30704 then we would need to fix the caching logic and at least update the self.dags_last_fetched[dag_id] to the current time (and not the time of last DAG parsing == sd_last_updated_datetime).

But I feel like the code in this section has grown over time and it took me three times to understand the logic. Comparing to an LRU cache this is looking very complex. Maybe a round of refactoring for DB Caching would be good - Maybe we can add something like LRU cache with a timeout and move the complexity out to a caching utility rather than implementing custom logic in DagBag?

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 5 days if no further activity occurs. Thank you for your contributions.

@github-actionsgithub-actionsBot added the stale Stale PRs per the .github/workflows/stale.yml policy file label Dec 15, 2023
@potiuk
potiuk deleted the dont-get-dag-we-already-have-it branch October 1, 2024 08:16
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Schedulerincluding HA (high availability) schedulerstaleStale PRs per the .github/workflows/stale.yml policy file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@ashb@jscheffl@AutomationDev85@hussein-awala
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Don't get DAG out of DagBag when we already have it by ashb · Pull Request #35243 · apache/airflow · GitHub
Skip to content

Don't get DAG out of DagBag when we already have it - #35243

Closed
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it
Closed

Don't get DAG out of DagBag when we already have it#35243
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it

Conversation

@ashb

@ashbashb commented Oct 28, 2023

Copy link
Copy Markdown
Member

Two things here:

  1. By the ponit we are looking at the "callbacks" dagrun.dag will already be set, (the or dagbag.get_dag is a safety precaution. It might not be required or worth it)
  2. DagBag already is a cache. We don't need an extra caching layer on top of it.
    ifdag_idnotinself.dags:
    # Load from DB if not (yet) in the bag
    self._add_dag_from_db(dag_id=dag_id, session=session)
    returnself.dags.get(dag_id)

This "soft reverts" #30704 and removes the lru_cache

Two things here:
1. By the ponit we are looking at the "callbacks" `dagrun.dag` will
already be set, (the `or dagbag.get_dag` is a safety precaution. It
might not be required or worth it)
2. DagBag already _is_ a cache. We don't need an extra caching layer on
top of it.
This "soft reverts" #30704 and removes the lru_cache
@boring-cyborgboring-cyborgBot added the area:Scheduler including HA (high availability) scheduler label Oct 28, 2023

@hussein-awalahussein-awala 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.

From the other PR description:

With the caching we were able to increase scheduler performance. Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

We don't use the dagbag cache directly, instead we check if we need to update the dag and reload it from the DB:

# If DAG is in the DagBag, check the following
# 1. if time has come to check if DAG is updated (controlled by min_serialized_dag_fetch_secs)
# 2. check the last_updated and hash columns in SerializedDag table to see if
# Serialized DAG is updated
# 3. if (2) is yes, fetch the Serialized DAG.
# 4. if (2) returns None (i.e. Serialized DAG is deleted), remove dag from dagbag
# if it exists and return None.

So, I wonder if this refresh for some dags is necessary in our case (if so, your PR will be a bug fix) or if we need a local LRU cache to avoid reloading some dags from the DB.
(I'm talking about the revert of the second method _get_next_dagruns_to_examine and not the one which uses dag_run.dag)

)
for dag_run, callback_to_run in callback_tuples:
dag = cached_get_dag(dag_run.dag_id)
dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For instance, just before this loop are these two calls:

dag_runs=self._get_next_dagruns_to_examine(DagRunState.RUNNING, session)
# Bulk fetch the currently active dag runs for the dags we are# examining, rather than making one query per DagRuncallback_tuples=self._schedule_all_dag_runs(guard, dag_runs, session)

Both of those get the dag out of the dagbag which weren't affected by an LRU cache, but every dagrun we have here must have been in the call to _schedule_all_dag_runs.

@ashb

ashb commented Oct 28, 2023

Copy link
Copy Markdown
MemberAuthor

Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

The point is that dagbag.get_dag is already this cache. There were no numbers provided in that PR that show the PR actually makes any difference. My assumption is that this doesn't actually save anything (as not every call to dagbag.get_dag in the scheduler loop was replaced by a cache.

@jscheffl

Copy link
Copy Markdown
Contributor

Yes, in deep I was also scratching my head. Obviously there is a kind of basic caching but also with expiry check. The main driver for the lru_cache was the use in _get_next_dagruns_to_examine for the case if 200+ times the same DAG is queued. Then we don't need to check during one iteration over-and-over whether the DAG changed in between. That was the intend.
But the in-deep-investigation - and therefore the previous PR was made by @AutomationDev85 - who is probably back online on Monday.

@ashb

ashb commented Oct 30, 2023

Copy link
Copy Markdown
MemberAuthor

The difference between an LRU cache and the cache in dagbag is that the later does a datetime.now() call (more or less).

Additionally the change here to dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session) should negate even that call in 99% of cases without needing the extra cache.

@AutomationDev85

Copy link
Copy Markdown
Contributor

During the runtime measurement a few month ago I run into issue that these lines consumed a lot of time. When we schedule DAG with many DAG runs. I was not aware that there is some basic caching but my measurement looked like it was not working for our use case :( Any idea why it was not working if you try to schedule 200 DAG Runs of the same DAG?

@jscheffl

jscheffl commented Oct 30, 2023

Copy link
Copy Markdown
Contributor

While sitting in the train failing to build the Airflow container via breeze I was re-inspecting the code. I believe I now saw the root cause for the performance problem we had and why @AutomationDev85 added the cache around it.

There are multiple dicts used in DagBag to cache the DAG objects and the timestamps and versions. The attribute self.dags_last_fetched stores when last time a DAG was fetched and checks for the standard of 10 seconds to ensure cache is not too old. But in DagBag.get_dag() (current main in https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L207) it always hits the DB with a query if serialized cached value smells like too old. But actually the last check time is only updated if the DAG has been re-parsed in between (see https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L221). Otherwise the date marker when last time checked is not touched. This is inconsistent and means if performance problems hit the DAG parsing (or the DAG parser is running in the scheduler and the scheduler loop hits some performance problems >10 seconds) then this increases DB load.
So when removing the lru cache as by previous PR #30704 then we would need to fix the caching logic and at least update the self.dags_last_fetched[dag_id] to the current time (and not the time of last DAG parsing == sd_last_updated_datetime).

But I feel like the code in this section has grown over time and it took me three times to understand the logic. Comparing to an LRU cache this is looking very complex. Maybe a round of refactoring for DB Caching would be good - Maybe we can add something like LRU cache with a timeout and move the complexity out to a caching utility rather than implementing custom logic in DagBag?

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 5 days if no further activity occurs. Thank you for your contributions.

@github-actionsgithub-actionsBot added the stale Stale PRs per the .github/workflows/stale.yml policy file label Dec 15, 2023
@potiuk
potiuk deleted the dont-get-dag-we-already-have-it branch October 1, 2024 08:16
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Schedulerincluding HA (high availability) schedulerstaleStale PRs per the .github/workflows/stale.yml policy file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@ashb@jscheffl@AutomationDev85@hussein-awala
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Don't get DAG out of DagBag when we already have it by ashb · Pull Request #35243 · apache/airflow · GitHub
Skip to content

Don't get DAG out of DagBag when we already have it - #35243

Closed
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it
Closed

Don't get DAG out of DagBag when we already have it#35243
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it

Conversation

@ashb

@ashbashb commented Oct 28, 2023

Copy link
Copy Markdown
Member

Two things here:

  1. By the ponit we are looking at the "callbacks" dagrun.dag will already be set, (the or dagbag.get_dag is a safety precaution. It might not be required or worth it)
  2. DagBag already is a cache. We don't need an extra caching layer on top of it.
    ifdag_idnotinself.dags:
    # Load from DB if not (yet) in the bag
    self._add_dag_from_db(dag_id=dag_id, session=session)
    returnself.dags.get(dag_id)

This "soft reverts" #30704 and removes the lru_cache

Two things here:
1. By the ponit we are looking at the "callbacks" `dagrun.dag` will
already be set, (the `or dagbag.get_dag` is a safety precaution. It
might not be required or worth it)
2. DagBag already _is_ a cache. We don't need an extra caching layer on
top of it.
This "soft reverts" #30704 and removes the lru_cache
@boring-cyborgboring-cyborgBot added the area:Scheduler including HA (high availability) scheduler label Oct 28, 2023

@hussein-awalahussein-awala 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.

From the other PR description:

With the caching we were able to increase scheduler performance. Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

We don't use the dagbag cache directly, instead we check if we need to update the dag and reload it from the DB:

# If DAG is in the DagBag, check the following
# 1. if time has come to check if DAG is updated (controlled by min_serialized_dag_fetch_secs)
# 2. check the last_updated and hash columns in SerializedDag table to see if
# Serialized DAG is updated
# 3. if (2) is yes, fetch the Serialized DAG.
# 4. if (2) returns None (i.e. Serialized DAG is deleted), remove dag from dagbag
# if it exists and return None.

So, I wonder if this refresh for some dags is necessary in our case (if so, your PR will be a bug fix) or if we need a local LRU cache to avoid reloading some dags from the DB.
(I'm talking about the revert of the second method _get_next_dagruns_to_examine and not the one which uses dag_run.dag)

)
for dag_run, callback_to_run in callback_tuples:
dag = cached_get_dag(dag_run.dag_id)
dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For instance, just before this loop are these two calls:

dag_runs=self._get_next_dagruns_to_examine(DagRunState.RUNNING, session)
# Bulk fetch the currently active dag runs for the dags we are# examining, rather than making one query per DagRuncallback_tuples=self._schedule_all_dag_runs(guard, dag_runs, session)

Both of those get the dag out of the dagbag which weren't affected by an LRU cache, but every dagrun we have here must have been in the call to _schedule_all_dag_runs.

@ashb

ashb commented Oct 28, 2023

Copy link
Copy Markdown
MemberAuthor

Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

The point is that dagbag.get_dag is already this cache. There were no numbers provided in that PR that show the PR actually makes any difference. My assumption is that this doesn't actually save anything (as not every call to dagbag.get_dag in the scheduler loop was replaced by a cache.

@jscheffl

Copy link
Copy Markdown
Contributor

Yes, in deep I was also scratching my head. Obviously there is a kind of basic caching but also with expiry check. The main driver for the lru_cache was the use in _get_next_dagruns_to_examine for the case if 200+ times the same DAG is queued. Then we don't need to check during one iteration over-and-over whether the DAG changed in between. That was the intend.
But the in-deep-investigation - and therefore the previous PR was made by @AutomationDev85 - who is probably back online on Monday.

@ashb

ashb commented Oct 30, 2023

Copy link
Copy Markdown
MemberAuthor

The difference between an LRU cache and the cache in dagbag is that the later does a datetime.now() call (more or less).

Additionally the change here to dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session) should negate even that call in 99% of cases without needing the extra cache.

@AutomationDev85

Copy link
Copy Markdown
Contributor

During the runtime measurement a few month ago I run into issue that these lines consumed a lot of time. When we schedule DAG with many DAG runs. I was not aware that there is some basic caching but my measurement looked like it was not working for our use case :( Any idea why it was not working if you try to schedule 200 DAG Runs of the same DAG?

@jscheffl

jscheffl commented Oct 30, 2023

Copy link
Copy Markdown
Contributor

While sitting in the train failing to build the Airflow container via breeze I was re-inspecting the code. I believe I now saw the root cause for the performance problem we had and why @AutomationDev85 added the cache around it.

There are multiple dicts used in DagBag to cache the DAG objects and the timestamps and versions. The attribute self.dags_last_fetched stores when last time a DAG was fetched and checks for the standard of 10 seconds to ensure cache is not too old. But in DagBag.get_dag() (current main in https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L207) it always hits the DB with a query if serialized cached value smells like too old. But actually the last check time is only updated if the DAG has been re-parsed in between (see https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L221). Otherwise the date marker when last time checked is not touched. This is inconsistent and means if performance problems hit the DAG parsing (or the DAG parser is running in the scheduler and the scheduler loop hits some performance problems >10 seconds) then this increases DB load.
So when removing the lru cache as by previous PR #30704 then we would need to fix the caching logic and at least update the self.dags_last_fetched[dag_id] to the current time (and not the time of last DAG parsing == sd_last_updated_datetime).

But I feel like the code in this section has grown over time and it took me three times to understand the logic. Comparing to an LRU cache this is looking very complex. Maybe a round of refactoring for DB Caching would be good - Maybe we can add something like LRU cache with a timeout and move the complexity out to a caching utility rather than implementing custom logic in DagBag?

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 5 days if no further activity occurs. Thank you for your contributions.

@github-actionsgithub-actionsBot added the stale Stale PRs per the .github/workflows/stale.yml policy file label Dec 15, 2023
@potiuk
potiuk deleted the dont-get-dag-we-already-have-it branch October 1, 2024 08:16
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Schedulerincluding HA (high availability) schedulerstaleStale PRs per the .github/workflows/stale.yml policy file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@ashb@jscheffl@AutomationDev85@hussein-awala
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Don't get DAG out of DagBag when we already have it by ashb · Pull Request #35243 · apache/airflow · GitHub
Skip to content

Don't get DAG out of DagBag when we already have it - #35243

Closed
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it
Closed

Don't get DAG out of DagBag when we already have it#35243
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it

Conversation

@ashb

@ashbashb commented Oct 28, 2023

Copy link
Copy Markdown
Member

Two things here:

  1. By the ponit we are looking at the "callbacks" dagrun.dag will already be set, (the or dagbag.get_dag is a safety precaution. It might not be required or worth it)
  2. DagBag already is a cache. We don't need an extra caching layer on top of it.
    ifdag_idnotinself.dags:
    # Load from DB if not (yet) in the bag
    self._add_dag_from_db(dag_id=dag_id, session=session)
    returnself.dags.get(dag_id)

This "soft reverts" #30704 and removes the lru_cache

Two things here:
1. By the ponit we are looking at the "callbacks" `dagrun.dag` will
already be set, (the `or dagbag.get_dag` is a safety precaution. It
might not be required or worth it)
2. DagBag already _is_ a cache. We don't need an extra caching layer on
top of it.
This "soft reverts" #30704 and removes the lru_cache
@boring-cyborgboring-cyborgBot added the area:Scheduler including HA (high availability) scheduler label Oct 28, 2023

@hussein-awalahussein-awala 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.

From the other PR description:

With the caching we were able to increase scheduler performance. Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

We don't use the dagbag cache directly, instead we check if we need to update the dag and reload it from the DB:

# If DAG is in the DagBag, check the following
# 1. if time has come to check if DAG is updated (controlled by min_serialized_dag_fetch_secs)
# 2. check the last_updated and hash columns in SerializedDag table to see if
# Serialized DAG is updated
# 3. if (2) is yes, fetch the Serialized DAG.
# 4. if (2) returns None (i.e. Serialized DAG is deleted), remove dag from dagbag
# if it exists and return None.

So, I wonder if this refresh for some dags is necessary in our case (if so, your PR will be a bug fix) or if we need a local LRU cache to avoid reloading some dags from the DB.
(I'm talking about the revert of the second method _get_next_dagruns_to_examine and not the one which uses dag_run.dag)

)
for dag_run, callback_to_run in callback_tuples:
dag = cached_get_dag(dag_run.dag_id)
dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For instance, just before this loop are these two calls:

dag_runs=self._get_next_dagruns_to_examine(DagRunState.RUNNING, session)
# Bulk fetch the currently active dag runs for the dags we are# examining, rather than making one query per DagRuncallback_tuples=self._schedule_all_dag_runs(guard, dag_runs, session)

Both of those get the dag out of the dagbag which weren't affected by an LRU cache, but every dagrun we have here must have been in the call to _schedule_all_dag_runs.

@ashb

ashb commented Oct 28, 2023

Copy link
Copy Markdown
MemberAuthor

Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

The point is that dagbag.get_dag is already this cache. There were no numbers provided in that PR that show the PR actually makes any difference. My assumption is that this doesn't actually save anything (as not every call to dagbag.get_dag in the scheduler loop was replaced by a cache.

@jscheffl

Copy link
Copy Markdown
Contributor

Yes, in deep I was also scratching my head. Obviously there is a kind of basic caching but also with expiry check. The main driver for the lru_cache was the use in _get_next_dagruns_to_examine for the case if 200+ times the same DAG is queued. Then we don't need to check during one iteration over-and-over whether the DAG changed in between. That was the intend.
But the in-deep-investigation - and therefore the previous PR was made by @AutomationDev85 - who is probably back online on Monday.

@ashb

ashb commented Oct 30, 2023

Copy link
Copy Markdown
MemberAuthor

The difference between an LRU cache and the cache in dagbag is that the later does a datetime.now() call (more or less).

Additionally the change here to dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session) should negate even that call in 99% of cases without needing the extra cache.

@AutomationDev85

Copy link
Copy Markdown
Contributor

During the runtime measurement a few month ago I run into issue that these lines consumed a lot of time. When we schedule DAG with many DAG runs. I was not aware that there is some basic caching but my measurement looked like it was not working for our use case :( Any idea why it was not working if you try to schedule 200 DAG Runs of the same DAG?

@jscheffl

jscheffl commented Oct 30, 2023

Copy link
Copy Markdown
Contributor

While sitting in the train failing to build the Airflow container via breeze I was re-inspecting the code. I believe I now saw the root cause for the performance problem we had and why @AutomationDev85 added the cache around it.

There are multiple dicts used in DagBag to cache the DAG objects and the timestamps and versions. The attribute self.dags_last_fetched stores when last time a DAG was fetched and checks for the standard of 10 seconds to ensure cache is not too old. But in DagBag.get_dag() (current main in https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L207) it always hits the DB with a query if serialized cached value smells like too old. But actually the last check time is only updated if the DAG has been re-parsed in between (see https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L221). Otherwise the date marker when last time checked is not touched. This is inconsistent and means if performance problems hit the DAG parsing (or the DAG parser is running in the scheduler and the scheduler loop hits some performance problems >10 seconds) then this increases DB load.
So when removing the lru cache as by previous PR #30704 then we would need to fix the caching logic and at least update the self.dags_last_fetched[dag_id] to the current time (and not the time of last DAG parsing == sd_last_updated_datetime).

But I feel like the code in this section has grown over time and it took me three times to understand the logic. Comparing to an LRU cache this is looking very complex. Maybe a round of refactoring for DB Caching would be good - Maybe we can add something like LRU cache with a timeout and move the complexity out to a caching utility rather than implementing custom logic in DagBag?

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 5 days if no further activity occurs. Thank you for your contributions.

@github-actionsgithub-actionsBot added the stale Stale PRs per the .github/workflows/stale.yml policy file label Dec 15, 2023
@potiuk
potiuk deleted the dont-get-dag-we-already-have-it branch October 1, 2024 08:16
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Schedulerincluding HA (high availability) schedulerstaleStale PRs per the .github/workflows/stale.yml policy file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@ashb@jscheffl@AutomationDev85@hussein-awala
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Don't get DAG out of DagBag when we already have it by ashb · Pull Request #35243 · apache/airflow · GitHub
Skip to content

Don't get DAG out of DagBag when we already have it - #35243

Closed
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it
Closed

Don't get DAG out of DagBag when we already have it#35243
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it

Conversation

@ashb

@ashbashb commented Oct 28, 2023

Copy link
Copy Markdown
Member

Two things here:

  1. By the ponit we are looking at the "callbacks" dagrun.dag will already be set, (the or dagbag.get_dag is a safety precaution. It might not be required or worth it)
  2. DagBag already is a cache. We don't need an extra caching layer on top of it.
    ifdag_idnotinself.dags:
    # Load from DB if not (yet) in the bag
    self._add_dag_from_db(dag_id=dag_id, session=session)
    returnself.dags.get(dag_id)

This "soft reverts" #30704 and removes the lru_cache

Two things here:
1. By the ponit we are looking at the "callbacks" `dagrun.dag` will
already be set, (the `or dagbag.get_dag` is a safety precaution. It
might not be required or worth it)
2. DagBag already _is_ a cache. We don't need an extra caching layer on
top of it.
This "soft reverts" #30704 and removes the lru_cache
@boring-cyborgboring-cyborgBot added the area:Scheduler including HA (high availability) scheduler label Oct 28, 2023

@hussein-awalahussein-awala 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.

From the other PR description:

With the caching we were able to increase scheduler performance. Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

We don't use the dagbag cache directly, instead we check if we need to update the dag and reload it from the DB:

# If DAG is in the DagBag, check the following
# 1. if time has come to check if DAG is updated (controlled by min_serialized_dag_fetch_secs)
# 2. check the last_updated and hash columns in SerializedDag table to see if
# Serialized DAG is updated
# 3. if (2) is yes, fetch the Serialized DAG.
# 4. if (2) returns None (i.e. Serialized DAG is deleted), remove dag from dagbag
# if it exists and return None.

So, I wonder if this refresh for some dags is necessary in our case (if so, your PR will be a bug fix) or if we need a local LRU cache to avoid reloading some dags from the DB.
(I'm talking about the revert of the second method _get_next_dagruns_to_examine and not the one which uses dag_run.dag)

)
for dag_run, callback_to_run in callback_tuples:
dag = cached_get_dag(dag_run.dag_id)
dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For instance, just before this loop are these two calls:

dag_runs=self._get_next_dagruns_to_examine(DagRunState.RUNNING, session)
# Bulk fetch the currently active dag runs for the dags we are# examining, rather than making one query per DagRuncallback_tuples=self._schedule_all_dag_runs(guard, dag_runs, session)

Both of those get the dag out of the dagbag which weren't affected by an LRU cache, but every dagrun we have here must have been in the call to _schedule_all_dag_runs.

@ashb

ashb commented Oct 28, 2023

Copy link
Copy Markdown
MemberAuthor

Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

The point is that dagbag.get_dag is already this cache. There were no numbers provided in that PR that show the PR actually makes any difference. My assumption is that this doesn't actually save anything (as not every call to dagbag.get_dag in the scheduler loop was replaced by a cache.

@jscheffl

Copy link
Copy Markdown
Contributor

Yes, in deep I was also scratching my head. Obviously there is a kind of basic caching but also with expiry check. The main driver for the lru_cache was the use in _get_next_dagruns_to_examine for the case if 200+ times the same DAG is queued. Then we don't need to check during one iteration over-and-over whether the DAG changed in between. That was the intend.
But the in-deep-investigation - and therefore the previous PR was made by @AutomationDev85 - who is probably back online on Monday.

@ashb

ashb commented Oct 30, 2023

Copy link
Copy Markdown
MemberAuthor

The difference between an LRU cache and the cache in dagbag is that the later does a datetime.now() call (more or less).

Additionally the change here to dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session) should negate even that call in 99% of cases without needing the extra cache.

@AutomationDev85

Copy link
Copy Markdown
Contributor

During the runtime measurement a few month ago I run into issue that these lines consumed a lot of time. When we schedule DAG with many DAG runs. I was not aware that there is some basic caching but my measurement looked like it was not working for our use case :( Any idea why it was not working if you try to schedule 200 DAG Runs of the same DAG?

@jscheffl

jscheffl commented Oct 30, 2023

Copy link
Copy Markdown
Contributor

While sitting in the train failing to build the Airflow container via breeze I was re-inspecting the code. I believe I now saw the root cause for the performance problem we had and why @AutomationDev85 added the cache around it.

There are multiple dicts used in DagBag to cache the DAG objects and the timestamps and versions. The attribute self.dags_last_fetched stores when last time a DAG was fetched and checks for the standard of 10 seconds to ensure cache is not too old. But in DagBag.get_dag() (current main in https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L207) it always hits the DB with a query if serialized cached value smells like too old. But actually the last check time is only updated if the DAG has been re-parsed in between (see https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L221). Otherwise the date marker when last time checked is not touched. This is inconsistent and means if performance problems hit the DAG parsing (or the DAG parser is running in the scheduler and the scheduler loop hits some performance problems >10 seconds) then this increases DB load.
So when removing the lru cache as by previous PR #30704 then we would need to fix the caching logic and at least update the self.dags_last_fetched[dag_id] to the current time (and not the time of last DAG parsing == sd_last_updated_datetime).

But I feel like the code in this section has grown over time and it took me three times to understand the logic. Comparing to an LRU cache this is looking very complex. Maybe a round of refactoring for DB Caching would be good - Maybe we can add something like LRU cache with a timeout and move the complexity out to a caching utility rather than implementing custom logic in DagBag?

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 5 days if no further activity occurs. Thank you for your contributions.

@github-actionsgithub-actionsBot added the stale Stale PRs per the .github/workflows/stale.yml policy file label Dec 15, 2023
@potiuk
potiuk deleted the dont-get-dag-we-already-have-it branch October 1, 2024 08:16
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Schedulerincluding HA (high availability) schedulerstaleStale PRs per the .github/workflows/stale.yml policy file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@ashb@jscheffl@AutomationDev85@hussein-awala
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Don't get DAG out of DagBag when we already have it by ashb · Pull Request #35243 · apache/airflow · GitHub
Skip to content

Don't get DAG out of DagBag when we already have it - #35243

Closed
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it
Closed

Don't get DAG out of DagBag when we already have it#35243
ashb wants to merge 1 commit into
mainfrom
dont-get-dag-we-already-have-it

Conversation

@ashb

@ashbashb commented Oct 28, 2023

Copy link
Copy Markdown
Member

Two things here:

  1. By the ponit we are looking at the "callbacks" dagrun.dag will already be set, (the or dagbag.get_dag is a safety precaution. It might not be required or worth it)
  2. DagBag already is a cache. We don't need an extra caching layer on top of it.
    ifdag_idnotinself.dags:
    # Load from DB if not (yet) in the bag
    self._add_dag_from_db(dag_id=dag_id, session=session)
    returnself.dags.get(dag_id)

This "soft reverts" #30704 and removes the lru_cache

Two things here:
1. By the ponit we are looking at the "callbacks" `dagrun.dag` will
already be set, (the `or dagbag.get_dag` is a safety precaution. It
might not be required or worth it)
2. DagBag already _is_ a cache. We don't need an extra caching layer on
top of it.
This "soft reverts" #30704 and removes the lru_cache
@boring-cyborgboring-cyborgBot added the area:Scheduler including HA (high availability) scheduler label Oct 28, 2023

@hussein-awalahussein-awala 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.

From the other PR description:

With the caching we were able to increase scheduler performance. Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

We don't use the dagbag cache directly, instead we check if we need to update the dag and reload it from the DB:

# If DAG is in the DagBag, check the following
# 1. if time has come to check if DAG is updated (controlled by min_serialized_dag_fetch_secs)
# 2. check the last_updated and hash columns in SerializedDag table to see if
# Serialized DAG is updated
# 3. if (2) is yes, fetch the Serialized DAG.
# 4. if (2) returns None (i.e. Serialized DAG is deleted), remove dag from dagbag
# if it exists and return None.

So, I wonder if this refresh for some dags is necessary in our case (if so, your PR will be a bug fix) or if we need a local LRU cache to avoid reloading some dags from the DB.
(I'm talking about the revert of the second method _get_next_dagruns_to_examine and not the one which uses dag_run.dag)

)
for dag_run, callback_to_run in callback_tuples:
dag = cached_get_dag(dag_run.dag_id)
dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

For instance, just before this loop are these two calls:

dag_runs=self._get_next_dagruns_to_examine(DagRunState.RUNNING, session)
# Bulk fetch the currently active dag runs for the dags we are# examining, rather than making one query per DagRuncallback_tuples=self._schedule_all_dag_runs(guard, dag_runs, session)

Both of those get the dag out of the dagbag which weren't affected by an LRU cache, but every dagrun we have here must have been in the call to _schedule_all_dag_runs.

@ashb

ashb commented Oct 28, 2023

Copy link
Copy Markdown
MemberAuthor

Because the time on our slow DB to query the dag took between 50ms and 250ms and if you execute this only once or 60 times during one scheduler loop run this makes a big change.

The point is that dagbag.get_dag is already this cache. There were no numbers provided in that PR that show the PR actually makes any difference. My assumption is that this doesn't actually save anything (as not every call to dagbag.get_dag in the scheduler loop was replaced by a cache.

@jscheffl

Copy link
Copy Markdown
Contributor

Yes, in deep I was also scratching my head. Obviously there is a kind of basic caching but also with expiry check. The main driver for the lru_cache was the use in _get_next_dagruns_to_examine for the case if 200+ times the same DAG is queued. Then we don't need to check during one iteration over-and-over whether the DAG changed in between. That was the intend.
But the in-deep-investigation - and therefore the previous PR was made by @AutomationDev85 - who is probably back online on Monday.

@ashb

ashb commented Oct 30, 2023

Copy link
Copy Markdown
MemberAuthor

The difference between an LRU cache and the cache in dagbag is that the later does a datetime.now() call (more or less).

Additionally the change here to dag = dag_run.dag or self.dagbag.get_dag(dag_run.dag_id, session=session) should negate even that call in 99% of cases without needing the extra cache.

@AutomationDev85

Copy link
Copy Markdown
Contributor

During the runtime measurement a few month ago I run into issue that these lines consumed a lot of time. When we schedule DAG with many DAG runs. I was not aware that there is some basic caching but my measurement looked like it was not working for our use case :( Any idea why it was not working if you try to schedule 200 DAG Runs of the same DAG?

@jscheffl

jscheffl commented Oct 30, 2023

Copy link
Copy Markdown
Contributor

While sitting in the train failing to build the Airflow container via breeze I was re-inspecting the code. I believe I now saw the root cause for the performance problem we had and why @AutomationDev85 added the cache around it.

There are multiple dicts used in DagBag to cache the DAG objects and the timestamps and versions. The attribute self.dags_last_fetched stores when last time a DAG was fetched and checks for the standard of 10 seconds to ensure cache is not too old. But in DagBag.get_dag() (current main in https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L207) it always hits the DB with a query if serialized cached value smells like too old. But actually the last check time is only updated if the DAG has been re-parsed in between (see https://github.com/apache/airflow/blob/main/airflow/models/dagbag.py#L221). Otherwise the date marker when last time checked is not touched. This is inconsistent and means if performance problems hit the DAG parsing (or the DAG parser is running in the scheduler and the scheduler loop hits some performance problems >10 seconds) then this increases DB load.
So when removing the lru cache as by previous PR #30704 then we would need to fix the caching logic and at least update the self.dags_last_fetched[dag_id] to the current time (and not the time of last DAG parsing == sd_last_updated_datetime).

But I feel like the code in this section has grown over time and it took me three times to understand the logic. Comparing to an LRU cache this is looking very complex. Maybe a round of refactoring for DB Caching would be good - Maybe we can add something like LRU cache with a timeout and move the complexity out to a caching utility rather than implementing custom logic in DagBag?

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 5 days if no further activity occurs. Thank you for your contributions.

@github-actionsgithub-actionsBot added the stale Stale PRs per the .github/workflows/stale.yml policy file label Dec 15, 2023
@potiuk
potiuk deleted the dont-get-dag-we-already-have-it branch October 1, 2024 08:16
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Schedulerincluding HA (high availability) schedulerstaleStale PRs per the .github/workflows/stale.yml policy file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@ashb@jscheffl@AutomationDev85@hussein-awala