Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(dashboard): reduce number of rerenders of Charts #16444

Merged
merged 3 commits into from
Aug 26, 2021

Conversation

kgabryje
Copy link
Member

SUMMARY

We generate formData, which is passed through props to Chart, with a function getFormDataWithExtraFilters. The idea is to generate formData, save it in a cache object, and on each subsequent render use the cached version. However, due to incorrect equality checks, we never used the cached version and created a new formData object each time. That resulted not only in recalculating the same logic many times, but also triggered Chart's rerender, as formData object's reference was changing.
Moreover, in Chart component's shouldComponentUpdate function we triggered rerender each time chart's visibility changed - in other words, we rerendered all charts every time we changed a tab.
This PR fixes both of those behaviours.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

No changes

TESTING INSTRUCTIONS

Everything should work as it did before.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

CC @junlincc @jinghua-qa

Copy link
Member

@villebro villebro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM and appears to work well. Two minor non-blocking comments

Comment on lines 71 to 80
(cachedFiltersByChart[sliceId] || {}) === filters &&
(colorScheme == null ||
cachedFormdataByChart[sliceId].color_scheme === colorScheme) &&
cachedFormdataByChart[sliceId].color_namespace === colorNamespace &&
isEqual(cachedFormdataByChart[sliceId].label_colors, labelColors) &&
cachedFiltersByChart[sliceId] === filters &&
(colorScheme === null ||
cachedFormdataByChart[sliceId]?.color_scheme === colorScheme) &&
cachedFormdataByChart[sliceId]?.color_namespace === colorNamespace &&
isEqual(cachedFormdataByChart[sliceId]?.label_colors, labelColors) &&
!!cachedFormdataByChart[sliceId] &&
dataMask === undefined
areObjectsEqual(cachedFormdataByChart[sliceId]?.dataMask, dataMask, {
ignoreUndefined: true,
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're modifying code here and for the sake of DRY, would it make sense to break out

const cachedFormdata = cachedFormdataByChart[sliceId];

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Btw, Is it designed to not use the cache when the colorScheme is undefined? Because previous logic that colorScheme == null will return true if it is undefined and go to next condition. In addition, the above line of code seems that the colorScheme will not be null value whatever.

colorScheme: metadata?.color_scheme ?? undefined,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point Stephen! I think we can safely remove colorScheme === null and ?? undefined

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issues from both comments fixed

Comment on lines 104 to 105
static whyDidYouRender = true;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to start introducing these in the codebase now? I'm fine either way

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have no idea what is this. Since this is open source code base, everything should either self-explained or well documented for the whole community.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left that in by accident, thanks for pointing out!
@graceguo-supercat In order to detect unnecessary rerenders, I use a library whyDidYouRender (https://github.com/welldone-software/why-did-you-render). Adding a static property whyDidYouRender = true to a component tells the library to watch that component and report rerenders in the form of console logs. Of course, I meant to cleanup those props, but clearly I missed that one 🙂

@junlincc junlincc added hold:testing! On hold for testing test_priority:NA and removed hold:testing! On hold for testing labels Aug 25, 2021
@@ -87,7 +87,8 @@ const defaultProps = {
// resizing across all slices on a dashboard on every update
const RESIZE_TIMEOUT = 350;
const SHOULD_UPDATE_ON_PROP_CHANGES = Object.keys(propTypes).filter(
prop => prop !== 'width' && prop !== 'height',
prop =>
prop !== 'width' && prop !== 'height' && prop !== 'isComponentVisible',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work! this perf improvement work is great!
Before: chart component not update when width or height changed,
after: chart component not update width or height or visibility change.
But when chart component hide and show when tab changes, why chart component not need update?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In shouldComponentUpdate we have a if(isComponentVisible) statement followed by additional checks if component should render. The point is that we run those only when component is visible.
In other words, isComponentVisible should be used to decide if we should run checks to determine if component should be rerendered. However, the change of isComponentVisible alone shouldn't trigger a rerender, because it's already rendered. You can check out that behaviour by switching tabs back and forth. On the first change of tab, isComponentVisible changes, but so do other props, so the chart is rerendered. But if you go back to previous tab, only isComponentVisible changes, so there's no point in rerendering a chart because there are no changes that would affect that chart.

Sorry for rambling, I hope that it makes sense 😆

@codecov
Copy link

codecov bot commented Aug 26, 2021

Codecov Report

Merging #16444 (47b7db4) into master (970d762) will decrease coverage by 0.24%.
The diff coverage is 71.40%.

❗ Current head 47b7db4 differs from pull request most recent head ccd5980. Consider uploading reports for the commit ccd5980 to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##           master   #16444      +/-   ##
==========================================
- Coverage   76.64%   76.40%   -0.25%     
==========================================
  Files        1000     1002       +2     
  Lines       53489    53670     +181     
  Branches     6816     6855      +39     
==========================================
+ Hits        40996    41005       +9     
- Misses      12257    12426     +169     
- Partials      236      239       +3     
Flag Coverage Δ
hive ?
javascript 70.83% <64.47%> (-0.06%) ⬇️
presto ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...set-frontend/src/components/ModalTrigger/index.jsx 95.65% <ø> (ø)
...-frontend/src/components/OmniContainer/Omnibar.tsx 100.00% <ø> (ø)
...et-frontend/src/components/TableView/TableView.tsx 94.52% <ø> (ø)
superset-frontend/src/dashboard/actions/hydrate.js 1.72% <ø> (+0.02%) ⬆️
...et-frontend/src/dashboard/components/Dashboard.jsx 78.84% <ø> (ø)
...frontend/src/dashboard/components/Header/index.jsx 69.49% <ø> (ø)
...rontend/src/dashboard/containers/DashboardPage.tsx 0.00% <0.00%> (ø)
...perset-frontend/src/explore/components/Control.tsx 76.47% <ø> (ø)
...-frontend/src/explore/components/ControlHeader.jsx 86.20% <ø> (ø)
.../components/ExploreAdditionalActionsMenu/index.jsx 100.00% <ø> (ø)
... and 143 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 970d762...ccd5980. Read the comment docs.

Copy link

@graceguo-supercat graceguo-supercat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@kgabryje kgabryje merged commit 8ad495a into apache:master Aug 26, 2021
amitmiran137 pushed a commit to nielsen-oss/superset that referenced this pull request Aug 29, 2021
* upstream/master:
  fix: create example DB if needed (apache#16451)
  fix(native-filters): add handle undefined control value gracefully (apache#16468)
  Revert "chore: Changes the DatabaseSelector to use the new Select component (apache#16334)" (apache#16478)
  fix(explore): JS error for creating new metrics from columns (apache#16477)
  fix: queryEditor bug (apache#16452)
  docs: make code snippet usable with required imports (apache#16473)
  perf(dashboard): decouple redux props from dashboard components (apache#16421)
  perf(dashboard): reduce number of rerenders of Charts (apache#16444)
opus-42 pushed a commit to opus-42/incubator-superset that referenced this pull request Nov 14, 2021
* perf(dashboard): reduce number of rerenders of Charts

* Remove static property

* Cleanup
QAlexBall pushed a commit to QAlexBall/superset that referenced this pull request Dec 28, 2021
* perf(dashboard): reduce number of rerenders of Charts

* Remove static property

* Cleanup
@mistercrunch mistercrunch added 🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels 🚢 1.4.0 labels Mar 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels size/M test_priority:NA 🚢 1.4.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants