This is an automated email from the ASF dual-hosted git repository.

pierrejeambrun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 93d722e9b82 Re-render only the changed column when Grid summaries 
stream in (#69917)
93d722e9b82 is described below

commit 93d722e9b82faca703e34ea0e5461fbd4689bdaf
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Thu Jul 16 11:12:38 2026 +0200

    Re-render only the changed column when Grid summaries stream in (#69917)
    
    The Dag Grid streams TI summaries as one NDJSON line per run and stores 
them in
    a Map; every line replaced the Map and re-rendered Grid. Because 
useVirtualizer
    (@tanstack/react-virtual) is on the React Compiler's known-incompatible 
list, the
    compiler skips optimizing all of Grid — so flatNodes and the click handlers 
got
    fresh references on every render and every column re-rendered on every 
line, not
    just the run whose summary arrived. On a ~20-run x 30-task grid that was 117
    spurious column re-renders per load.
    
    Memoize the values handed down to the columns by hand — the escape hatch the
    compiler's own bailout message points to for incompatible-library APIs. 
Measured
    on the same grid: column re-renders per load dropped 171 -> 54 (all 
legitimate),
    grid load render work ~47% lower.
    
    Also drop a redundant immediate stream restart: an unconditional refresh 
tick
    fired the instant the interval effect mounted, aborting and reopening the
    just-opened mount stream (an AbortError on every grid mount with active 
runs).
    
    related: #69531
---
 .../src/airflow/ui/src/layouts/Details/Grid/Grid.tsx      | 15 ++++++++++-----
 .../src/airflow/ui/src/queries/useGridTISummaries.ts      |  6 ++++--
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
index eed38a10ad4..a1cb9d36e99 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
@@ -20,7 +20,7 @@ import { Box, Flex } from "@chakra-ui/react";
 import { useVirtualizer } from "@tanstack/react-virtual";
 import dayjs from "dayjs";
 import dayjsDuration from "dayjs/plugin/duration";
-import { useRef } from "react";
+import { useCallback, useMemo, useRef } from "react";
 import type { RefObject } from "react";
 import { useParams, useSearchParams } from "react-router-dom";
 
@@ -141,7 +141,12 @@ export const Grid = ({
     showVersionIndicatorMode,
   });
 
-  const { flatNodes } = flattenNodes(dagStructure, openGroupIds);
+  // React Compiler skips optimizing this whole component: `useVirtualizer` 
(@tanstack/react-virtual) is
+  // on the compiler's known-incompatible list — its return value exposes 
functions that can't be
+  // memoized safely — so it declines to memoize anything in Grid. Without the 
manual memoization here
+  // and on the click handlers below, `flatNodes` and the handlers get fresh 
references every render, so
+  // each TI-summaries stream line re-renders every column instead of only the 
run whose summary changed.
+  const { flatNodes } = useMemo(() => flattenNodes(dagStructure, 
openGroupIds), [dagStructure, openGroupIds]);
 
   const taskNameColumnWidthPx = showGantt ? 
estimateTaskNameColumnWidthPx(flatNodes) : undefined;
 
@@ -166,9 +171,9 @@ export const Grid = ({
     tasks: flatNodes,
   });
 
-  const handleRowClick = () => setMode(NavigationModes.TASK);
-  const handleCellClick = () => setMode(NavigationModes.TI);
-  const handleColumnClick = () => setMode(NavigationModes.RUN);
+  const handleRowClick = useCallback(() => setMode(NavigationModes.TASK), 
[setMode]);
+  const handleCellClick = useCallback(() => setMode(NavigationModes.TI), 
[setMode]);
+  const handleColumnClick = useCallback(() => setMode(NavigationModes.RUN), 
[setMode]);
 
   const rowVirtualizer = useVirtualizer({
     count: flatNodes.length,
diff --git a/airflow-core/src/airflow/ui/src/queries/useGridTISummaries.ts 
b/airflow-core/src/airflow/ui/src/queries/useGridTISummaries.ts
index 1ec67127b3f..d90b2f0078e 100644
--- a/airflow-core/src/airflow/ui/src/queries/useGridTISummaries.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useGridTISummaries.ts
@@ -144,8 +144,10 @@ export const useGridTiSummariesStream = ({
       return undefined;
     }
 
-    // Kick off an immediate refresh so the stream doesn't have to wait for 
the first interval to elapse.
-    setRefreshTick((tick) => tick + 1);
+    // The stream already fetches on mount and whenever runIdsKey changes, so 
there is no first-interval
+    // wait to avoid. Bumping refreshTick here would abort that just-opened 
mount stream and immediately
+    // reopen it — a redundant connection plus an AbortError on every grid 
mount — so let the interval be
+    // the only re-stream trigger.
     const timer = setInterval(() => {
       setRefreshTick((tick) => tick + 1);
     }, baseRefetchInterval);

Reply via email to