Improve performance twenty orm (#6691)

## Context

As we grow, the messaging scripts are experiencing performance issues
forcing us to temporarily disable them on the cloud.
While investigating the performance, I have noticed that generating the
entity schema (for twentyORM) in the repository is taking ~500ms locally
on my Mac M2 so likely more on pods. Caching the entitySchema then!

I'm also clarifying naming around schemaVersion and cacheVersions ==>
both are renamed workspaceMetadataVersion and migrated to the workspace
table (the workspaceCacheVersion table is dropped).
This commit is contained in:
Charles Bochet
2024-08-20 19:42:02 +02:00
committed by GitHub
parent 3ae89d15de
commit 17a1760afd
80 changed files with 583 additions and 468 deletions

View File

@ -1,10 +1,8 @@
import { Module } from '@nestjs/common';
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
@Module({
imports: [WorkspaceCacheVersionModule],
providers: [WorkspaceCacheStorageService],
exports: [WorkspaceCacheStorageService],
})

View File

@ -1,56 +1,67 @@
import { Injectable, Logger } from '@nestjs/common';
import { EntitySchemaOptions } from 'typeorm';
import { CacheStorageService } from 'src/engine/integrations/cache-storage/cache-storage.service';
import { InjectCacheStorage } from 'src/engine/integrations/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageNamespace } from 'src/engine/integrations/cache-storage/types/cache-storage-namespace.enum';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
enum WorkspaceCacheKeys {
GraphQLTypeDefs = 'graphql:type-defs',
GraphQLUsedScalarNames = 'graphql:used-scalar-names',
GraphQLOperations = 'graphql:operations',
ORMEntitySchemas = 'orm:entity-schemas',
MetadataObjectMetadataCollection = 'metadata:object-metadata-collection',
MetadataVersion = 'metadata:workspace-metadata-version',
}
@Injectable()
export class WorkspaceCacheStorageService {
private readonly logger = new Logger(WorkspaceCacheStorageService.name);
constructor(
@InjectCacheStorage(CacheStorageNamespace.WorkspaceSchema)
private readonly workspaceSchemaCache: CacheStorageService,
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
private readonly cacheStorageService: CacheStorageService,
) {}
async validateCacheVersion(workspaceId: string): Promise<void> {
const currentVersion =
(await this.workspaceSchemaCache.get<string>(
`cacheVersion:${workspaceId}`,
)) ?? '0';
setORMEntitySchema(
workspaceId: string,
entitySchemas: EntitySchemaOptions<any>[],
) {
return this.cacheStorageService.set<EntitySchemaOptions<any>[]>(
`${WorkspaceCacheKeys.ORMEntitySchemas}:${workspaceId}`,
entitySchemas,
);
}
let latestVersion =
await this.workspaceCacheVersionService.getVersion(workspaceId);
getORMEntitySchema(
workspaceId: string,
): Promise<EntitySchemaOptions<any>[] | undefined> {
return this.cacheStorageService.get<EntitySchemaOptions<any>[]>(
`${WorkspaceCacheKeys.ORMEntitySchemas}:${workspaceId}`,
);
}
if (!latestVersion || currentVersion !== latestVersion) {
// Invalidate cache if version mismatch is detected"
this.logger.log(
`Cache version mismatch detected for workspace ${workspaceId}. Current version: ${currentVersion}. Latest version: ${latestVersion}. Invalidating cache...`,
);
setMetadataVersion(workspaceId: string, version: number): Promise<void> {
return this.cacheStorageService.set<number>(
`${WorkspaceCacheKeys.MetadataVersion}:${workspaceId}`,
version,
);
}
await this.invalidateCache(workspaceId);
// If the latest version is not found, increment the version
latestVersion ??=
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
// Update the cache version after invalidation
await this.workspaceSchemaCache.set<string>(
`cacheVersion:${workspaceId}`,
latestVersion,
);
}
getMetadataVersion(workspaceId: string): Promise<number | undefined> {
return this.cacheStorageService.get<number>(
`${WorkspaceCacheKeys.MetadataVersion}:${workspaceId}`,
);
}
setObjectMetadataCollection(
workspaceId: string,
objectMetadataCollection: ObjectMetadataEntity[],
) {
return this.workspaceSchemaCache.set<ObjectMetadataEntity[]>(
`objectMetadataCollection:${workspaceId}`,
return this.cacheStorageService.set<ObjectMetadataEntity[]>(
`${WorkspaceCacheKeys.MetadataObjectMetadataCollection}:${workspaceId}`,
objectMetadataCollection,
);
}
@ -58,43 +69,57 @@ export class WorkspaceCacheStorageService {
getObjectMetadataCollection(
workspaceId: string,
): Promise<ObjectMetadataEntity[] | undefined> {
return this.workspaceSchemaCache.get<ObjectMetadataEntity[]>(
`objectMetadataCollection:${workspaceId}`,
return this.cacheStorageService.get<ObjectMetadataEntity[]>(
`${WorkspaceCacheKeys.MetadataObjectMetadataCollection}:${workspaceId}`,
);
}
setTypeDefs(workspaceId: string, typeDefs: string): Promise<void> {
return this.workspaceSchemaCache.set<string>(
`typeDefs:${workspaceId}`,
setGraphQLTypeDefs(workspaceId: string, typeDefs: string): Promise<void> {
return this.cacheStorageService.set<string>(
`${WorkspaceCacheKeys.GraphQLTypeDefs}:${workspaceId}`,
typeDefs,
);
}
getTypeDefs(workspaceId: string): Promise<string | undefined> {
return this.workspaceSchemaCache.get<string>(`typeDefs:${workspaceId}`);
getGraphQLTypeDefs(workspaceId: string): Promise<string | undefined> {
return this.cacheStorageService.get<string>(
`${WorkspaceCacheKeys.GraphQLTypeDefs}:${workspaceId}`,
);
}
setUsedScalarNames(
setGraphQLUsedScalarNames(
workspaceId: string,
scalarsUsed: string[],
usedScalarNames: string[],
): Promise<void> {
return this.workspaceSchemaCache.set<string[]>(
`usedScalarNames:${workspaceId}`,
scalarsUsed,
return this.cacheStorageService.set<string[]>(
`${WorkspaceCacheKeys.GraphQLUsedScalarNames}:${workspaceId}`,
usedScalarNames,
);
}
getUsedScalarNames(workspaceId: string): Promise<string[] | undefined> {
return this.workspaceSchemaCache.get<string[]>(
`usedScalarNames:${workspaceId}`,
getGraphQLUsedScalarNames(
workspaceId: string,
): Promise<string[] | undefined> {
return this.cacheStorageService.get<string[]>(
`${WorkspaceCacheKeys.GraphQLUsedScalarNames}:${workspaceId}`,
);
}
async invalidateCache(workspaceId: string): Promise<void> {
await this.workspaceSchemaCache.del(
`objectMetadataCollection:${workspaceId}`,
async flush(workspaceId: string): Promise<void> {
await this.cacheStorageService.del(
`${WorkspaceCacheKeys.MetadataObjectMetadataCollection}:${workspaceId}`,
);
await this.cacheStorageService.del(
`${WorkspaceCacheKeys.MetadataVersion}:${workspaceId}`,
);
await this.cacheStorageService.del(
`${WorkspaceCacheKeys.GraphQLTypeDefs}:${workspaceId}`,
);
await this.cacheStorageService.del(
`${WorkspaceCacheKeys.GraphQLUsedScalarNames}:${workspaceId}`,
);
await this.cacheStorageService.del(
`${WorkspaceCacheKeys.ORMEntitySchemas}:${workspaceId}`,
);
await this.workspaceSchemaCache.del(`typeDefs:${workspaceId}`);
await this.workspaceSchemaCache.del(`usedScalarNames:${workspaceId}`);
}
}