Conversation
react-virtual force-rerenders TaskLogContent synchronously (flushSync) on every native scroll event whose visible range changes. useLogGroups rebuilt its entire visibleItems / originalToVisibleIndex / lineNumberToVisibleIndex index with an unmemoized O(total log lines) loop on every one of those renders, so scroll cost scaled with total log size instead of the number of rows actually on screen -- the mechanism behind apache#55173 ("Viewing large task logs get slower to scroll as they get larger"). Wrap the group-header index and the visible-items build in useMemo, keyed on the values they actually depend on (parsedLogs, expandedGroups, groupParentMap) so they only recompute when the log content or expand state actually changes. Generated-by: Claude Code Signed-off-by: Udaya Tejas <udayatejas2004@gmail.com>
udsy19
requested review from
bbovenzi,
choo121600,
guan404ming,
pierrejeambrun,
ryanahamilton,
shubhamraj-git and
vatsrahul1001
as code owners
September 16, 2026 18:45
|
Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
closes: #55173
TaskLogContentre-renders on every scroll event (@tanstack/react-virtual'sscroll handler calls
flushSync(rerender)synchronously whenever the visiblerow range changes), and
useLogGroupsrebuilt its entirevisibleItems/originalToVisibleIndex/lineNumberToVisibleIndexindex with anunmemoized loop over the whole
parsedLogsarray on every one of thoserenders — cost scaling with total log size, not with the handful of rows
actually on screen. That is the mechanism behind #55173: scrolling a large
task log gets progressively slower as the log grows, because every scroll
tick pays for a full rebuild of the whole log's visibility index, not just
the virtualized viewport.
Who reaches this: any user viewing task logs in the Airflow 3 UI
(
/dags/{dag_id}/runs/{run_id}/tasks/{task_id}, Logs tab) — the standard,documented way to read task output. Triggered by scrolling (wheel/trackpad)
a log of non-trivial size; every native
scrollevent on the log containerforces this rebuild.
Impact: perf-regression
Fix
Wrap the group-header index and the visible-items build in
useMemo, keyedon what they actually depend on (
parsedLogs,expandedGroups,groupParentMap), so they only recompute when the log content or theexpand/collapse state actually changes, not on every scroll-triggered
re-render.
Measured before/after
Isolated
useLogGroupswithrenderHook, forcing a re-render whose propsare identical to the previous render (the same shape of update
react-virtualtriggers on every scroll event whereparsedLogsandexpandedGroupsare unchanged). Measured withperformance.now(), medianof 15 repeat re-renders per log size, in
vitest(happy-dom).Before: 43.571 ms / After: 0.118 ms, on the largest log size measured
(see the full size sweep below).
Before the fix, cost scales with total log size (~120x from 1k to 100k
lines) and already exceeds a 16ms/60fps frame budget at 100k — on a hot path
that runs on every scroll event, not once per log load. After the fix, cost
is flat, since the rebuild only reruns when its inputs actually change.
Negative control
Added
useLogGroups.test.tsx› "does not rebuild the visible-items indexon a re-render that changes neither parsedLogs nor expand state": renders
the hook, captures
visibleItems/lineNumberToVisibleIndex, re-renderswith the identical
parsedLogsreference and no state change, and assertsreferential equality (
toBe) — true only if the memoization actually held.Test fails without the fix, passes with it:
Was generative AI tooling used to co-author this PR?
Claude Code
Generated-by: Claude Code following the guidelines
Signed-off-by: Udaya Tejas udayatejas2004@gmail.com