@@ -143,6 +143,8 @@ def get_dag_structure(
143143 """Return dag structure for grid view."""
144144 latest_serdag = _get_latest_serdag (dag_id , session )
145145 latest_dag = latest_serdag .dag
146+ latest_serdag_id = latest_serdag .id
147+ session .expunge (latest_serdag ) # allow GC of serdag; only latest_dag is needed from here
146148
147149 # Apply filtering if root task is specified
148150 if root :
@@ -176,12 +178,22 @@ def get_dag_structure(
176178 nodes = [task_group_to_dict_grid (x ) for x in task_group_sort (latest_dag .task_group )]
177179 return [GridNodeResponse (** n ) for n in nodes ]
178180
179- serdags = session .scalars (
180- select (SerializedDagModel ).where (
181+ # Process and merge the latest serdag first
182+ merged_nodes : list [dict [str , Any ]] = []
183+ nodes = [task_group_to_dict_grid (x ) for x in task_group_sort (latest_dag .task_group )]
184+ _merge_node_dicts (merged_nodes , nodes )
185+ del latest_dag
186+
187+ # Process serdags one by one and merge immediately to reduce memory usage.
188+ # Use yield_per() for streaming results and expunge each serdag after processing
189+ # to allow garbage collection and prevent memory buildup in the session identity map.
190+ serdags_query = (
191+ select (SerializedDagModel )
192+ .where (
181193 # Even though dag_id is filtered in base_query,
182194 # adding this line here can improve the performance of this endpoint
183195 SerializedDagModel .dag_id == dag_id ,
184- SerializedDagModel .id != latest_serdag . id ,
196+ SerializedDagModel .id != latest_serdag_id ,
185197 SerializedDagModel .dag_version_id .in_ (
186198 select (TaskInstance .dag_version_id )
187199 .join (TaskInstance .dag_run )
@@ -191,25 +203,25 @@ def get_dag_structure(
191203 .distinct ()
192204 ),
193205 )
206+ .execution_options (yield_per = 5 ) # balance between peak memory usage and round trips
194207 )
195- merged_nodes : list [dict [str , Any ]] = []
196- dags = [latest_dag ]
197- for serdag in serdags :
198- if serdag :
199- filtered_dag = serdag .dag
200- # Apply the same filtering to historical DAG versions
201- if root :
202- filtered_dag = filtered_dag .partial_subset (
203- task_ids = root ,
204- include_upstream = include_upstream ,
205- include_downstream = include_downstream ,
206- depth = depth ,
207- )
208- dags .append (filtered_dag )
209- for dag in dags :
210- nodes = [task_group_to_dict_grid (x ) for x in task_group_sort (dag .task_group )]
208+
209+ for serdag in session .scalars (serdags_query ):
210+ filtered_dag = serdag .dag
211+ # Apply the same filtering to historical DAG versions
212+ if root :
213+ filtered_dag = filtered_dag .partial_subset (
214+ task_ids = root ,
215+ include_upstream = include_upstream ,
216+ include_downstream = include_downstream ,
217+ depth = depth ,
218+ )
219+ # Merge immediately instead of collecting all DAGs in memory
220+ nodes = [task_group_to_dict_grid (x ) for x in task_group_sort (filtered_dag .task_group )]
211221 _merge_node_dicts (merged_nodes , nodes )
212222
223+ session .expunge (serdag ) # to allow garbage collection
224+
213225 return [GridNodeResponse (** n ) for n in merged_nodes ]
214226
215227
0 commit comments