-
Notifications
You must be signed in to change notification settings - Fork 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: App start improvement [INS-3957] #7492
Conversation
have some |
} | ||
}; | ||
|
||
async function migrateProjectsUnderOrganization(personalOrganizationId: string, sessionId: string) { |
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.
its really dangerous to mess with migrations, I'd scope this out if you can without introducing more risk
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.
The origin migration logic is in /organizationId
index route loader, after the initial entry change, the user will not match this index route, so we need to put the migration in action.If we could add some test to reduce the risk?
can you provide more evidence that this approach is addressing the lionshare of performance impact? from what i can see this PR is doing at least two things. in a perf PR please only focus on a single measurable improvement and give a rationale about why this perf change was chosen in context of other perf impacts which were also investigated. eg.
|
Do you mean the two things is modity initial entry and add async task hooks? This is because if we only modify the initial entry, the logic in the |
for (const task of asyncTaskList) { | ||
if (task === AsyncTask.SyncOrganization) { | ||
invariant(sessionId, 'sessionId is required'); | ||
invariant(accountId, 'accountId is required'); | ||
taskPromiseList.push(syncOrganization(sessionId, accountId)); | ||
} | ||
|
||
if (task === AsyncTask.MigrateProjects) { | ||
invariant(personalOrganizationId, 'personalOrganizationId is required'); | ||
invariant(sessionId, 'sessionId is required'); | ||
taskPromiseList.push(migrateProjectsUnderOrganization(personalOrganizationId, sessionId)); | ||
} | ||
|
||
if (task === AsyncTask.SyncProjects) { | ||
invariant(organizationId, 'organizationId is required'); | ||
taskPromiseList.push(syncProjects(organizationId)); | ||
} | ||
} | ||
|
||
await Promise.all(taskPromiseList); |
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.
Using signals with actions is a new concept. I still don't understand the benefit of this over creating 3 separate actions. This kind of out of the box thinking is cool but really hard to reason about. I don't want it in our happy path if I can help it.
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.
One reason is 3 seperate action will case loader rerun 3 times.So I merge into only action
useEffect(() => { | ||
// sync organizations | ||
if (asyncTaskList.includes(AsyncTask.SyncOrganization)) { | ||
console.log('running sync organization task'); | ||
const submit = syncOrgsFetcher.submit; | ||
submit({}, { | ||
action: '/organization/sync', | ||
method: 'POST', | ||
}); | ||
} | ||
}, [asyncTaskList, syncOrgsFetcher.submit]); | ||
|
||
useEffect(() => { | ||
// sync projects under organization | ||
if (asyncTaskList.includes(AsyncTask.SyncProjects)) { | ||
console.log('running sync projects task'); | ||
const submit = syncProjectsFetcher.submit; | ||
submit({}, { | ||
action: `/organization/${organizationId}/sync-projects`, | ||
method: 'POST', | ||
}); | ||
} | ||
}, [asyncTaskList, organizationId, syncProjectsFetcher.submit]); | ||
|
||
useEffect(() => { | ||
// migrate projects under personal organization | ||
if (asyncTaskList.includes(AsyncTask.MigrateProjects)) { | ||
console.log('running migrate projects task'); | ||
const submit = migrateProjectsFetcher.submit; | ||
submit({}, { | ||
action: '/organization/migrate-projects', | ||
method: 'POST', | ||
}); | ||
} | ||
}, [asyncTaskList, migrateProjectsFetcher.submit]); | ||
|
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.
@gatzjames @jackkav if we fetch the existing action instead of combine to a new action, there are 3 action submit, and the loader will re-run 3 times.This will result in repeated requests in the project loader several times.I'd suggest merging into a new action to reduce the number of requests.
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.
using defer also can not cancel the fetch request in loader
39cc512
to
f364d71
Compare
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.
Some tests on my machine:
Latest beta packaged version:
- 956 ms loading screen until we see dashboard
- 1633 ms activity until fully loaded (affected by network loading of collections and status)
This PR (using packaged app, from npm run app-package
):
- 428 ms loading screen until we see dashboard
- 1500 ms activity until fully loaded (affected by network loading of collections and status)
Loading time difference is noticeable.
Switching between Organizations also is fast, but there's a split second when it still is not showing all the unsynced collections right after going to another project/organization.
@filfreire Thank you very much for your test.Not showing all the unsynced files right away is as expected, because we return a deferred data in react-router loader and wait the promise in component render phase. |
// ideas: | ||
// 1. useEffect with controlled execution | ||
// 2. use fetcher history state to avoid running the same task multiple times | ||
// 3. create an event after logged in user is detected in initial entry and listen.once in this component |
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.
// ideas: | |
// 1. useEffect with controlled execution | |
// 2. use fetcher history state to avoid running the same task multiple times | |
// 3. create an event after logged in user is detected in initial entry and listen.once in this component | |
// ideas: | |
// 1. useEffect with controlled execution | |
// 2. use fetcher history state to avoid running the same task multiple times | |
// 3. create an event after logged in user is detected in initial entry and listen.once in this component | |
// 4. rather than use an action hack to avoid fetching in the loader, we can now use a deferred promise in the loader since they are now non-blocking. |
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.
Try this:
// 4. rather than use an action hack to avoid fetching in the loader, we can now use a deferred promise in the loader since they are now non-blocking.
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 think this idea is not feasible for two reasons
- Because in current solution, we find best route in InitialEntry, and will skip the index route loader, so we are not able to keep fetching in the index route loader.
- If we don't find best route and keep using '/organization' as the initial entry, the logic is executed to index route loader. But the index route loader just use for refresh cache data and return a redirect, we can't return a deferred promise because no component connected to the loader .
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.
The async loading strategy of workspaces seems straightforward with low maintaining effort and which can work with organizations, projects loading in parallel. So I propose to move forward with this part at first.
export const findPersonalOrganization = (organizations: Organization[], accountId: string) => { | ||
return organizations.filter(isPersonalOrganization) | ||
.find(organization => | ||
isOwnerOfOrganization({ |
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.
nit: Probably need double check if It is good enough to say that it is a personal org.
// ideas: | ||
// 1. useEffect with controlled execution | ||
// 2. use fetcher history state to avoid running the same task multiple times | ||
// 3. create an event after logged in user is detected in initial entry and listen.once in this component |
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.
The async loading strategy of workspaces seems straightforward with low maintaining effort and which can work with organizations, projects loading in parallel. So I propose to move forward with this part at first.
12aa93d
to
55b5f6c
Compare
55b5f6c
to
4cc27bc
Compare
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 investigation on the pros/cons of different approaches to improve data fetching.
Let's clean up the code and get this merged!
SyncOrganization, | ||
MigrateProjects, | ||
SyncProjects, | ||
} |
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.
consider writing a unit test together for this? to help search for simplifications
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.
Good advice. Added to my todo list
close INS-4026 INS-3957
objective: Reduce the App start duration
Changes
Loading strategy
findBestRoute
function to try to return the path with orgId and projectId in:return deferred value in project loader
Test
Other approaches we tried:
useEffect
logicrouter.subscribe
executes many times in one navigation, we need use a flag to control execution times