-
Notifications
You must be signed in to change notification settings - Fork 14.2k
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
Conversation
There was a problem hiding this 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
(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, | ||
}) |
There was a problem hiding this comment.
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];
There was a problem hiding this comment.
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,
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
static whyDidYouRender = true; | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 🙂
@@ -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', |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 Report
@@ 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
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
* 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)
* perf(dashboard): reduce number of rerenders of Charts * Remove static property * Cleanup
* perf(dashboard): reduce number of rerenders of Charts * Remove static property * Cleanup
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
CC @junlincc @jinghua-qa