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: App start improvement [INS-3957] #7492

Merged
merged 12 commits into from
Jul 8, 2024
Merged

Conversation

CurryYangxx
Copy link
Member

@CurryYangxx CurryYangxx commented Jun 5, 2024

close INS-4026 INS-3957
objective: Reduce the App start duration

Changes

Loading strategy

  • use the findBestRoute function to try to return the path with orgId and projectId in:
    • initialEntry
    • organizationId indexLoader
  • sync task: submit action in useEffect instead of syncing in index route loader (organization index & organizationId index)

return deferred value in project loader

This solution is beneficial for both app start and projects change.

  • return deferred data in project loader and wait promise in component

Test

  • user has no cache data
  • user has cache data

Other approaches we tried:

  • Use the router navigation events:
    • Added more complexity on the routing side and relies on private router APIs
    • Simplifies the useEffect logic
    • Adds another way to fetch and store data and caching still needs another solution
    • router.subscribe executes many times in one navigation, we need use a flag to control execution times
    • There's uncertainty about the execution timing (e.g. has the component rendered before we fetch the data or after?)
    • Having a status of which data has been fetched is still hard to achieve
  • Use a cache and move the fetching to loaders using defer as a mechanism to update
    • Better UX and DX
    • When using defer we have a mechanism to show stale data on the UI and update when fresh data come in.
    • The problem that remains is cache invalidation and fetching too many times since fetching now happens on the hot path of the app

@CurryYangxx CurryYangxx marked this pull request as draft June 5, 2024 09:11
@CurryYangxx CurryYangxx changed the title wip: gen whole route in initialEntry wip: gen whole route in initialEntry [INS-3957] Jun 5, 2024
@CurryYangxx CurryYangxx changed the title wip: gen whole route in initialEntry [INS-3957] perf: gen whole route in initialEntry [INS-3957] Jun 11, 2024
@CurryYangxx
Copy link
Member Author

have some console.log for testing now, will be deleted before merge

@CurryYangxx CurryYangxx marked this pull request as ready for review June 11, 2024 03:39
@CurryYangxx CurryYangxx requested a review from a team June 11, 2024 03:40
}
};

async function migrateProjectsUnderOrganization(personalOrganizationId: string, sessionId: string) {
Copy link
Contributor

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

Copy link
Member Author

@CurryYangxx CurryYangxx Jun 12, 2024

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?

@jackkav
Copy link
Contributor

jackkav commented Jun 12, 2024

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.

thing a 2%
thing b 10%
thing c 88%
i chose c because its was worth optimizing.

@CurryYangxx CurryYangxx changed the title perf: gen whole route in initialEntry [INS-3957] perf: App start improvement - gen whole route in initialEntry [INS-3957] Jun 12, 2024
@CurryYangxx
Copy link
Member Author

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.

thing a 2%
thing b 10%
thing c 88%
i chose c because its was worth optimizing.

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 organization and organizationId index route loader will not run, so I put the two changes together to make logic of the app be complete.

Comment on lines 190 to 194
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);
Copy link
Contributor

@jackkav jackkav Jun 13, 2024

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.

Copy link
Member Author

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

@CurryYangxx CurryYangxx changed the title perf: App start improvement - gen whole route in initialEntry [INS-3957] perf: App start improvement [INS-3957] Jun 19, 2024
@CurryYangxx CurryYangxx changed the base branch from develop to release-performance-improvement June 21, 2024 02:59
Comment on lines 441 to 469
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]);

Copy link
Member Author

@CurryYangxx CurryYangxx Jun 21, 2024

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.
image

Copy link
Member Author

@CurryYangxx CurryYangxx Jun 24, 2024

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

@CurryYangxx CurryYangxx changed the base branch from release-performance-improvement to develop June 24, 2024 03:29
@CurryYangxx CurryYangxx requested a review from jackkav June 24, 2024 05:49
Copy link
Member

@filfreire filfreire left a 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.

@CurryYangxx
Copy link
Member Author

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.

Comment on lines 428 to 451
// 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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// 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.

Copy link
Contributor

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.

Copy link
Member Author

@CurryYangxx CurryYangxx Jun 26, 2024

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

  1. 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.
  2. 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 .

Copy link
Contributor

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({
Copy link
Contributor

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.

packages/insomnia/src/utils/router.ts Outdated Show resolved Hide resolved
Comment on lines 428 to 451
// 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
Copy link
Contributor

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.

Copy link
Contributor

@gatzjames gatzjames left a 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!

packages/insomnia/src/ui/routes/organization.tsx Outdated Show resolved Hide resolved
packages/insomnia/src/ui/routes/project.tsx Outdated Show resolved Hide resolved
packages/insomnia/src/ui/routes/project.tsx Outdated Show resolved Hide resolved
packages/insomnia/src/ui/routes/project.tsx Outdated Show resolved Hide resolved
packages/insomnia/src/utils/router.ts Outdated Show resolved Hide resolved
packages/insomnia/src/utils/router.ts Outdated Show resolved Hide resolved
packages/insomnia/src/ui/routes/project.tsx Outdated Show resolved Hide resolved
packages/insomnia/src/ui/routes/project.tsx Outdated Show resolved Hide resolved
packages/insomnia/src/ui/routes/project.tsx Outdated Show resolved Hide resolved
SyncOrganization,
MigrateProjects,
SyncProjects,
}
Copy link
Contributor

@jackkav jackkav Jul 5, 2024

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

Copy link
Member Author

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

jackkav
jackkav previously approved these changes Jul 5, 2024
gatzjames
gatzjames previously approved these changes Jul 5, 2024
@CurryYangxx CurryYangxx dismissed stale reviews from gatzjames and jackkav via 76bdb31 July 8, 2024 06:17
ihexxa
ihexxa previously approved these changes Jul 8, 2024
@CurryYangxx CurryYangxx enabled auto-merge (squash) July 8, 2024 06:21
@CurryYangxx CurryYangxx merged commit deacf15 into develop Jul 8, 2024
7 checks passed
@CurryYangxx CurryYangxx deleted the perf/modify-initial-entry branch July 8, 2024 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants