Optimize EntityMetadata caching in GlobalWorkspaceDataSource (#16146)
## Context EntityMetadata was being rebuilt from scratch on every findMetadata()/getMetadata() call (~20 times per request). This involved running EntitySchemaTransformer.transform() and EntityMetadataBuilder.build() repeatedly, causing unnecessary CPU overhead. ## Implementation Cache entityMetadatas in ORMWorkspaceContext: Build EntityMetadata once during workspace context initialization instead of on every metadata lookup Remove redundant entitySchemas caching: Since flatMetadata is already cached, the additional Redis cache for entitySchemaOptions was unnecessary overhead Remove WorkspaceEntitiesStorage: Replaced with direct lookup from FlatObjectMetadataMap Simplify getObjectMetadataFromEntityTarget: Now only accepts string targets, using flat metadata maps directly Also: Removed unused injections in some services
This commit is contained in:
-2
@@ -6,7 +6,6 @@ import {
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
|
||||
import { GlobalWorkspaceDataSource } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
|
||||
@@ -19,7 +18,6 @@ export class GlobalWorkspaceDataSourceService
|
||||
|
||||
constructor(
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly entitySchemaFactory: EntitySchemaFactory,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
) {}
|
||||
|
||||
|
||||
+3
-24
@@ -4,7 +4,6 @@ import {
|
||||
DataSource,
|
||||
type DataSourceOptions,
|
||||
type EntityMetadata,
|
||||
type EntitySchema,
|
||||
type EntityTarget,
|
||||
type ObjectLiteral,
|
||||
type QueryRunner,
|
||||
@@ -12,9 +11,7 @@ import {
|
||||
type SelectQueryBuilder,
|
||||
} from 'typeorm';
|
||||
import { EntityManagerFactory } from 'typeorm/entity-manager/EntityManagerFactory';
|
||||
import { EntitySchemaTransformer } from 'typeorm/entity-schema/EntitySchemaTransformer';
|
||||
import { EntityMetadataNotFoundError } from 'typeorm/error/EntityMetadataNotFoundError';
|
||||
import { EntityMetadataBuilder } from 'typeorm/metadata-builder/EntityMetadataBuilder';
|
||||
|
||||
import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
|
||||
import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
@@ -26,7 +23,7 @@ import {
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { type WorkspaceQueryRunner } from 'src/engine/twenty-orm/query-runner/workspace-query-runner';
|
||||
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { getWorkspaceContext } from 'src/engine/twenty-orm/storage/workspace-context.storage';
|
||||
import { getWorkspaceContext } from 'src/engine/twenty-orm/storage/orm-workspace-context.storage';
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
import { type WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
|
||||
@@ -83,11 +80,9 @@ export class GlobalWorkspaceDataSource extends DataSource {
|
||||
target: EntityTarget<ObjectLiteral>,
|
||||
): EntityMetadata | undefined {
|
||||
const context = getWorkspaceContext();
|
||||
const { entitySchemas } = context;
|
||||
const { entityMetadatas } = context;
|
||||
|
||||
const entityMetadata = this.buildMetadatasFromSchemas(entitySchemas);
|
||||
|
||||
return entityMetadata.find((metadata) => metadata.target === target);
|
||||
return entityMetadatas.find((metadata) => metadata.target === target);
|
||||
}
|
||||
|
||||
override getMetadata(target: EntityTarget<ObjectLiteral>): EntityMetadata {
|
||||
@@ -264,20 +259,4 @@ export class GlobalWorkspaceDataSource extends DataSource {
|
||||
|
||||
return super.query(query, parameters, queryRunner);
|
||||
}
|
||||
|
||||
private buildMetadatasFromSchemas(
|
||||
entitySchemas: EntitySchema[],
|
||||
): EntityMetadata[] {
|
||||
const transformer = new EntitySchemaTransformer();
|
||||
const metadataArgsStorage = transformer.transform(entitySchemas);
|
||||
|
||||
const entityMetadataBuilder = new EntityMetadataBuilder(
|
||||
this,
|
||||
metadataArgsStorage,
|
||||
);
|
||||
|
||||
const entityMetadatas = entityMetadataBuilder.build();
|
||||
|
||||
return entityMetadatas;
|
||||
}
|
||||
}
|
||||
|
||||
+28
-48
@@ -2,6 +2,8 @@ import { Injectable, type Type } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { EntitySchema, ObjectLiteral } from 'typeorm';
|
||||
import { EntitySchemaTransformer } from 'typeorm/entity-schema/EntitySchemaTransformer';
|
||||
import { EntityMetadataBuilder } from 'typeorm/metadata-builder/EntityMetadataBuilder';
|
||||
|
||||
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
|
||||
|
||||
@@ -16,9 +18,9 @@ import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-sche
|
||||
import { GlobalWorkspaceDataSourceService } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.service';
|
||||
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import {
|
||||
type WorkspaceContext,
|
||||
type ORMWorkspaceContext,
|
||||
withWorkspaceContext,
|
||||
} from 'src/engine/twenty-orm/storage/workspace-context.storage';
|
||||
} from 'src/engine/twenty-orm/storage/orm-workspace-context.storage';
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
|
||||
@@ -85,7 +87,7 @@ export class GlobalWorkspaceOrmManager {
|
||||
|
||||
private async loadWorkspaceContext(
|
||||
authContext: WorkspaceAuthContext,
|
||||
): Promise<WorkspaceContext> {
|
||||
): Promise<ORMWorkspaceContext> {
|
||||
const workspaceId = authContext.workspace.id;
|
||||
|
||||
const { flatObjectMetadataMaps, flatFieldMetadataMaps, flatIndexMaps } =
|
||||
@@ -103,15 +105,6 @@ export class GlobalWorkspaceOrmManager {
|
||||
const { idByNameSingular: objectIdByNameSingular } =
|
||||
buildObjectIdByNameMaps(flatObjectMetadataMaps);
|
||||
|
||||
const metadataVersion =
|
||||
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
||||
|
||||
if (!isDefined(metadataVersion)) {
|
||||
throw new Error(
|
||||
`Metadata version not found for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const { data: featureFlagsMap } =
|
||||
await this.workspaceFeatureFlagsMapCacheService.getWorkspaceFeatureFlagsMapAndVersion(
|
||||
{ workspaceId },
|
||||
@@ -122,65 +115,52 @@ export class GlobalWorkspaceOrmManager {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const entitySchemas = await this.buildEntitySchemas(
|
||||
const entitySchemas = this.buildEntitySchemas(
|
||||
workspaceId,
|
||||
metadataVersion,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const entityMetadatas = this.buildEntityMetadatas(entitySchemas);
|
||||
|
||||
return {
|
||||
authContext,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatIndexMaps,
|
||||
objectIdByNameSingular,
|
||||
metadataVersion,
|
||||
featureFlagsMap,
|
||||
permissionsPerRoleId,
|
||||
entitySchemas,
|
||||
entityMetadatas,
|
||||
};
|
||||
}
|
||||
|
||||
private async buildEntitySchemas(
|
||||
private buildEntitySchemas(
|
||||
workspaceId: string,
|
||||
dataSourceMetadataVersion: number,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
const cachedEntitySchemaOptions =
|
||||
await this.workspaceCacheStorageService.getORMEntitySchema(
|
||||
workspaceId,
|
||||
dataSourceMetadataVersion,
|
||||
return Object.values(flatObjectMetadataMaps.byId)
|
||||
.filter(isDefined)
|
||||
.map((flatObjectMetadata) =>
|
||||
this.entitySchemaFactory.create(
|
||||
workspaceId,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let cachedEntitySchemas: EntitySchema[];
|
||||
private buildEntityMetadatas(entitySchemas: EntitySchema[]) {
|
||||
const transformer = new EntitySchemaTransformer();
|
||||
const metadataArgsStorage = transformer.transform(entitySchemas);
|
||||
|
||||
if (cachedEntitySchemaOptions) {
|
||||
cachedEntitySchemas = cachedEntitySchemaOptions.map(
|
||||
(option) => new EntitySchema(option),
|
||||
);
|
||||
} else {
|
||||
const entitySchemas = Object.values(flatObjectMetadataMaps.byId)
|
||||
.filter(isDefined)
|
||||
.map((flatObjectMetadata) =>
|
||||
this.entitySchemaFactory.create(
|
||||
workspaceId,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
),
|
||||
);
|
||||
const entityMetadataBuilder = new EntityMetadataBuilder(
|
||||
this.globalWorkspaceDataSourceService.getGlobalWorkspaceDataSource(),
|
||||
metadataArgsStorage,
|
||||
);
|
||||
|
||||
await this.workspaceCacheStorageService.setORMEntitySchema(
|
||||
workspaceId,
|
||||
dataSourceMetadataVersion,
|
||||
entitySchemas.map((entitySchema) => entitySchema.options),
|
||||
);
|
||||
|
||||
cachedEntitySchemas = entitySchemas;
|
||||
}
|
||||
|
||||
return cachedEntitySchemas;
|
||||
return entityMetadataBuilder.build();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user