5x Fix cache performance issues (#6616)

Calling `getObjectMetadata` from `WorkspaceCacheStorageService` in every
query was causing big performance issues. The `objectMetadataCollection`
is now part of the `WorkspaceInternalContext` so we only instance it
once in the `WorkspaceDatasourceFactory`.
Queries are now much faster, for instance for TimelineCalendar, it went
from ~450ms to 80ms.
This commit is contained in:
Raphaël Bosi
2024-08-13 17:54:55 +02:00
committed by GitHub
parent 65a961ff3e
commit 40bbee8d9f
6 changed files with 27 additions and 42 deletions

View File

@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common';
import { EntitySchemaRelationOptions } from 'typeorm'; import { EntitySchemaRelationOptions } from 'typeorm';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
import { determineRelationDetails } from 'src/engine/twenty-orm/utils/determine-relation-details.util'; import { determineRelationDetails } from 'src/engine/twenty-orm/utils/determine-relation-details.util';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
type EntitySchemaRelationMap = { type EntitySchemaRelationMap = {
@ -37,11 +37,19 @@ export class EntitySchemaRelationFactory {
); );
} }
const objectMetadataCollection =
await this.workspaceCacheStorageService.getObjectMetadataCollection(
workspaceId,
);
if (!objectMetadataCollection) {
throw new Error('Object metadata collection not found');
}
const relationDetails = await determineRelationDetails( const relationDetails = await determineRelationDetails(
workspaceId,
fieldMetadata, fieldMetadata,
relationMetadata, relationMetadata,
this.workspaceCacheStorageService, objectMetadataCollection,
); );
entitySchemaRelationMap[fieldMetadata.name] = { entitySchemaRelationMap[fieldMetadata.name] = {

View File

@ -93,7 +93,7 @@ export class WorkspaceDatasourceFactory {
const workspaceDataSource = new WorkspaceDataSource( const workspaceDataSource = new WorkspaceDataSource(
{ {
workspaceId, workspaceId,
workspaceCacheStorage: this.workspaceCacheStorageService, objectMetadataCollection: cachedObjectMetadataCollection,
}, },
{ {
url: url:

View File

@ -1,6 +1,6 @@
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
export interface WorkspaceInternalContext { export interface WorkspaceInternalContext {
workspaceId: string; workspaceId: string;
workspaceCacheStorage: WorkspaceCacheStorageService; objectMetadataCollection: ObjectMetadataEntity[];
} }

View File

@ -18,9 +18,9 @@ import {
SaveOptions, SaveOptions,
UpdateResult, UpdateResult,
} from 'typeorm'; } from 'typeorm';
import { PickKeysByType } from 'typeorm/common/PickKeysByType';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
import { UpsertOptions } from 'typeorm/repository/UpsertOptions'; import { UpsertOptions } from 'typeorm/repository/UpsertOptions';
import { PickKeysByType } from 'typeorm/common/PickKeysByType';
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
@ -235,6 +235,7 @@ export class WorkspaceRepository<
formattedEntityOrEntities, formattedEntityOrEntities,
options, options,
); );
const formattedResult = await this.formatResult(result); const formattedResult = await this.formatResult(result);
return formattedResult; return formattedResult;
@ -423,6 +424,7 @@ export class WorkspaceRepository<
entityManager?: EntityManager, entityManager?: EntityManager,
): Promise<InsertResult> { ): Promise<InsertResult> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const formatedEntity = await this.formatData(entity); const formatedEntity = await this.formatData(entity);
const result = await manager.insert(this.target, formatedEntity); const result = await manager.insert(this.target, formatedEntity);
const formattedResult = await this.formatResult(result); const formattedResult = await this.formatResult(result);
@ -621,22 +623,15 @@ export class WorkspaceRepository<
throw new Error('Object metadata name is missing'); throw new Error('Object metadata name is missing');
} }
const objectMetadata = const objectMetadata = this.internalContext.objectMetadataCollection.find(
await this.internalContext.workspaceCacheStorage.getObjectMetadata( (objectMetadata) => objectMetadata.nameSingular === objectMetadataName,
this.internalContext.workspaceId, );
(objectMetadata) => objectMetadata.nameSingular === objectMetadataName,
);
if (!objectMetadata) { if (!objectMetadata) {
const objectMetadataCollection =
await this.internalContext.workspaceCacheStorage.getObjectMetadataCollection(
this.internalContext.workspaceId,
);
throw new Error( throw new Error(
`Object metadata for object "${objectMetadataName}" is missing ` + `Object metadata for object "${objectMetadataName}" is missing ` +
`in workspace "${this.internalContext.workspaceId}" ` + `in workspace "${this.internalContext.workspaceId}" ` +
`with object metadata collection length: ${objectMetadataCollection?.length}`, `with object metadata collection length: ${this.internalContext.objectMetadataCollection.length}`,
); );
} }
@ -806,15 +801,13 @@ export class WorkspaceRepository<
if (relationMetadata) { if (relationMetadata) {
const toObjectMetadata = const toObjectMetadata =
await this.internalContext.workspaceCacheStorage.getObjectMetadata( this.internalContext.objectMetadataCollection.find(
relationMetadata.workspaceId,
(objectMetadata) => (objectMetadata) =>
objectMetadata.id === relationMetadata.toObjectMetadataId, objectMetadata.id === relationMetadata.toObjectMetadataId,
); );
const fromObjectMetadata = const fromObjectMetadata =
await this.internalContext.workspaceCacheStorage.getObjectMetadata( this.internalContext.objectMetadataCollection.find(
relationMetadata.workspaceId,
(objectMetadata) => (objectMetadata) =>
objectMetadata.id === relationMetadata.fromObjectMetadataId, objectMetadata.id === relationMetadata.fromObjectMetadataId,
); );
@ -833,6 +826,7 @@ export class WorkspaceRepository<
newData[key] = await this.formatResult( newData[key] = await this.formatResult(
value, value,
relationType === 'one-to-many' relationType === 'one-to-many'
? toObjectMetadata ? toObjectMetadata
: fromObjectMetadata, : fromObjectMetadata,

View File

@ -4,7 +4,6 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { computeRelationType } from 'src/engine/twenty-orm/utils/compute-relation-type.util'; import { computeRelationType } from 'src/engine/twenty-orm/utils/compute-relation-type.util';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
interface RelationDetails { interface RelationDetails {
relationType: RelationType; relationType: RelationType;
@ -14,10 +13,9 @@ interface RelationDetails {
} }
export async function determineRelationDetails( export async function determineRelationDetails(
workspaceId: string,
fieldMetadata: FieldMetadataEntity, fieldMetadata: FieldMetadataEntity,
relationMetadata: RelationMetadataEntity, relationMetadata: RelationMetadataEntity,
workspaceCacheStorageService: WorkspaceCacheStorageService, objectMetadataCollection: ObjectMetadataEntity[],
): Promise<RelationDetails> { ): Promise<RelationDetails> {
const relationType = computeRelationType(fieldMetadata, relationMetadata); const relationType = computeRelationType(fieldMetadata, relationMetadata);
let fromObjectMetadata: ObjectMetadataEntity | undefined = let fromObjectMetadata: ObjectMetadataEntity | undefined =
@ -28,8 +26,8 @@ export async function determineRelationDetails(
// RelationMetadata always store the relation from the perspective of the `from` object, MANY_TO_ONE relations are not stored yet // RelationMetadata always store the relation from the perspective of the `from` object, MANY_TO_ONE relations are not stored yet
if (relationType === 'many-to-one') { if (relationType === 'many-to-one') {
fromObjectMetadata = fieldMetadata.object; fromObjectMetadata = fieldMetadata.object;
toObjectMetadata = await workspaceCacheStorageService.getObjectMetadata(
workspaceId, toObjectMetadata = objectMetadataCollection.find(
(objectMetadata) => (objectMetadata) =>
objectMetadata.id === relationMetadata.fromObjectMetadataId, objectMetadata.id === relationMetadata.fromObjectMetadataId,
); );

View File

@ -63,21 +63,6 @@ export class WorkspaceCacheStorageService {
); );
} }
async getObjectMetadata(
workspaceId: string,
predicate: (objectMetadata: ObjectMetadataEntity) => boolean,
): Promise<ObjectMetadataEntity | undefined> {
const objectMetadataCollection = await this.workspaceSchemaCache.get<
ObjectMetadataEntity[]
>(`objectMetadataCollection:${workspaceId}`);
if (!objectMetadataCollection) {
return;
}
return objectMetadataCollection.find(predicate);
}
setTypeDefs(workspaceId: string, typeDefs: string): Promise<void> { setTypeDefs(workspaceId: string, typeDefs: string): Promise<void> {
return this.workspaceSchemaCache.set<string>( return this.workspaceSchemaCache.set<string>(
`typeDefs:${workspaceId}`, `typeDefs:${workspaceId}`,