-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
[NoQA] [Audit][Implementation] - Increase max cache keys count and use waitForCollectionCallback #38464
[NoQA] [Audit][Implementation] - Increase max cache keys count and use waitForCollectionCallback #38464
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 👍
Question @hurali97, what's stopping us from having single connection to ONYXKEYS.COLLECTION.REPORT
, which would result in single instance of reports
object that then later could be shared across the app?
This is more complicated then it looks like. A straight forward solution is to implement something like this. This will keep only two connections to Onyx, one for However, this will get more complicated over the time as we'll have to do the same for each of the collection, for eg, I also evaluated whether we can update some parts of Onyx to have only one connection per key but that's easier said than done. The reason is that we have callbacks with each connection and even if we manage to keep only one connection per key, we will have to store the callbacks like below: {
key: 'report',
callbacks: [
{
callback: () => {},
waitForConnectionCallback: false,
...otherProps,
},
{
callback: () => {},
waitForConnectionCallback: true,
...otherProps,
}
]
} Even with above structure, we will have to loop over the callbacks to fire them whenever Now, given all of the above I think it's better to avoid the individual loop within the Onyx for the larger keys. Which means reduce the chances of iterating over the keys and getting them from cache, then from storage by using |
What I had in mind is starting small. The passive usages of App/src/libs/OptionsListUtils.ts Lines 251 to 259 in bbf4bee
Could be replaced with single instance which we could import in many places. This would reduce code complexity as all passive connection would be explicitly handled in one place, e.g. For interactive usages (where live callback is needed), then we could implement it with Observer pattern like so (presented in simple form for brevity): const store = {};
const listeners = new Set();
Onyx.connect({
key: 'some_key',
callback: (data) => {
store = data;
listeners.forEach(l => l(store));
},
});
const addListener = (l) => {
listeners.add(l);
return () => listeners.remove(l)
}
// Rest of the module
export { addListener, removeListener, data } This is more complex topic but the main take-away is that, as you pointed out in your analysis, we have many redundant connections scattered across the app which builds up both mental and perf overheads. Question is, the |
@kacper-mikolajczak I see. I implemented something similar when working in analysis phase here. What you’ve proposed is more or less the same thing. The only issue is the complexity that this solution brings. Just see that we will have to do this for each key, if we want to use it. Also, in my profiling with the changes in the PR, I realise that this single connection approach doesn’t bring much of an improvement. So I guess with these improvements, this doesn’t make sense to introduce more complexity just for the sake of single connection because it’s not hurting the performance, courtesy of the changes added in the PR now :thinking_face: Previously, what you’ve mentioned was my go to approach but I guess we have something better now, so I will keep it until in the future we encounter it again then we can continue the discussion. |
Great, thanks for the explanation! We can definitely continue the discussion :) GJ on the PR 🚀 |
@shubham1206agra any updates on the review? 🙂 |
@hurali97 I thought you were going to implement https://github.com/Expensify/App/pull/38464/files#r1531973454. Let me ping @roryabraham here to confirm if we can proceed with this solution. |
@shubham1206agra Yeah I am willing to implement this just waiting for a green light from the reviewers 👍 |
@roryabraham Friendly bump here. |
@grgia Can you comment on #38464 (comment) instead if possible? |
@hurali97 Shouldn't the same thing applies to |
@shubham1206agra I think no, because the Edit: Yeah sure thing 👍 I didn't see that |
@hurali97 You may have been misguided by the type. You may want to recheck the usage of |
@shubham1206agra Yeah I was because I checked before merging the main. Anyways, things are resolved now 👍 |
@hurali97 No, you are misguided now. The correct type of input is Participant only :). |
@shubham1206agra Oh.... so you mean I should rename |
Yes, and update the function accordingly. |
I am understanding that we can rename the parameter Are you referring to add function getPolicyExpenseReportOption(participant: Participant): ReportUtils.OptionData {
const expenseReport = ReportUtils.isPolicyExpenseChat(participant) ? ReportUtils.getReport(participant.reportID) : null;
.... |
@hurali97 Yes |
Reviewer Checklist
Screenshots/VideosAndroid: NativeScreen.Recording.2024-03-27.at.9.26.31.PM.movAndroid: mWeb ChromeScreen.Recording.2024-03-27.at.9.06.25.PM.moviOS: NativeScreen.Recording.2024-03-27.at.9.14.18.PM.moviOS: mWeb SafariScreen.Recording.2024-03-27.at.9.00.42.PM.movMacOS: Chrome / SafariScreen.Recording.2024-03-27.at.8.11.53.PM.movMacOS: DesktopScreen.Recording.2024-03-27.at.9.09.48.PM.mov |
Co-authored-by: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com>
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.
Thank you!
Going to go ahead since C+ reviewed and Georgia approved before too |
@mountiny looks like this was merged without a test passing. Please add a note explaining why this was done and remove the |
Melvin the gaslighter the tests were passing |
🚀 Deployed to staging by https://github.com/mountiny in version: 1.4.58-0 🚀
|
🚀 Deployed to production by https://github.com/Beamanator in version: 1.4.58-8 🚀
|
@@ -42,11 +38,11 @@ function getTaskReportActionMessage(action: OnyxEntry<ReportAction>): Pick<Messa | |||
} | |||
|
|||
function getTaskTitle(taskReportID: string, fallbackTitle = ''): string { | |||
const taskReport = allReports[taskReportID] ?? {}; | |||
const taskReport = allReports?.[taskReportID] ?? {}; |
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.
This should've been updated to const taskReport = allReports?.[
${ONYXKEYS.COLLECTION.REPORT}${taskReportID}] ?? {};
after updating how we key the reports. it caused a regression #40121 More information: #40121 (comment)
Details
This PR is part of the implementation phase of App Startup Audit by Callstack.
Originally the proposal is here but after thinking it thoroughly, we decided to follow a better solution.
We have lots of connections to
Reports
data in Onyx in our utils files and some of them useswaitForCollectionCallback: true
and the rest of them doesn't. Which adds up to the app startup time. The reason is that when we don't setwaitForCollectionCallback
totrue
inOnyx.js
the following code is executed:This loop will be executed for all the keys and will individually inform the subscriber of updates. The
get
function first checks whether the value for a key is present in cache and if it doesn't it then gets it from the storage.We have 6 connections to
Reports
without thewaitForCollectionCallback
so this loop will be executed 6 times. Bearing in mind thatReports
can be a huge collection, for eg on a heavy account it can get to 15k reports.The solution is to refactor our current
Onyx.connect
calls toReports
to includewaitForCollectionCallback
and adjust the code accordingly. There's one place where we won't be changing it and that's inReportUtils.ts
where we have to callhandleReportChanged
on each report.One more thing to notice is that currently our cache limit is 10k which is easily hit by a heavy account of 15k reports, so this cache limit isn't helping the app in case of a heavy account. We can increase it to 20k so that it can hold more reports.
Below are Hermes traces that have been gathered on:
Baseline
With waitForCollectionCallback
`With increased cache and waitForCollectionCallback
To summarise, When launching the app on Android:
waitForCollectionCallback
🟢waitForCollectionCallback
🟢 🟢Fixed Issues
$ #38586
PROPOSAL:
Tests
Offline tests
QA Steps
PR Author Checklist
### Fixed Issues
section aboveTests
sectionOffline steps
sectionQA steps
sectiontoggleReport
and notonIconClick
)myBool && <MyComponent />
.src/languages/*
files and using the translation methodSTYLE.md
) were followedAvatar
, I verified the components usingAvatar
are working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
)Avatar
is modified, I verified thatAvatar
is working as expected in all cases)Design
label and/or tagged@Expensify/design
so the design team can review the changes.ScrollView
component to make it scrollable when more elements are added to the page.main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTest
steps.Screenshots/Videos
Android: Native
android.mov
Android: mWeb Chrome
android-web.mov
iOS: Native
ios.mov
iOS: mWeb Safari
ios-web.mov
MacOS: Chrome / Safari
web.mov
MacOS: Desktop
desktop.mov