-
Notifications
You must be signed in to change notification settings - Fork 16.3k
apply gc.freeze in dag-processor to improve memory performance #60505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
potiuk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great - thanks for the thorough investigation. I am fine with early freezing, as this indeed should help.
What I think about the remaining memory growth - this might be connected with the imports initialized even before that - basically when airflow imports happen and I hope we will be able to get rid of it eventually when we implement explicit initialization rather than having all the import airlfow side effects we still have - so I would rather come back to the memory exercise after we do it.
|
I added the usual suspects for reviews -> if there will be no more comments, we can merge it and backport for 3.1.7 |
|
Merging. |
|
Thanks @wjddn279 ! |
(cherry picked from commit 9d31db3) Co-authored-by: Jeongwoo Do <48639483+wjddn279@users.noreply.github.com>
Motivation
discussed: https://lists.apache.org/thread/33hdp3hm705mzgrltv7o3468wvwbjsr3
closed: #56879
Insights
trying to apply gc.freeze / unfreeze cycle
First, to apply it in the same way as implemented in LocalExecutor, I perform gc.freeze and gc.unfreeze immediately before and after forking:
However, after applying this, memory inspection revealed excessive memory leaks.

This is the existing (v3.1.5) memory graph pattern.

Looking at the graph shape, you can see heap memory dropping at specific intervals, which appears to be a typical pattern of old gc, so I inferred there might be a connection.
I believe objects that should be cleaned up when old gc (generation 2 gc) occurs are frozen and thus escape gc, continuing to accumulate. As shown below, if you forcibly collect gc before freezing or reduce the generation 2 gc threshold to an extreme low, memory doesn't increase:
or
However, I judged that forcibly changing the gc flow would have very significant side effects, so I didn't apply this cycle.
apply it before parsing start
Instead, I inferred that simply freezing existing objects would be sufficient to help prevent COW.
There was a debate in the Python community about gc.freeze, and the main points are as follows:
https://discuss.python.org/t/it-seems-to-me-that-gc-freeze-is-pointless-and-the-documentation-misleading/71775
Since Airflow loads the same modules for all components and much of it goes unused, I judged that simply freezing these would be sufficient to prevent COW, and I froze objects created before the dag parsing loop runs.
Performance
I deployed both the existing 3.1.5 version image and an image with gc.freeze applied to k8s. I deployed the same plugins and dags to the dag-processor. The parsing stats are as follows (dag name is masked):
After monitoring memory usage for about two days, the results are as follows (x axis is time with KST):


I confirmed that the overall average memory usage is lower with gc.freeze, and the memory peak is also lower in the applied version. This difference can be attributed to improved memory usage due to COW prevention in the fork process when dag file parsing time is long. Looking broadly, both show a slight upward trend in memory usage, which I judge is ultimately a problem that needs to be resolved.
Was generative AI tooling used to co-author this PR?
{pr_number}.significant.rstor{issue_number}.significant.rst, in airflow-core/newsfragments.