feat: add memory cache to boost performance (#2620)

* feat: add memory cache to boost performance

* fix: tests

* fix: logging

* fix: missing commented stuff
This commit is contained in:
Jérémy M
2023-11-21 18:29:31 +01:00
committed by GitHub
parent 74e0122294
commit dd125ddfcc
27 changed files with 458 additions and 17 deletions

View File

@ -0,0 +1,11 @@
export class WorkspaceMigrationAppliedEvent {
private readonly _workspaceId: string;
constructor(worskapceId: string) {
this._workspaceId = worskapceId;
}
get workspaceId(): string {
return this._workspaceId;
}
}

View File

@ -0,0 +1,3 @@
export enum WorkspaceMigrationEvents {
MigrationApplied = '@workspace/migration-applied',
}

View File

@ -1,4 +1,5 @@
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import {
QueryRunner,
@ -17,6 +18,8 @@ import {
WorkspaceMigrationColumnCreate,
WorkspaceMigrationColumnRelation,
} from 'src/metadata/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationEvents } from 'src/workspace/workspace-migration-runner/events/workspace-migration-events';
import { WorkspaceMigrationAppliedEvent } from 'src/workspace/workspace-migration-runner/events/workspace-migration-applied.event';
import { customTableDefaultColumns } from './utils/custom-table-default-column.util';
@ -25,6 +28,7 @@ export class WorkspaceMigrationRunnerService {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private readonly workspaceMigrationService: WorkspaceMigrationService,
private readonly eventEmitter: EventEmitter2,
) {}
/**
@ -78,6 +82,12 @@ export class WorkspaceMigrationRunnerService {
await queryRunner.release();
// Emit event when migration is applied
this.eventEmitter.emit(
WorkspaceMigrationEvents.MigrationApplied,
new WorkspaceMigrationAppliedEvent(workspaceId),
);
return flattenedPendingMigrations;
}

View File

@ -142,13 +142,9 @@ export class WorkspaceQueryRunnerService {
)};
`);
const queryFormatted = query
.replace('neq:null', 'is:NOT_NULL')
.replace('eq:null', 'is:NULL');
const results = await workspaceDataSource?.query<PGGraphQLResult>(`
SELECT graphql.resolve($$
${queryFormatted}
${query}
$$);
`);

View File

@ -0,0 +1,29 @@
import { Module } from '@nestjs/common';
import { MemoryStorageType } from 'src/integrations/environment/interfaces/memory-storage.interface';
import { MemoryStorageModule } from 'src/integrations/memory-storage/memory-storage.module';
import { MemoryStorageJsonSerializer } from 'src/integrations/memory-storage/serializers/json.serializer';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
import { WorkspaceSchemaStorageService } from 'src/workspace/workspace-schema-storage/workspace-schema-storage.service';
@Module({
imports: [
ObjectMetadataModule,
MemoryStorageModule.forRoot({
identifier: 'objectMetadataCollection',
type: MemoryStorageType.Local,
options: {},
serializer: new MemoryStorageJsonSerializer<ObjectMetadataEntity[]>(),
}),
MemoryStorageModule.forRoot({
identifier: 'typeDefs',
type: MemoryStorageType.Local,
options: {},
}),
],
providers: [WorkspaceSchemaStorageService],
exports: [WorkspaceSchemaStorageService],
})
export class WorkspaceSchemaStorageModule {}

View File

@ -0,0 +1,60 @@
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { InjectMemoryStorage } from 'src/integrations/memory-storage/decorators/inject-memory-storage.decorator';
import { MemoryStorageService } from 'src/integrations/memory-storage/memory-storage.service';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { WorkspaceMigrationAppliedEvent } from 'src/workspace/workspace-migration-runner/events/workspace-migration-applied.event';
import { WorkspaceMigrationEvents } from 'src/workspace/workspace-migration-runner/events/workspace-migration-events';
@Injectable()
export class WorkspaceSchemaStorageService {
constructor(
@InjectMemoryStorage('objectMetadataCollection')
private readonly objectMetadataMemoryStorageService: MemoryStorageService<
ObjectMetadataEntity[]
>,
@InjectMemoryStorage('typeDefs')
private readonly typeDefsMemoryStorageService: MemoryStorageService<string>,
) {}
setObjectMetadata(
workspaceId: string,
objectMetadata: ObjectMetadataEntity[],
) {
return this.objectMetadataMemoryStorageService.write({
key: workspaceId,
data: objectMetadata,
});
}
getObjectMetadata(
workspaceId: string,
): Promise<ObjectMetadataEntity[] | null> {
return this.objectMetadataMemoryStorageService.read({
key: workspaceId,
});
}
setTypeDefs(workspaceId: string, typeDefs: string): Promise<void> {
return this.typeDefsMemoryStorageService.write({
key: workspaceId,
data: typeDefs,
});
}
getTypeDefs(workspaceId: string): Promise<string | null> {
return this.typeDefsMemoryStorageService.read({
key: workspaceId,
});
}
/**
* Clear the workspace schema storage when new migrations are applied for a specific workspace
*/
@OnEvent(WorkspaceMigrationEvents.MigrationApplied)
handleMigrationAppliedEvent({ workspaceId }: WorkspaceMigrationAppliedEvent) {
this.objectMetadataMemoryStorageService.delete({ key: workspaceId });
this.typeDefsMemoryStorageService.delete({ key: workspaceId });
}
}

View File

@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
import { WorkspaceSchemaStorageService } from 'src/workspace/workspace-schema-storage/workspace-schema-storage.service';
import { WorkspaceFactory } from './workspace.factory';
@ -31,6 +32,10 @@ describe('WorkspaceFactory', () => {
provide: WorkspaceResolverFactory,
useValue: {},
},
{
provide: WorkspaceSchemaStorageService,
useValue: {},
},
],
}).compile();

View File

@ -5,6 +5,7 @@ import { makeExecutableSchema } from '@graphql-tools/schema';
import { gql } from 'graphql-tag';
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
import { WorkspaceSchemaStorageService } from 'src/workspace/workspace-schema-storage/workspace-schema-storage.service';
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
import { WorkspaceGraphQLSchemaFactory } from './workspace-schema-builder/workspace-graphql-schema.factory';
@ -18,6 +19,7 @@ export class WorkspaceFactory {
private readonly objectMetadataService: ObjectMetadataService,
private readonly workspaceGraphQLSchemaFactory: WorkspaceGraphQLSchemaFactory,
private readonly workspaceResolverFactory: WorkspaceResolverFactory,
private readonly workspaceSchemaStorageService: WorkspaceSchemaStorageService,
) {}
async createGraphQLSchema(
@ -37,15 +39,43 @@ export class WorkspaceFactory {
return new GraphQLSchema({});
}
const objectMetadataCollection =
await this.objectMetadataService.getObjectMetadataFromWorkspaceId(
workspaceId,
);
// Get object metadata from cache
let objectMetadataCollection =
await this.workspaceSchemaStorageService.getObjectMetadata(workspaceId);
const autoGeneratedSchema = await this.workspaceGraphQLSchemaFactory.create(
objectMetadataCollection,
workspaceResolverBuilderMethodNames,
// If object metadata is not cached, get it from the database
if (!objectMetadataCollection) {
objectMetadataCollection =
await this.objectMetadataService.getObjectMetadataFromWorkspaceId(
workspaceId,
);
await this.workspaceSchemaStorageService.setObjectMetadata(
workspaceId,
objectMetadataCollection,
);
}
// Get typeDefs from cache
let typeDefs = await this.workspaceSchemaStorageService.getTypeDefs(
workspaceId,
);
// If typeDefs are not cached, generate them
if (!typeDefs) {
const autoGeneratedSchema =
await this.workspaceGraphQLSchemaFactory.create(
objectMetadataCollection,
workspaceResolverBuilderMethodNames,
);
typeDefs = printSchema(autoGeneratedSchema);
await this.workspaceSchemaStorageService.setTypeDefs(
workspaceId,
typeDefs,
);
}
const autoGeneratedResolvers = await this.workspaceResolverFactory.create(
workspaceId,
objectMetadataCollection,
@ -53,7 +83,6 @@ export class WorkspaceFactory {
);
// TODO: Cache the generate type definitions
const typeDefs = printSchema(autoGeneratedSchema);
const executableSchema = makeExecutableSchema({
typeDefs: gql`
${typeDefs}

View File

@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { MetadataModule } from 'src/metadata/metadata.module';
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
import { WorkspaceSchemaStorageModule } from 'src/workspace/workspace-schema-storage/workspace-schema-storage.module';
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
import { WorkspaceFactory } from './workspace.factory';
@ -16,6 +17,7 @@ import { WorkspaceResolverBuilderModule } from './workspace-resolver-builder/wor
ObjectMetadataModule,
WorkspaceSchemaBuilderModule,
WorkspaceResolverBuilderModule,
WorkspaceSchemaStorageModule,
],
providers: [WorkspaceFactory],
exports: [WorkspaceFactory],