Fix performances on view loading (#14859)

## Context

We are experiencing bad performance on Twenty. One of the root cause
hypothesis is that computing `currentUser.currentWorkspace.views` is CPU
consuming. Without views, the GetCurrentUser response is ~1000 lines.
With its ~10000 lines.

As graphql is going through all fields recursively this can be quite
heavy on CPU. We had a similar issues on ObjectMetadataItems 2 years ago
and came with storing the response in redis.

Note: I thought there was also a cache in RAM but this is not the case,
so to invalidate the cache we can just empty redis.

## How

- Extract getting all views from GetCurrentUser and update frontend to
perform both queries
- Add views to cached graphql operations
- invalidate the cache manually on view or related core entities update
/ create / delete / destroy

## Tests

I have tested a lot on v1
This commit is contained in:
Charles Bochet
2025-10-02 22:53:03 +02:00
committed by GitHub
parent 87256e82f5
commit 5dcd0607b3
26 changed files with 385 additions and 151 deletions
@@ -22,6 +22,7 @@ import { AppPath, type ObjectPermissions } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type WorkspaceMember,
useFindAllCoreViewsQuery,
useGetCurrentUserQuery,
} from '~/generated-metadata/graphql';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
@@ -29,7 +30,7 @@ import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { isMatchingLocation } from '~/utils/isMatchingLocation';
export const UserProviderEffect = () => {
export const UserAndViewsProviderEffect = () => {
const location = useLocation();
const [isCurrentUserLoaded, setIsCurrentUserLoaded] = useRecoilState(
@@ -82,53 +83,57 @@ export const UserProviderEffect = () => {
currentWorkspaceDeletedMembersState,
);
const { data: queryData, loading: queryLoading } = useGetCurrentUserQuery({
skip:
!isLoggedIn ||
isCurrentUserLoaded ||
isMatchingLocation(location, AppPath.Verify) ||
isMatchingLocation(location, AppPath.VerifyEmail),
const shouldSkip =
!isLoggedIn ||
isCurrentUserLoaded ||
isMatchingLocation(location, AppPath.Verify) ||
isMatchingLocation(location, AppPath.VerifyEmail);
const { data: userQueryData, loading: userQueryLoading } =
useGetCurrentUserQuery({
skip: shouldSkip,
});
const { data: queryDataCoreViews } = useFindAllCoreViewsQuery({
skip: shouldSkip,
});
useEffect(() => {
if (!queryLoading) {
if (!userQueryLoading) {
setIsCurrentUserLoaded(true);
}
if (!isDefined(queryData?.currentUser)) return;
if (!isDefined(userQueryData?.currentUser)) return;
setCurrentUser(queryData.currentUser);
setCurrentUser(userQueryData.currentUser);
if (isDefined(queryData.currentUser.currentWorkspace)) {
if (isDefined(userQueryData.currentUser.currentWorkspace)) {
setCurrentWorkspace({
...queryData.currentUser.currentWorkspace,
defaultRole: queryData.currentUser.currentWorkspace.defaultRole ?? null,
...userQueryData.currentUser.currentWorkspace,
defaultRole:
userQueryData.currentUser.currentWorkspace.defaultRole ?? null,
defaultAgent:
queryData.currentUser.currentWorkspace.defaultAgent ?? null,
userQueryData.currentUser.currentWorkspace.defaultAgent ?? null,
});
}
if (isDefined(queryData.currentUser.currentUserWorkspace)) {
if (isDefined(userQueryData.currentUser.currentUserWorkspace)) {
setCurrentUserWorkspace({
...queryData.currentUser.currentUserWorkspace,
...userQueryData.currentUser.currentUserWorkspace,
objectsPermissions:
(queryData.currentUser.currentUserWorkspace
(userQueryData.currentUser.currentUserWorkspace
.objectsPermissions as Array<
ObjectPermissions & { objectMetadataId: string }
>) ?? [],
});
}
if (isDefined(queryData.currentUser?.currentWorkspace?.views)) {
setCoreViews(queryData.currentUser.currentWorkspace.views);
}
const {
workspaceMember,
workspaceMembers,
deletedWorkspaceMembers,
availableWorkspaces,
} = queryData.currentUser;
} = userQueryData.currentUser;
const affectDefaultValuesOnEmptyWorkspaceMemberFields = (
workspaceMember: WorkspaceMember,
@@ -168,8 +173,8 @@ export const UserProviderEffect = () => {
setAvailableWorkspaces(availableWorkspaces);
}
}, [
queryLoading,
queryData?.currentUser,
userQueryLoading,
userQueryData?.currentUser,
setCurrentUser,
setCurrentUserWorkspace,
setCurrentWorkspaceMembers,
@@ -183,5 +188,11 @@ export const UserProviderEffect = () => {
setCoreViews,
]);
useEffect(() => {
if (!isDefined(queryDataCoreViews?.getCoreViews)) return;
setCoreViews(queryDataCoreViews.getCoreViews);
}, [queryDataCoreViews?.getCoreViews, setCoreViews]);
return null;
};
@@ -4,14 +4,13 @@ import {
} from '@/auth/graphql/fragments/authFragments';
import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment';
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
import { BILLING_SUBSCRIPTION_FRAGMENT } from '@/users/graphql/fragments/billingSubscriptionsFragment';
import { CURRENT_BILLING_SUBSCRIPTION_FRAGMENT } from '@/users/graphql/fragments/currentBillingSubscriptionFragement';
import { WORKSPACE_URLS_FRAGMENT } from '@/users/graphql/fragments/workspaceUrlsFragment';
import { VIEW_FRAGMENT } from '@/views/graphql/fragments/viewFragment';
import { DELETED_WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/deletedWorkspaceMemberQueryFragment';
import { PARTIAL_WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/partialWorkspaceMemberQueryFragment';
import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment';
import { gql } from '@apollo/client';
import { CURRENT_BILLING_SUBSCRIPTION_FRAGMENT } from '@/users/graphql/fragments/currentBillingSubscriptionFragement';
import { BILLING_SUBSCRIPTION_FRAGMENT } from '@/users/graphql/fragments/billingSubscriptionsFragment';
export const USER_QUERY_FRAGMENT = gql`
fragment UserQueryFragment on User {
@@ -80,9 +79,6 @@ export const USER_QUERY_FRAGMENT = gql`
id
}
isTwoFactorAuthenticationEnforced
views {
...ViewFragment
}
}
availableWorkspaces {
...AvailableWorkspacesFragment
@@ -96,7 +92,6 @@ export const USER_QUERY_FRAGMENT = gql`
${OBJECT_PERMISSION_FRAGMENT}
${WORKSPACE_URLS_FRAGMENT}
${ROLE_FRAGMENT}
${VIEW_FRAGMENT}
${AVAILABLE_WORKSPACES_FOR_AUTH_FRAGMENT}
${AVAILABLE_WORKSPACE_FOR_AUTH_FRAGMENT}
${CURRENT_BILLING_SUBSCRIPTION_FRAGMENT}
@@ -14,7 +14,10 @@ import { SOURCE_LOCALE, type APP_LOCALES } from 'twenty-shared/translations';
import { type ObjectPermissions } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type ColorScheme } from 'twenty-ui/input';
import { useGetCurrentUserLazyQuery } from '~/generated-metadata/graphql';
import {
useFindAllCoreViewsLazyQuery,
useGetCurrentUserLazyQuery,
} from '~/generated-metadata/graphql';
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
@@ -37,12 +40,17 @@ export const useLoadCurrentUser = () => {
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
const [getCurrentUser] = useGetCurrentUserLazyQuery();
const [findAllCoreViews] = useFindAllCoreViewsLazyQuery();
const loadCurrentUser = useCallback(async () => {
const currentUserResult = await getCurrentUser({
fetchPolicy: 'network-only',
});
const coreViewsResult = await findAllCoreViews({
fetchPolicy: 'network-only',
});
if (isDefined(currentUserResult.error)) {
throw new Error(currentUserResult.error.message);
}
@@ -102,8 +110,8 @@ export const useLoadCurrentUser = () => {
});
}
if (isDefined(workspace) && isDefined(workspace.views)) {
setCoreViews(workspace.views);
if (isDefined(coreViewsResult.data?.getCoreViews)) {
setCoreViews(coreViewsResult.data.getCoreViews);
}
return {
@@ -113,16 +121,17 @@ export const useLoadCurrentUser = () => {
};
}, [
getCurrentUser,
isOnAWorkspace,
setAvailableWorkspaces,
setCoreViews,
findAllCoreViews,
setCurrentUser,
setCurrentUserWorkspace,
setCurrentWorkspace,
setCurrentWorkspaceMember,
isOnAWorkspace,
setCurrentWorkspaceMembers,
setAvailableWorkspaces,
setCurrentUserWorkspace,
setCurrentWorkspaceMember,
initializeFormatPreferences,
setLastAuthenticateWorkspaceDomain,
setCoreViews,
]);
return {