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);