Patch/cascade component perf#256037
Closed
eokoneyo wants to merge 5 commits intoelastic:mainfrom
Closed
Conversation
Contributor
|
🤖 Jobs for this PR can be triggered through checkboxes. 🚧
ℹ️ To trigger the CI, please tick the checkbox below 👇
|
c489a84 to
0934461
Compare
Contributor
Author
|
Test out the cascade in storybook with complex render with the multiple state story rendering exactly 100K rows here |
bb83bcb to
74314b7
Compare
eokoneyo
added a commit
that referenced
this pull request
Mar 6, 2026
## Summary Part of #255745, Culled from #256037. This PR removes the usage of the the eui component `TruncateText` for titles in the discover cascade experience, in replacement for the much simpler [`line-clamp`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/line-clamp) property. line clamp is supported in most browsers besides IE and Opera Mini [see here](https://caniuse.com/?search=line-clamp). ### Why are we doing this? The component `TruncateText` uses a canvas to measure text to determine how text can be displayed in addition it also adds a resizer so it can make adjustments for the amount for the text to display to the user. For our use case this is an overkill given we were already virtualising items, so this resulted in a large chunk of unnecessary work being done. See trace profile below; <img width="2558" height="1092" alt="Screenshot 2026-03-05 at 15 02 01" src="https://github.com/user-attachments/assets/a4675691-36f4-41fe-9895-d11c0c576871" /> _Figure: Trace profile with eui truncate text_ <img width="2558" height="1142" alt="Screenshot 2026-03-05 at 15 15 26" src="https://github.com/user-attachments/assets/a52c3224-f68e-4ba6-9926-7cc7a7f7d0fa" /> _Figure: Trace profile without eui truncate text_ ### Performance improvements (Chrome DevTools Trace comparison analysis by cursor) | Metric | Before (with `EuiTextTruncate`) | After (CSS clamp) | Improvement | |---|---|---|---| | **UpdateLayoutTree (style recalc)** | **2,328 ms** across 150 events | **528 ms** across 69 events | **~77% reduction** (1,800 ms saved) | | **Total rendering cost** | **2,391 ms** | **632 ms** | **~74% reduction** | | **Long tasks > 50 ms (count)** | 6 | 18 | More tasks, but far smaller (see below) | | **Worst long task** | 2,374 ms | 2,256 ms | ~5% shorter | | **Mean long task duration** | 1,010 ms | 299 ms | **~70% reduction** | | **Long tasks > 1 s** | 3 | 1 | Eliminated 2 multi-second jank frames | ### Key takeaways - **Style recalculation is the headline win.** The before trace shows 150 `UpdateLayoutTree` events — all in the 10–31 ms range — totalling 2.3 s. After the change there are only 69 events (37 of which are sub-1 ms), totalling 528 ms. This is because `EuiTextTruncate` forces the browser to synchronously recalculate styles to measure text width on every render pass. - **Long task severity drops dramatically.** Before the change, 3 long tasks exceeded 1 second (up to 2.4 s), and the mean long task was over 1 s. After the change only 1 long task exceeds 1 s (the initial pointer-out handler, which is unrelated to truncation), and 9 of the 18 long tasks fall in the 100–200 ms range — well below the "noticeable jank" threshold. - **Scroll is significantly smoother.** The before trace processed only 7 scroll events during the capture window; the after trace processed 43 scroll events in roughly the same window. This means the browser was able to dispatch and paint far more scroll frames, resulting in visibly smoother scrolling. - **The rendering pipeline is no longer the bottleneck.** Total rendering cost dropped from 2.4 s to 0.6 s, meaning the main thread spends ~74% less time in layout/style work during scroll. ### What changed Replaced `<EuiTextTruncate>` (JS-measured truncation with a render callback) with a CSS `-webkit-line-clamp: 2` approach on the `<h4>` element, which lets the browser handle truncation natively without requiring layout measurement. The visual result is equivalent — text is truncated with an ellipsis — but with no JavaScript overhead. That been said there's still couple of improvements to be made. ### Visuals <img width="993" height="758" alt="Screenshot 2026-03-05 at 15 38 36" src="https://github.com/user-attachments/assets/3c799132-4280-45c0-95f9-d3dc33d3c67e" /> _Figure: Cascade experience with Truncate Text, note text ellipsis shows at one line_ <img width="1152" height="901" alt="Screenshot 2026-03-05 at 15 37 42" src="https://github.com/user-attachments/assets/5da7666c-cfd3-4d07-905b-7e3988037f94" /> _Figure: Cascade experience with line clamp, note text ellipsis shows at 2 lines to match CATEGORIZE pattern behaviour_ <!-- ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... -->
This was referenced Mar 6, 2026
kapral18
pushed a commit
to kapral18/kibana
that referenced
this pull request
Mar 9, 2026
## Summary Part of elastic#255745, Culled from elastic#256037. This PR removes the usage of the the eui component `TruncateText` for titles in the discover cascade experience, in replacement for the much simpler [`line-clamp`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/line-clamp) property. line clamp is supported in most browsers besides IE and Opera Mini [see here](https://caniuse.com/?search=line-clamp). ### Why are we doing this? The component `TruncateText` uses a canvas to measure text to determine how text can be displayed in addition it also adds a resizer so it can make adjustments for the amount for the text to display to the user. For our use case this is an overkill given we were already virtualising items, so this resulted in a large chunk of unnecessary work being done. See trace profile below; <img width="2558" height="1092" alt="Screenshot 2026-03-05 at 15 02 01" src="https://github.com/user-attachments/assets/a4675691-36f4-41fe-9895-d11c0c576871" /> _Figure: Trace profile with eui truncate text_ <img width="2558" height="1142" alt="Screenshot 2026-03-05 at 15 15 26" src="https://github.com/user-attachments/assets/a52c3224-f68e-4ba6-9926-7cc7a7f7d0fa" /> _Figure: Trace profile without eui truncate text_ ### Performance improvements (Chrome DevTools Trace comparison analysis by cursor) | Metric | Before (with `EuiTextTruncate`) | After (CSS clamp) | Improvement | |---|---|---|---| | **UpdateLayoutTree (style recalc)** | **2,328 ms** across 150 events | **528 ms** across 69 events | **~77% reduction** (1,800 ms saved) | | **Total rendering cost** | **2,391 ms** | **632 ms** | **~74% reduction** | | **Long tasks > 50 ms (count)** | 6 | 18 | More tasks, but far smaller (see below) | | **Worst long task** | 2,374 ms | 2,256 ms | ~5% shorter | | **Mean long task duration** | 1,010 ms | 299 ms | **~70% reduction** | | **Long tasks > 1 s** | 3 | 1 | Eliminated 2 multi-second jank frames | ### Key takeaways - **Style recalculation is the headline win.** The before trace shows 150 `UpdateLayoutTree` events — all in the 10–31 ms range — totalling 2.3 s. After the change there are only 69 events (37 of which are sub-1 ms), totalling 528 ms. This is because `EuiTextTruncate` forces the browser to synchronously recalculate styles to measure text width on every render pass. - **Long task severity drops dramatically.** Before the change, 3 long tasks exceeded 1 second (up to 2.4 s), and the mean long task was over 1 s. After the change only 1 long task exceeds 1 s (the initial pointer-out handler, which is unrelated to truncation), and 9 of the 18 long tasks fall in the 100–200 ms range — well below the "noticeable jank" threshold. - **Scroll is significantly smoother.** The before trace processed only 7 scroll events during the capture window; the after trace processed 43 scroll events in roughly the same window. This means the browser was able to dispatch and paint far more scroll frames, resulting in visibly smoother scrolling. - **The rendering pipeline is no longer the bottleneck.** Total rendering cost dropped from 2.4 s to 0.6 s, meaning the main thread spends ~74% less time in layout/style work during scroll. ### What changed Replaced `<EuiTextTruncate>` (JS-measured truncation with a render callback) with a CSS `-webkit-line-clamp: 2` approach on the `<h4>` element, which lets the browser handle truncation natively without requiring layout measurement. The visual result is equivalent — text is truncated with an ellipsis — but with no JavaScript overhead. That been said there's still couple of improvements to be made. ### Visuals <img width="993" height="758" alt="Screenshot 2026-03-05 at 15 38 36" src="https://github.com/user-attachments/assets/3c799132-4280-45c0-95f9-d3dc33d3c67e" /> _Figure: Cascade experience with Truncate Text, note text ellipsis shows at one line_ <img width="1152" height="901" alt="Screenshot 2026-03-05 at 15 37 42" src="https://github.com/user-attachments/assets/5da7666c-cfd3-4d07-905b-7e3988037f94" /> _Figure: Cascade experience with line clamp, note text ellipsis shows at 2 lines to match CATEGORIZE pattern behaviour_ <!-- ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... -->
74314b7 to
90b2af5
Compare
90b2af5 to
ea34f5e
Compare
1 task
eokoneyo
added a commit
that referenced
this pull request
Mar 9, 2026
## Summary This PR is part of the cascade performance improvement work(#255745), culled from #256037. Adds CSS `contain: layout style` and `overflowAnchor: none` to the DataCascade virtualized scroll container and row wrappers, scoping layout/style invalidation so that row DOM mutations during scrolling cannot trigger full-document relayouts. ## Changes - **`data_cascade_impl.styles.tsx`** (`cascadeTreeGridBlock`): add `contain: 'layout style'` and `overflowAnchor: 'none'` - **`cascade_row.styles.ts`** (`rowWrapper`): add `contain: 'layout style'` ## Performance impact (Generated by Cursor) Compared using Chrome DevTools trace profiles (baseline vs this PR) during scroll interaction: | Metric (per-second normalized) | Baseline | PR | Change | |---|---|---|---| | Layout total time | 3,737 us/s | 1,686 us/s | **-55%** | | Full (non-partial) layouts | 9 | **0** | **-100%** | | UpdateLayoutTree time | 63,821 us/s | 38,009 us/s | **-40%** | | Paint time | 4,392 us/s | 1,025 us/s | **-77%** | | Layout invalidations | 13.3/s | 6.7/s | **-50%** | | Style recalc triggers | 20.6/s | 9.6/s | **-54%** | | Long task (>50ms) time | 459,137 us/s | 237,431 us/s | **-48%** | | Avg frame time | 5.9 ms | 3.4 ms | **-42%** | | Worst-case frame | 203 ms | 73 ms | **-64%** | | Max dirty objects/layout | 864 | 414 | **-52%** | | Layout shifts | 4 | 2 | **-50%** | The headline result is **complete elimination of full document layouts** during scroll. The two nested containment boundaries (container + rows) ensure layout invalidation from virtualizer DOM mutations stays confined to the contained subtree, turning expensive full-document relayouts into cheap scoped partial layouts. `overflowAnchor: none` prevents additional layout passes from scroll anchoring adjustments during DOM recycling. <!-- ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... -->
kibanamachine
pushed a commit
to kibanamachine/kibana
that referenced
this pull request
Mar 9, 2026
…56630) ## Summary This PR is part of the cascade performance improvement work(elastic#255745), culled from elastic#256037. Adds CSS `contain: layout style` and `overflowAnchor: none` to the DataCascade virtualized scroll container and row wrappers, scoping layout/style invalidation so that row DOM mutations during scrolling cannot trigger full-document relayouts. ## Changes - **`data_cascade_impl.styles.tsx`** (`cascadeTreeGridBlock`): add `contain: 'layout style'` and `overflowAnchor: 'none'` - **`cascade_row.styles.ts`** (`rowWrapper`): add `contain: 'layout style'` ## Performance impact (Generated by Cursor) Compared using Chrome DevTools trace profiles (baseline vs this PR) during scroll interaction: | Metric (per-second normalized) | Baseline | PR | Change | |---|---|---|---| | Layout total time | 3,737 us/s | 1,686 us/s | **-55%** | | Full (non-partial) layouts | 9 | **0** | **-100%** | | UpdateLayoutTree time | 63,821 us/s | 38,009 us/s | **-40%** | | Paint time | 4,392 us/s | 1,025 us/s | **-77%** | | Layout invalidations | 13.3/s | 6.7/s | **-50%** | | Style recalc triggers | 20.6/s | 9.6/s | **-54%** | | Long task (>50ms) time | 459,137 us/s | 237,431 us/s | **-48%** | | Avg frame time | 5.9 ms | 3.4 ms | **-42%** | | Worst-case frame | 203 ms | 73 ms | **-64%** | | Max dirty objects/layout | 864 | 414 | **-52%** | | Layout shifts | 4 | 2 | **-50%** | The headline result is **complete elimination of full document layouts** during scroll. The two nested containment boundaries (container + rows) ensure layout invalidation from virtualizer DOM mutations stays confined to the contained subtree, turning expensive full-document relayouts into cheap scoped partial layouts. `overflowAnchor: none` prevents additional layout passes from scroll anchoring adjustments during DOM recycling. <!-- ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --> (cherry picked from commit 52968c7)
kibanamachine
added a commit
that referenced
this pull request
Mar 9, 2026
…6630) (#256800) # Backport This will backport the following commits from `main` to `9.3`: - [[Cascade] Specify strict layout guidance for rows and cell (#256630)](#256630) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Eyo O. Eyo","email":"7893459+eokoneyo@users.noreply.github.com"},"sourceCommit":{"committedDate":"2026-03-09T21:34:44Z","message":"[Cascade] Specify strict layout guidance for rows and cell (#256630)\n\n## Summary\nThis PR is part of the cascade performance improvement\nwork(#255745), culled from\nhttps://github.com//pull/256037.\n\nAdds CSS `contain: layout style` and `overflowAnchor: none` to the\nDataCascade virtualized scroll container and row wrappers, scoping\nlayout/style invalidation so that row DOM mutations during scrolling\ncannot trigger full-document relayouts.\n\n## Changes\n\n- **`data_cascade_impl.styles.tsx`** (`cascadeTreeGridBlock`): add\n`contain: 'layout style'` and `overflowAnchor: 'none'`\n- **`cascade_row.styles.ts`** (`rowWrapper`): add `contain: 'layout\nstyle'`\n\n## Performance impact (Generated by Cursor)\n\nCompared using Chrome DevTools trace profiles (baseline vs this PR)\nduring scroll interaction:\n\n| Metric (per-second normalized) | Baseline | PR | Change |\n|---|---|---|---|\n| Layout total time | 3,737 us/s | 1,686 us/s | **-55%** |\n| Full (non-partial) layouts | 9 | **0** | **-100%** |\n| UpdateLayoutTree time | 63,821 us/s | 38,009 us/s | **-40%** |\n| Paint time | 4,392 us/s | 1,025 us/s | **-77%** |\n| Layout invalidations | 13.3/s | 6.7/s | **-50%** |\n| Style recalc triggers | 20.6/s | 9.6/s | **-54%** |\n| Long task (>50ms) time | 459,137 us/s | 237,431 us/s | **-48%** |\n| Avg frame time | 5.9 ms | 3.4 ms | **-42%** |\n| Worst-case frame | 203 ms | 73 ms | **-64%** |\n| Max dirty objects/layout | 864 | 414 | **-52%** |\n| Layout shifts | 4 | 2 | **-50%** |\n\nThe headline result is **complete elimination of full document layouts**\nduring scroll. The two nested containment boundaries (container + rows)\nensure layout invalidation from virtualizer DOM mutations stays confined\nto the contained subtree, turning expensive full-document relayouts into\ncheap scoped partial layouts. `overflowAnchor: none` prevents additional\nlayout passes from scroll anchoring adjustments during DOM recycling.\n\n","sha":"52968c7bcc3bb75f83fa57c3bbb8fcffaa260454","branchLabelMapping":{"^v9.4.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:SharedUX","backport:version","v9.4.0","v9.3.2"],"title":"[Cascade] Specify strict layout guidance for rows and cell","number":256630,"url":"https://github.com/elastic/kibana/pull/256630","mergeCommit":{"message":"[Cascade] Specify strict layout guidance for rows and cell (#256630)\n\n## Summary\nThis PR is part of the cascade performance improvement\nwork(#255745), culled from\nhttps://github.com//pull/256037.\n\nAdds CSS `contain: layout style` and `overflowAnchor: none` to the\nDataCascade virtualized scroll container and row wrappers, scoping\nlayout/style invalidation so that row DOM mutations during scrolling\ncannot trigger full-document relayouts.\n\n## Changes\n\n- **`data_cascade_impl.styles.tsx`** (`cascadeTreeGridBlock`): add\n`contain: 'layout style'` and `overflowAnchor: 'none'`\n- **`cascade_row.styles.ts`** (`rowWrapper`): add `contain: 'layout\nstyle'`\n\n## Performance impact (Generated by Cursor)\n\nCompared using Chrome DevTools trace profiles (baseline vs this PR)\nduring scroll interaction:\n\n| Metric (per-second normalized) | Baseline | PR | Change |\n|---|---|---|---|\n| Layout total time | 3,737 us/s | 1,686 us/s | **-55%** |\n| Full (non-partial) layouts | 9 | **0** | **-100%** |\n| UpdateLayoutTree time | 63,821 us/s | 38,009 us/s | **-40%** |\n| Paint time | 4,392 us/s | 1,025 us/s | **-77%** |\n| Layout invalidations | 13.3/s | 6.7/s | **-50%** |\n| Style recalc triggers | 20.6/s | 9.6/s | **-54%** |\n| Long task (>50ms) time | 459,137 us/s | 237,431 us/s | **-48%** |\n| Avg frame time | 5.9 ms | 3.4 ms | **-42%** |\n| Worst-case frame | 203 ms | 73 ms | **-64%** |\n| Max dirty objects/layout | 864 | 414 | **-52%** |\n| Layout shifts | 4 | 2 | **-50%** |\n\nThe headline result is **complete elimination of full document layouts**\nduring scroll. The two nested containment boundaries (container + rows)\nensure layout invalidation from virtualizer DOM mutations stays confined\nto the contained subtree, turning expensive full-document relayouts into\ncheap scoped partial layouts. `overflowAnchor: none` prevents additional\nlayout passes from scroll anchoring adjustments during DOM recycling.\n\n","sha":"52968c7bcc3bb75f83fa57c3bbb8fcffaa260454"}},"sourceBranch":"main","suggestedTargetBranches":["9.3"],"targetPullRequestStates":[{"branch":"main","label":"v9.4.0","branchLabelMappingKey":"^v9.4.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/256630","number":256630,"mergeCommit":{"message":"[Cascade] Specify strict layout guidance for rows and cell (#256630)\n\n## Summary\nThis PR is part of the cascade performance improvement\nwork(#255745), culled from\nhttps://github.com//pull/256037.\n\nAdds CSS `contain: layout style` and `overflowAnchor: none` to the\nDataCascade virtualized scroll container and row wrappers, scoping\nlayout/style invalidation so that row DOM mutations during scrolling\ncannot trigger full-document relayouts.\n\n## Changes\n\n- **`data_cascade_impl.styles.tsx`** (`cascadeTreeGridBlock`): add\n`contain: 'layout style'` and `overflowAnchor: 'none'`\n- **`cascade_row.styles.ts`** (`rowWrapper`): add `contain: 'layout\nstyle'`\n\n## Performance impact (Generated by Cursor)\n\nCompared using Chrome DevTools trace profiles (baseline vs this PR)\nduring scroll interaction:\n\n| Metric (per-second normalized) | Baseline | PR | Change |\n|---|---|---|---|\n| Layout total time | 3,737 us/s | 1,686 us/s | **-55%** |\n| Full (non-partial) layouts | 9 | **0** | **-100%** |\n| UpdateLayoutTree time | 63,821 us/s | 38,009 us/s | **-40%** |\n| Paint time | 4,392 us/s | 1,025 us/s | **-77%** |\n| Layout invalidations | 13.3/s | 6.7/s | **-50%** |\n| Style recalc triggers | 20.6/s | 9.6/s | **-54%** |\n| Long task (>50ms) time | 459,137 us/s | 237,431 us/s | **-48%** |\n| Avg frame time | 5.9 ms | 3.4 ms | **-42%** |\n| Worst-case frame | 203 ms | 73 ms | **-64%** |\n| Max dirty objects/layout | 864 | 414 | **-52%** |\n| Layout shifts | 4 | 2 | **-50%** |\n\nThe headline result is **complete elimination of full document layouts**\nduring scroll. The two nested containment boundaries (container + rows)\nensure layout invalidation from virtualizer DOM mutations stays confined\nto the contained subtree, turning expensive full-document relayouts into\ncheap scoped partial layouts. `overflowAnchor: none` prevents additional\nlayout passes from scroll anchoring adjustments during DOM recycling.\n\n","sha":"52968c7bcc3bb75f83fa57c3bbb8fcffaa260454"}},{"branch":"9.3","label":"v9.3.2","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Eyo O. Eyo <7893459+eokoneyo@users.noreply.github.com>
ea34f5e to
07ccdfb
Compare
Contributor
eokoneyo
added a commit
that referenced
this pull request
Mar 10, 2026
## Summary This PR is part of the cascade performance improvement work(#255745), culled from #256037. Refactors the `ScrollSyncProvider` to eliminate per-row scroll overhead by centralizing scroll coordination into a single capture-phase listener with an allocation-free hot path. ### What changed - **Single capture-phase scroll listener**: Replaced N individual resize observers and `onScroll` React handlers (one per row) with a single native `scroll` listener attached at the provider's wrapper element using `{ capture: true, passive: true }`. This bypasses React's synthetic event system entirely for programmatic scroll events and uses a scroll-leader pattern to prevent the O(N^2) feedback cascade where each `scrollTo` write re-triggers handlers on all other rows. - **`useSyncExternalStore` for scroll state**: Replaced per-row `useState` + `onScroll` with a centralized pub/sub store. The provider computes three boolean flags (`isScrollable`, `canScrollLeft`, `canScrollRight`) and only emits to subscribers when the booleans actually change — not on every pixel of scroll. Consumers call `useSyncExternalStore(subscribe, getSnapshot)` for batched, stable re-renders. - **Cached layout dimensions**: `scrollWidth` and `clientWidth` are cached in the provider and only refreshed via `ResizeObserver` or on hover. The scroll hot path never reads these layout-forcing properties from the DOM. - **Zero-allocation scroll hot path**: All intermediate objects are mutated in place to minimize GC pressure: - Scroll state booleans are compared inline; a new snapshot object is only allocated when they change (~2-3 times per session, not per event). - A single reusable `ScrollToOptions` ref is shared across all `scrollTo` calls instead of allocating `{ left, behavior: 'instant' }` per container per event. - `cachedDimensions` are updated via property assignment, not object replacement. - `for...of` loops replace `.forEach` to avoid closure allocation. - The scroll-leader timeout callback is hoisted to a stable `useCallback` ref. - **Instant programmatic scrolling**: All programmatic `scrollTo` calls use `{ behavior: 'instant' }` to override the CSS `scroll-behavior: smooth` on scroll containers, ensuring synchronized rows update in the same frame as the leader. User-initiated smooth scroll (e.g. arrow button clicks) is unaffected since those rely on the CSS property directly. ### Performance results Measured via Chrome DevTools traces on a data cascade with ~40 visible rows, comparing the optimized implementation against the original baseline: | Category | Metric | Baseline | Optimized | Change | |---|---|---|---|---| | **GC** | Events | 4,949 | 3,818 | **-23%** | | | Time | 2,121 ms | 2,099 ms | **-1%** (parity) | | **Layout** | Style recalc time | 722 ms | 668 ms | **-7%** | | | Layout time | 47 ms | 40 ms | **-15%** | | **Frames** | P95 inter-frame gap | 111 ms | 111 ms | parity | | | P50 FPS | 59.9 | 59.8 | parity | **Key wins**: GC events are 23% below baseline, layout/style costs are reduced, and the P95 inter-frame gap and median FPS are at parity. The architectural shift from N independent scroll handlers to a single coordinated listener also reduces the total number of scroll dispatch events by 26%. Most importantly we are giving back rendering time to consumers of the component. How to test - Verify that scroll sync works; - Navigate to the storybook link that show cases the cascade rendering multiple stats, on resizing the viewport to the extent where the stats get clipped, hovering over any one of the rows should reveal the scroll button - Clicking the scroll button should move all rows in the specified direction <!-- ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --> --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
34b9117 to
2a95539
Compare
qn895
pushed a commit
to qn895/kibana
that referenced
this pull request
Mar 11, 2026
## Summary Part of elastic#255745, Culled from elastic#256037. This PR removes the usage of the the eui component `TruncateText` for titles in the discover cascade experience, in replacement for the much simpler [`line-clamp`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/line-clamp) property. line clamp is supported in most browsers besides IE and Opera Mini [see here](https://caniuse.com/?search=line-clamp). ### Why are we doing this? The component `TruncateText` uses a canvas to measure text to determine how text can be displayed in addition it also adds a resizer so it can make adjustments for the amount for the text to display to the user. For our use case this is an overkill given we were already virtualising items, so this resulted in a large chunk of unnecessary work being done. See trace profile below; <img width="2558" height="1092" alt="Screenshot 2026-03-05 at 15 02 01" src="https://github.com/user-attachments/assets/a4675691-36f4-41fe-9895-d11c0c576871" /> _Figure: Trace profile with eui truncate text_ <img width="2558" height="1142" alt="Screenshot 2026-03-05 at 15 15 26" src="https://github.com/user-attachments/assets/a52c3224-f68e-4ba6-9926-7cc7a7f7d0fa" /> _Figure: Trace profile without eui truncate text_ ### Performance improvements (Chrome DevTools Trace comparison analysis by cursor) | Metric | Before (with `EuiTextTruncate`) | After (CSS clamp) | Improvement | |---|---|---|---| | **UpdateLayoutTree (style recalc)** | **2,328 ms** across 150 events | **528 ms** across 69 events | **~77% reduction** (1,800 ms saved) | | **Total rendering cost** | **2,391 ms** | **632 ms** | **~74% reduction** | | **Long tasks > 50 ms (count)** | 6 | 18 | More tasks, but far smaller (see below) | | **Worst long task** | 2,374 ms | 2,256 ms | ~5% shorter | | **Mean long task duration** | 1,010 ms | 299 ms | **~70% reduction** | | **Long tasks > 1 s** | 3 | 1 | Eliminated 2 multi-second jank frames | ### Key takeaways - **Style recalculation is the headline win.** The before trace shows 150 `UpdateLayoutTree` events — all in the 10–31 ms range — totalling 2.3 s. After the change there are only 69 events (37 of which are sub-1 ms), totalling 528 ms. This is because `EuiTextTruncate` forces the browser to synchronously recalculate styles to measure text width on every render pass. - **Long task severity drops dramatically.** Before the change, 3 long tasks exceeded 1 second (up to 2.4 s), and the mean long task was over 1 s. After the change only 1 long task exceeds 1 s (the initial pointer-out handler, which is unrelated to truncation), and 9 of the 18 long tasks fall in the 100–200 ms range — well below the "noticeable jank" threshold. - **Scroll is significantly smoother.** The before trace processed only 7 scroll events during the capture window; the after trace processed 43 scroll events in roughly the same window. This means the browser was able to dispatch and paint far more scroll frames, resulting in visibly smoother scrolling. - **The rendering pipeline is no longer the bottleneck.** Total rendering cost dropped from 2.4 s to 0.6 s, meaning the main thread spends ~74% less time in layout/style work during scroll. ### What changed Replaced `<EuiTextTruncate>` (JS-measured truncation with a render callback) with a CSS `-webkit-line-clamp: 2` approach on the `<h4>` element, which lets the browser handle truncation natively without requiring layout measurement. The visual result is equivalent — text is truncated with an ellipsis — but with no JavaScript overhead. That been said there's still couple of improvements to be made. ### Visuals <img width="993" height="758" alt="Screenshot 2026-03-05 at 15 38 36" src="https://github.com/user-attachments/assets/3c799132-4280-45c0-95f9-d3dc33d3c67e" /> _Figure: Cascade experience with Truncate Text, note text ellipsis shows at one line_ <img width="1152" height="901" alt="Screenshot 2026-03-05 at 15 37 42" src="https://github.com/user-attachments/assets/5da7666c-cfd3-4d07-905b-7e3988037f94" /> _Figure: Cascade experience with line clamp, note text ellipsis shows at 2 lines to match CATEGORIZE pattern behaviour_ <!-- ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... -->
qn895
pushed a commit
to qn895/kibana
that referenced
this pull request
Mar 11, 2026
…56630) ## Summary This PR is part of the cascade performance improvement work(elastic#255745), culled from elastic#256037. Adds CSS `contain: layout style` and `overflowAnchor: none` to the DataCascade virtualized scroll container and row wrappers, scoping layout/style invalidation so that row DOM mutations during scrolling cannot trigger full-document relayouts. ## Changes - **`data_cascade_impl.styles.tsx`** (`cascadeTreeGridBlock`): add `contain: 'layout style'` and `overflowAnchor: 'none'` - **`cascade_row.styles.ts`** (`rowWrapper`): add `contain: 'layout style'` ## Performance impact (Generated by Cursor) Compared using Chrome DevTools trace profiles (baseline vs this PR) during scroll interaction: | Metric (per-second normalized) | Baseline | PR | Change | |---|---|---|---| | Layout total time | 3,737 us/s | 1,686 us/s | **-55%** | | Full (non-partial) layouts | 9 | **0** | **-100%** | | UpdateLayoutTree time | 63,821 us/s | 38,009 us/s | **-40%** | | Paint time | 4,392 us/s | 1,025 us/s | **-77%** | | Layout invalidations | 13.3/s | 6.7/s | **-50%** | | Style recalc triggers | 20.6/s | 9.6/s | **-54%** | | Long task (>50ms) time | 459,137 us/s | 237,431 us/s | **-48%** | | Avg frame time | 5.9 ms | 3.4 ms | **-42%** | | Worst-case frame | 203 ms | 73 ms | **-64%** | | Max dirty objects/layout | 864 | 414 | **-52%** | | Layout shifts | 4 | 2 | **-50%** | The headline result is **complete elimination of full document layouts** during scroll. The two nested containment boundaries (container + rows) ensure layout invalidation from virtualizer DOM mutations stays confined to the contained subtree, turning expensive full-document relayouts into cheap scoped partial layouts. `overflowAnchor: none` prevents additional layout passes from scroll anchoring adjustments during DOM recycling. <!-- ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... -->
qn895
pushed a commit
to qn895/kibana
that referenced
this pull request
Mar 11, 2026
## Summary This PR is part of the cascade performance improvement work(elastic#255745), culled from elastic#256037. Refactors the `ScrollSyncProvider` to eliminate per-row scroll overhead by centralizing scroll coordination into a single capture-phase listener with an allocation-free hot path. ### What changed - **Single capture-phase scroll listener**: Replaced N individual resize observers and `onScroll` React handlers (one per row) with a single native `scroll` listener attached at the provider's wrapper element using `{ capture: true, passive: true }`. This bypasses React's synthetic event system entirely for programmatic scroll events and uses a scroll-leader pattern to prevent the O(N^2) feedback cascade where each `scrollTo` write re-triggers handlers on all other rows. - **`useSyncExternalStore` for scroll state**: Replaced per-row `useState` + `onScroll` with a centralized pub/sub store. The provider computes three boolean flags (`isScrollable`, `canScrollLeft`, `canScrollRight`) and only emits to subscribers when the booleans actually change — not on every pixel of scroll. Consumers call `useSyncExternalStore(subscribe, getSnapshot)` for batched, stable re-renders. - **Cached layout dimensions**: `scrollWidth` and `clientWidth` are cached in the provider and only refreshed via `ResizeObserver` or on hover. The scroll hot path never reads these layout-forcing properties from the DOM. - **Zero-allocation scroll hot path**: All intermediate objects are mutated in place to minimize GC pressure: - Scroll state booleans are compared inline; a new snapshot object is only allocated when they change (~2-3 times per session, not per event). - A single reusable `ScrollToOptions` ref is shared across all `scrollTo` calls instead of allocating `{ left, behavior: 'instant' }` per container per event. - `cachedDimensions` are updated via property assignment, not object replacement. - `for...of` loops replace `.forEach` to avoid closure allocation. - The scroll-leader timeout callback is hoisted to a stable `useCallback` ref. - **Instant programmatic scrolling**: All programmatic `scrollTo` calls use `{ behavior: 'instant' }` to override the CSS `scroll-behavior: smooth` on scroll containers, ensuring synchronized rows update in the same frame as the leader. User-initiated smooth scroll (e.g. arrow button clicks) is unaffected since those rely on the CSS property directly. ### Performance results Measured via Chrome DevTools traces on a data cascade with ~40 visible rows, comparing the optimized implementation against the original baseline: | Category | Metric | Baseline | Optimized | Change | |---|---|---|---|---| | **GC** | Events | 4,949 | 3,818 | **-23%** | | | Time | 2,121 ms | 2,099 ms | **-1%** (parity) | | **Layout** | Style recalc time | 722 ms | 668 ms | **-7%** | | | Layout time | 47 ms | 40 ms | **-15%** | | **Frames** | P95 inter-frame gap | 111 ms | 111 ms | parity | | | P50 FPS | 59.9 | 59.8 | parity | **Key wins**: GC events are 23% below baseline, layout/style costs are reduced, and the P95 inter-frame gap and median FPS are at parity. The architectural shift from N independent scroll handlers to a single coordinated listener also reduces the total number of scroll dispatch events by 26%. Most importantly we are giving back rendering time to consumers of the component. How to test - Verify that scroll sync works; - Navigate to the storybook link that show cases the cascade rendering multiple stats, on resizing the viewport to the extent where the stats get clipped, hovering over any one of the rows should reveal the scroll button - Clicking the scroll button should move all rows in the specified direction <!-- ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --> --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
- provide escape hatch for fetch all persisted data if it exists
2a95539 to
ad2c235
Compare
- structural changes to virtualizer implementation - favor anchor item index over scroll offset for scroll restoration - use cascade virtualizer for nesting in discover - deferred mounting for nested rows - add foundation for controlled child virtualizer - add orchestration for nested virtualized children - adjustments so returning expanded rows do not show loading skeleton
- configure cascade multiple stats story to render 100K rows - split data cascade stories into managable chunks
ad2c235 to
8e98b10
Compare
sorenlouv
pushed a commit
that referenced
this pull request
Mar 17, 2026
## Summary This PR is part of the cascade performance improvement work(#255745), culled from #256037. Adds CSS `contain: layout style` and `overflowAnchor: none` to the DataCascade virtualized scroll container and row wrappers, scoping layout/style invalidation so that row DOM mutations during scrolling cannot trigger full-document relayouts. ## Changes - **`data_cascade_impl.styles.tsx`** (`cascadeTreeGridBlock`): add `contain: 'layout style'` and `overflowAnchor: 'none'` - **`cascade_row.styles.ts`** (`rowWrapper`): add `contain: 'layout style'` ## Performance impact (Generated by Cursor) Compared using Chrome DevTools trace profiles (baseline vs this PR) during scroll interaction: | Metric (per-second normalized) | Baseline | PR | Change | |---|---|---|---| | Layout total time | 3,737 us/s | 1,686 us/s | **-55%** | | Full (non-partial) layouts | 9 | **0** | **-100%** | | UpdateLayoutTree time | 63,821 us/s | 38,009 us/s | **-40%** | | Paint time | 4,392 us/s | 1,025 us/s | **-77%** | | Layout invalidations | 13.3/s | 6.7/s | **-50%** | | Style recalc triggers | 20.6/s | 9.6/s | **-54%** | | Long task (>50ms) time | 459,137 us/s | 237,431 us/s | **-48%** | | Avg frame time | 5.9 ms | 3.4 ms | **-42%** | | Worst-case frame | 203 ms | 73 ms | **-64%** | | Max dirty objects/layout | 864 | 414 | **-52%** | | Layout shifts | 4 | 2 | **-50%** | The headline result is **complete elimination of full document layouts** during scroll. The two nested containment boundaries (container + rows) ensure layout invalidation from virtualizer DOM mutations stays confined to the contained subtree, turning expensive full-document relayouts into cheap scoped partial layouts. `overflowAnchor: none` prevents additional layout passes from scroll anchoring adjustments during DOM recycling. <!-- ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... -->
sorenlouv
pushed a commit
that referenced
this pull request
Mar 17, 2026
## Summary This PR is part of the cascade performance improvement work(#255745), culled from #256037. Refactors the `ScrollSyncProvider` to eliminate per-row scroll overhead by centralizing scroll coordination into a single capture-phase listener with an allocation-free hot path. ### What changed - **Single capture-phase scroll listener**: Replaced N individual resize observers and `onScroll` React handlers (one per row) with a single native `scroll` listener attached at the provider's wrapper element using `{ capture: true, passive: true }`. This bypasses React's synthetic event system entirely for programmatic scroll events and uses a scroll-leader pattern to prevent the O(N^2) feedback cascade where each `scrollTo` write re-triggers handlers on all other rows. - **`useSyncExternalStore` for scroll state**: Replaced per-row `useState` + `onScroll` with a centralized pub/sub store. The provider computes three boolean flags (`isScrollable`, `canScrollLeft`, `canScrollRight`) and only emits to subscribers when the booleans actually change — not on every pixel of scroll. Consumers call `useSyncExternalStore(subscribe, getSnapshot)` for batched, stable re-renders. - **Cached layout dimensions**: `scrollWidth` and `clientWidth` are cached in the provider and only refreshed via `ResizeObserver` or on hover. The scroll hot path never reads these layout-forcing properties from the DOM. - **Zero-allocation scroll hot path**: All intermediate objects are mutated in place to minimize GC pressure: - Scroll state booleans are compared inline; a new snapshot object is only allocated when they change (~2-3 times per session, not per event). - A single reusable `ScrollToOptions` ref is shared across all `scrollTo` calls instead of allocating `{ left, behavior: 'instant' }` per container per event. - `cachedDimensions` are updated via property assignment, not object replacement. - `for...of` loops replace `.forEach` to avoid closure allocation. - The scroll-leader timeout callback is hoisted to a stable `useCallback` ref. - **Instant programmatic scrolling**: All programmatic `scrollTo` calls use `{ behavior: 'instant' }` to override the CSS `scroll-behavior: smooth` on scroll containers, ensuring synchronized rows update in the same frame as the leader. User-initiated smooth scroll (e.g. arrow button clicks) is unaffected since those rely on the CSS property directly. ### Performance results Measured via Chrome DevTools traces on a data cascade with ~40 visible rows, comparing the optimized implementation against the original baseline: | Category | Metric | Baseline | Optimized | Change | |---|---|---|---|---| | **GC** | Events | 4,949 | 3,818 | **-23%** | | | Time | 2,121 ms | 2,099 ms | **-1%** (parity) | | **Layout** | Style recalc time | 722 ms | 668 ms | **-7%** | | | Layout time | 47 ms | 40 ms | **-15%** | | **Frames** | P95 inter-frame gap | 111 ms | 111 ms | parity | | | P50 FPS | 59.9 | 59.8 | parity | **Key wins**: GC events are 23% below baseline, layout/style costs are reduced, and the P95 inter-frame gap and median FPS are at parity. The architectural shift from N independent scroll handlers to a single coordinated listener also reduces the total number of scroll dispatch events by 26%. Most importantly we are giving back rendering time to consumers of the component. How to test - Verify that scroll sync works; - Navigate to the storybook link that show cases the cascade rendering multiple stats, on resizing the viewport to the extent where the stats get clipped, hovering over any one of the rows should reveal the scroll button - Clicking the scroll button should move all rows in the specified direction <!-- ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --> --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
eokoneyo
added a commit
that referenced
this pull request
Mar 30, 2026
## Summary This PR is part of the cascade performance improvement work(#255745), culled from #256037. Also resolves #255745 In this PR the virtualizer implementation has been reworked, notable amongst the implementation change; - Introduces a ChildVirtualizerController that orchestrates nested virtualized lists within the data cascade, replacing the previous approach where each leaf cell independently managed its own useVirtualizer instance and manually wired scroll elements, margins, and offsets via individual props - Adds staggered activation for child virtualizers so expanded rows mount their heavy content (e.g. UnifiedDataTable) progressively rather than all at once, preventing main-thread stalls - introduces item-index-based anchoring (initialAnchorItemIndex), which survives row resizes and remeasurements more reliably than a raw pixel offset - Defers initial overscan to zero on mount and applies the configured value after the first paint, reducing DOM node count and layout thrashing during initial render - Exposes connectedChildren state and scrollAnchorItemIndex through the public API snapshot, enabling external consumers to observe nested virtualizer state. - Row measurements are now cached by the row key, this enables stable reference for previously measured rows reducing the style recalculation that needs to be performed when a new row gets rendered into the DOM on scroll - Lifts the skeleton/content gate from the consumer (ESQLDataCascadeLeafCell) into the library (CascadeRowCellPrimitive), so the skeleton is coordinated by the controller's activation signal rather than being opt-in per consumer ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. <!-- - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials --> - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios <!-- - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --> --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Matthias Polman <matthias.wilhelm@elastic.co>
Contributor
Author
|
Closing this PR as all the work that came out from the investigation into the performance of cascade in discover has been merged in. |
jeramysoucy
pushed a commit
to jeramysoucy/kibana
that referenced
this pull request
Apr 1, 2026
## Summary This PR is part of the cascade performance improvement work(elastic#255745), culled from elastic#256037. Also resolves elastic#255745 In this PR the virtualizer implementation has been reworked, notable amongst the implementation change; - Introduces a ChildVirtualizerController that orchestrates nested virtualized lists within the data cascade, replacing the previous approach where each leaf cell independently managed its own useVirtualizer instance and manually wired scroll elements, margins, and offsets via individual props - Adds staggered activation for child virtualizers so expanded rows mount their heavy content (e.g. UnifiedDataTable) progressively rather than all at once, preventing main-thread stalls - introduces item-index-based anchoring (initialAnchorItemIndex), which survives row resizes and remeasurements more reliably than a raw pixel offset - Defers initial overscan to zero on mount and applies the configured value after the first paint, reducing DOM node count and layout thrashing during initial render - Exposes connectedChildren state and scrollAnchorItemIndex through the public API snapshot, enabling external consumers to observe nested virtualizer state. - Row measurements are now cached by the row key, this enables stable reference for previously measured rows reducing the style recalculation that needs to be performed when a new row gets rendered into the DOM on scroll - Lifts the skeleton/content gate from the consumer (ESQLDataCascadeLeafCell) into the library (CascadeRowCellPrimitive), so the skeleton is coordinated by the controller's activation signal rather than being opt-in per consumer ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. <!-- - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials --> - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios <!-- - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --> --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Matthias Polman <matthias.wilhelm@elastic.co>
paulinashakirova
pushed a commit
to paulinashakirova/kibana
that referenced
this pull request
Apr 2, 2026
## Summary This PR is part of the cascade performance improvement work(elastic#255745), culled from elastic#256037. Also resolves elastic#255745 In this PR the virtualizer implementation has been reworked, notable amongst the implementation change; - Introduces a ChildVirtualizerController that orchestrates nested virtualized lists within the data cascade, replacing the previous approach where each leaf cell independently managed its own useVirtualizer instance and manually wired scroll elements, margins, and offsets via individual props - Adds staggered activation for child virtualizers so expanded rows mount their heavy content (e.g. UnifiedDataTable) progressively rather than all at once, preventing main-thread stalls - introduces item-index-based anchoring (initialAnchorItemIndex), which survives row resizes and remeasurements more reliably than a raw pixel offset - Defers initial overscan to zero on mount and applies the configured value after the first paint, reducing DOM node count and layout thrashing during initial render - Exposes connectedChildren state and scrollAnchorItemIndex through the public API snapshot, enabling external consumers to observe nested virtualizer state. - Row measurements are now cached by the row key, this enables stable reference for previously measured rows reducing the style recalculation that needs to be performed when a new row gets rendered into the DOM on scroll - Lifts the skeleton/content gate from the consumer (ESQLDataCascadeLeafCell) into the library (CascadeRowCellPrimitive), so the skeleton is coordinated by the controller's activation signal rather than being opt-in per consumer ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. <!-- - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials --> - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios <!-- - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --> --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Matthias Polman <matthias.wilhelm@elastic.co>
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
Fixes resulting from cascade performance investigation (#255745);
Apply the[Cascade] Specify strict layout guidance for rows and cell #256630containCSS property to rows and cells, to reduce style calculations on each subsequnt renderuseCascadeVirtualizerwithin discover, that controls it's nested virtualizer, handles removing the render coordination responsibility from the consumer. [Cascade] Virtualizer Improvements #256699Removes the unnecessary use of the EUI Truncate text component, this component creates a canvas for every row instance with it's own resize observers that also get called on width change, the functionality provided here has been replaced with a simple line-clamp property which widely supported except for in IE and Opera Mini, see here[Cascade] remove unnecessary use of EUITruncateText #256254Modifies the scroll sync util to use one single resize-observer across entire cascade element, said observer will be attached to only the current row being hover on, and will remain observing said till another row is hovered on since anyways we can only show the scroll button on single hover rows, there's no need to spawn all those observers.[Cascade] Improve scroll sync implementation #256511This PR is still a Work In Progress, created it so I can evaluate the experience for a production build in a la carte.