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:
+1
-1
@@ -107,7 +107,7 @@ export class CacheStorageService {
|
||||
|
||||
do {
|
||||
const result = await redisClient.scan(cursor, {
|
||||
MATCH: scanPattern,
|
||||
MATCH: `${this.namespace}:${scanPattern}`,
|
||||
COUNT: 100,
|
||||
});
|
||||
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ describe('ViewFieldService', () => {
|
||||
provide: ViewService,
|
||||
useValue: {
|
||||
findByIdWithRelatedObjectMetadata: jest.fn(),
|
||||
flushGraphQLCache: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
+7
@@ -13,6 +13,7 @@ import {
|
||||
generateViewFilterGroupUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-filter-group.exception';
|
||||
import { ViewFilterGroupService } from 'src/engine/core-modules/view/services/view-filter-group.service';
|
||||
import { ViewService } from 'src/engine/core-modules/view/services/view.service';
|
||||
|
||||
describe('ViewFilterGroupService', () => {
|
||||
let viewFilterGroupService: ViewFilterGroupService;
|
||||
@@ -44,6 +45,12 @@ describe('ViewFilterGroupService', () => {
|
||||
delete: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ViewService,
|
||||
useValue: {
|
||||
flushGraphQLCache: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
+7
@@ -13,6 +13,7 @@ import {
|
||||
generateViewFilterUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-filter.exception';
|
||||
import { ViewFilterService } from 'src/engine/core-modules/view/services/view-filter.service';
|
||||
import { ViewService } from 'src/engine/core-modules/view/services/view.service';
|
||||
|
||||
describe('ViewFilterService', () => {
|
||||
let viewFilterService: ViewFilterService;
|
||||
@@ -35,6 +36,12 @@ describe('ViewFilterService', () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ViewFilterService,
|
||||
{
|
||||
provide: ViewService,
|
||||
useValue: {
|
||||
flushGraphQLCache: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(ViewFilterEntity),
|
||||
useValue: {
|
||||
|
||||
+7
@@ -12,6 +12,7 @@ import {
|
||||
generateViewGroupUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-group.exception';
|
||||
import { ViewGroupService } from 'src/engine/core-modules/view/services/view-group.service';
|
||||
import { ViewService } from 'src/engine/core-modules/view/services/view.service';
|
||||
|
||||
describe('ViewGroupService', () => {
|
||||
let viewGroupService: ViewGroupService;
|
||||
@@ -45,6 +46,12 @@ describe('ViewGroupService', () => {
|
||||
delete: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ViewService,
|
||||
useValue: {
|
||||
flushGraphQLCache: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
+7
@@ -13,6 +13,7 @@ import {
|
||||
generateViewSortUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-sort.exception';
|
||||
import { ViewSortService } from 'src/engine/core-modules/view/services/view-sort.service';
|
||||
import { ViewService } from 'src/engine/core-modules/view/services/view.service';
|
||||
|
||||
describe('ViewSortService', () => {
|
||||
let viewSortService: ViewSortService;
|
||||
@@ -44,6 +45,12 @@ describe('ViewSortService', () => {
|
||||
delete: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ViewService,
|
||||
useValue: {
|
||||
flushGraphQLCache: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
+7
@@ -15,6 +15,7 @@ import {
|
||||
generateViewUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/core-modules/view/exceptions/view.exception';
|
||||
import { ViewService } from 'src/engine/core-modules/view/services/view.service';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
describe('ViewService', () => {
|
||||
let viewService: ViewService;
|
||||
@@ -56,6 +57,12 @@ describe('ViewService', () => {
|
||||
delete: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceCacheStorageService,
|
||||
useValue: {
|
||||
flushGraphQLOperation: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: I18nService,
|
||||
useValue: {
|
||||
|
||||
@@ -95,6 +95,9 @@ export class ViewFieldService {
|
||||
);
|
||||
|
||||
const savedViewField = await this.viewFieldRepository.save(viewField);
|
||||
|
||||
await this.viewService.flushGraphQLCache(viewFieldData.workspaceId);
|
||||
|
||||
const createdViewField = await this.findById(
|
||||
savedViewField.id,
|
||||
viewFieldData.workspaceId,
|
||||
@@ -223,6 +226,8 @@ export class ViewFieldService {
|
||||
...updateData,
|
||||
});
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return { ...existingViewField, ...updatedViewField };
|
||||
}
|
||||
|
||||
@@ -241,6 +246,8 @@ export class ViewFieldService {
|
||||
|
||||
await this.viewFieldRepository.softDelete(id);
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return viewField;
|
||||
}
|
||||
|
||||
@@ -259,6 +266,8 @@ export class ViewFieldService {
|
||||
|
||||
await this.viewFieldRepository.delete(id);
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return viewField;
|
||||
}
|
||||
|
||||
|
||||
+14
-1
@@ -12,12 +12,14 @@ import {
|
||||
generateViewFilterGroupExceptionMessage,
|
||||
generateViewFilterGroupUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-filter-group.exception';
|
||||
import { ViewService } from 'src/engine/core-modules/view/services/view.service';
|
||||
|
||||
@Injectable()
|
||||
export class ViewFilterGroupService {
|
||||
constructor(
|
||||
@InjectRepository(ViewFilterGroupEntity)
|
||||
private readonly viewFilterGroupRepository: Repository<ViewFilterGroupEntity>,
|
||||
private readonly viewService: ViewService,
|
||||
) {}
|
||||
|
||||
async findByWorkspaceId(
|
||||
@@ -118,7 +120,14 @@ export class ViewFilterGroupService {
|
||||
const viewFilterGroup =
|
||||
this.viewFilterGroupRepository.create(viewFilterGroupData);
|
||||
|
||||
return this.viewFilterGroupRepository.save(viewFilterGroup);
|
||||
await this.viewService.flushGraphQLCache(viewFilterGroupData.workspaceId);
|
||||
|
||||
const savedViewFilterGroup =
|
||||
await this.viewFilterGroupRepository.save(viewFilterGroup);
|
||||
|
||||
await this.viewService.flushGraphQLCache(viewFilterGroupData.workspaceId);
|
||||
|
||||
return savedViewFilterGroup;
|
||||
}
|
||||
|
||||
async update(
|
||||
@@ -143,6 +152,8 @@ export class ViewFilterGroupService {
|
||||
...updateData,
|
||||
});
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return { ...existingViewFilterGroup, ...updatedViewFilterGroup };
|
||||
}
|
||||
|
||||
@@ -182,6 +193,8 @@ export class ViewFilterGroupService {
|
||||
|
||||
await this.viewFilterGroupRepository.delete(id);
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -12,12 +12,14 @@ import {
|
||||
generateViewFilterExceptionMessage,
|
||||
generateViewFilterUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-filter.exception';
|
||||
import { ViewService } from 'src/engine/core-modules/view/services/view.service';
|
||||
|
||||
@Injectable()
|
||||
export class ViewFilterService {
|
||||
constructor(
|
||||
@InjectRepository(ViewFilterEntity)
|
||||
private readonly viewFilterRepository: Repository<ViewFilterEntity>,
|
||||
private readonly viewService: ViewService,
|
||||
) {}
|
||||
|
||||
async findByWorkspaceId(workspaceId: string): Promise<ViewFilterEntity[]> {
|
||||
@@ -109,7 +111,13 @@ export class ViewFilterService {
|
||||
|
||||
const viewFilter = this.viewFilterRepository.create(viewFilterData);
|
||||
|
||||
return this.viewFilterRepository.save(viewFilter);
|
||||
await this.viewService.flushGraphQLCache(viewFilterData.workspaceId);
|
||||
|
||||
const savedViewFilter = await this.viewFilterRepository.save(viewFilter);
|
||||
|
||||
await this.viewService.flushGraphQLCache(viewFilterData.workspaceId);
|
||||
|
||||
return savedViewFilter;
|
||||
}
|
||||
|
||||
async update(
|
||||
@@ -134,6 +142,8 @@ export class ViewFilterService {
|
||||
...updateData,
|
||||
});
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return { ...existingViewFilter, ...updatedViewFilter };
|
||||
}
|
||||
|
||||
@@ -152,6 +162,8 @@ export class ViewFilterService {
|
||||
|
||||
await this.viewFilterRepository.softDelete(id);
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return viewFilter;
|
||||
}
|
||||
|
||||
@@ -170,6 +182,8 @@ export class ViewFilterService {
|
||||
|
||||
await this.viewFilterRepository.delete(id);
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,14 @@ import {
|
||||
generateViewGroupExceptionMessage,
|
||||
generateViewGroupUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-group.exception';
|
||||
import { ViewService } from 'src/engine/core-modules/view/services/view.service';
|
||||
|
||||
@Injectable()
|
||||
export class ViewGroupService {
|
||||
constructor(
|
||||
@InjectRepository(ViewGroupEntity)
|
||||
private readonly viewGroupRepository: Repository<ViewGroupEntity>,
|
||||
private readonly viewService: ViewService,
|
||||
) {}
|
||||
|
||||
async findByWorkspaceId(workspaceId: string): Promise<ViewGroupEntity[]> {
|
||||
@@ -109,7 +111,13 @@ export class ViewGroupService {
|
||||
|
||||
const viewGroup = this.viewGroupRepository.create(viewGroupData);
|
||||
|
||||
return this.viewGroupRepository.save(viewGroup);
|
||||
await this.viewService.flushGraphQLCache(viewGroupData.workspaceId);
|
||||
|
||||
const savedViewGroup = await this.viewGroupRepository.save(viewGroup);
|
||||
|
||||
await this.viewService.flushGraphQLCache(viewGroupData.workspaceId);
|
||||
|
||||
return savedViewGroup;
|
||||
}
|
||||
|
||||
async update(
|
||||
@@ -134,6 +142,8 @@ export class ViewGroupService {
|
||||
...updateData,
|
||||
});
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return { ...existingViewGroup, ...updatedViewGroup };
|
||||
}
|
||||
|
||||
@@ -152,6 +162,8 @@ export class ViewGroupService {
|
||||
|
||||
await this.viewGroupRepository.softDelete(id);
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return viewGroup;
|
||||
}
|
||||
|
||||
@@ -170,6 +182,8 @@ export class ViewGroupService {
|
||||
|
||||
await this.viewGroupRepository.delete(id);
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,14 @@ import {
|
||||
generateViewSortExceptionMessage,
|
||||
generateViewSortUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/core-modules/view/exceptions/view-sort.exception';
|
||||
import { ViewService } from 'src/engine/core-modules/view/services/view.service';
|
||||
|
||||
@Injectable()
|
||||
export class ViewSortService {
|
||||
constructor(
|
||||
@InjectRepository(ViewSortEntity)
|
||||
private readonly viewSortRepository: Repository<ViewSortEntity>,
|
||||
private readonly viewService: ViewService,
|
||||
) {}
|
||||
|
||||
async findByWorkspaceId(workspaceId: string): Promise<ViewSortEntity[]> {
|
||||
@@ -105,7 +107,11 @@ export class ViewSortService {
|
||||
|
||||
const viewSort = this.viewSortRepository.create(viewSortData);
|
||||
|
||||
return this.viewSortRepository.save(viewSort);
|
||||
const savedViewSort = await this.viewSortRepository.save(viewSort);
|
||||
|
||||
await this.viewService.flushGraphQLCache(viewSortData.workspaceId);
|
||||
|
||||
return savedViewSort;
|
||||
}
|
||||
|
||||
async update(
|
||||
@@ -130,6 +136,8 @@ export class ViewSortService {
|
||||
...updateData,
|
||||
});
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return { ...existingViewSort, ...updatedViewSort };
|
||||
}
|
||||
|
||||
@@ -148,6 +156,8 @@ export class ViewSortService {
|
||||
|
||||
await this.viewSortRepository.softDelete(id);
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return viewSort;
|
||||
}
|
||||
|
||||
@@ -166,6 +176,8 @@ export class ViewSortService {
|
||||
|
||||
await this.viewSortRepository.delete(id);
|
||||
|
||||
await this.viewService.flushGraphQLCache(workspaceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
generateViewExceptionMessage,
|
||||
generateViewUserFriendlyExceptionMessage,
|
||||
} from 'src/engine/core-modules/view/exceptions/view.exception';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
@Injectable()
|
||||
export class ViewService {
|
||||
@@ -22,6 +23,7 @@ export class ViewService {
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
private readonly i18nService: I18nService,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
) {}
|
||||
|
||||
async findByWorkspaceId(workspaceId: string): Promise<ViewEntity[]> {
|
||||
@@ -134,7 +136,11 @@ export class ViewService {
|
||||
isCustom: true,
|
||||
});
|
||||
|
||||
return this.viewRepository.save(view);
|
||||
const savedView = await this.viewRepository.save(view);
|
||||
|
||||
await this.flushGraphQLCache(viewData.workspaceId);
|
||||
|
||||
return savedView;
|
||||
}
|
||||
|
||||
async update(
|
||||
@@ -159,6 +165,8 @@ export class ViewService {
|
||||
...updateData,
|
||||
});
|
||||
|
||||
await this.flushGraphQLCache(workspaceId);
|
||||
|
||||
return { ...existingView, ...updatedView };
|
||||
}
|
||||
|
||||
@@ -177,6 +185,8 @@ export class ViewService {
|
||||
|
||||
await this.viewRepository.softDelete(id);
|
||||
|
||||
await this.flushGraphQLCache(workspaceId);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -194,6 +204,7 @@ export class ViewService {
|
||||
}
|
||||
|
||||
await this.viewRepository.delete(id);
|
||||
await this.flushGraphQLCache(workspaceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -235,4 +246,11 @@ export class ViewService {
|
||||
|
||||
return viewName;
|
||||
}
|
||||
|
||||
async flushGraphQLCache(workspaceId: string): Promise<void> {
|
||||
await this.workspaceCacheStorageService.flushGraphQLOperation({
|
||||
operationName: 'FindAllCoreViews',
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user