From ebc25c8695de44032075a6fe096aa2d35f93ac7b Mon Sep 17 00:00:00 2001 From: Weiko Date: Fri, 26 Apr 2024 19:27:09 +0200 Subject: [PATCH] Add redis to useMetadataCache yoga plugin (#5194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context @lucasbordeau introduced a new Yoga plugin that allows us to cache our requests (👏), see https://github.com/twentyhq/twenty/pull/5189 I'm simply updating the implementation to allow us to use different cache storage types such as redis Also adding a check so it does not use cache for other operations than ObjectMetadataItems ## Test locally, first call takes 340ms, 2nd takes 30ms with 'redis' and 13ms with 'memory' --- .../hooks/use-cached-metadata.ts | 33 +++++++++++++------ .../graphql/metadata-graphql-api.module.ts | 10 +++++- .../api/graphql/metadata.module-factory.ts | 12 ++++++- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-config/hooks/use-cached-metadata.ts b/packages/twenty-server/src/engine/api/graphql/graphql-config/hooks/use-cached-metadata.ts index b89af80dc..5aa40c728 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-config/hooks/use-cached-metadata.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-config/hooks/use-cached-metadata.ts @@ -1,8 +1,12 @@ import { Plugin } from 'graphql-yoga'; -export function useCachedMetadata(): Plugin { - const cache = new Map(); +export type CacheMetadataPluginConfig = { + cacheGetter: (key: string) => any; + cacheSetter: (key: string, value: any) => void; + operationsToCache: string[]; +}; +export function useCachedMetadata(config: CacheMetadataPluginConfig): Plugin { const computeCacheKey = (serverContext: any) => { const workspaceId = serverContext.req.workspace?.id ?? 'anonymous'; const cacheVersion = serverContext.req.cacheVersion ?? '0'; @@ -10,28 +14,37 @@ export function useCachedMetadata(): Plugin { return `${workspaceId}:${cacheVersion}`; }; + const getOperationName = (serverContext: any) => + serverContext?.req?.body?.operationName; + return { - onRequest: ({ endResponse, serverContext }) => { + onRequest: async ({ endResponse, serverContext }) => { + if (!config.operationsToCache.includes(getOperationName(serverContext))) { + return; + } + const cacheKey = computeCacheKey(serverContext); - const foundInCache = cache.has(cacheKey); - - if (foundInCache) { - const cachedResponse = cache.get(cacheKey); + const cachedResponse = await config.cacheGetter(cacheKey); + if (cachedResponse) { const earlyResponse = Response.json(cachedResponse); return endResponse(earlyResponse); } }, onResponse: async ({ response, serverContext }) => { + if (!config.operationsToCache.includes(getOperationName(serverContext))) { + return; + } + const cacheKey = computeCacheKey(serverContext); - const foundInCache = cache.has(cacheKey); + const cachedResponse = await config.cacheGetter(cacheKey); - if (!foundInCache) { + if (!cachedResponse) { const responseBody = await response.json(); - cache.set(cacheKey, responseBody); + config.cacheSetter(cacheKey, responseBody); } }, }; diff --git a/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts b/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts index cc305f99c..cc41b6e74 100644 --- a/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts @@ -12,6 +12,8 @@ import { EnvironmentService } from 'src/engine/integrations/environment/environm import { ExceptionHandlerService } from 'src/engine/integrations/exception-handler/exception-handler.service'; import { DataloaderModule } from 'src/engine/dataloaders/dataloader.module'; import { DataloaderService } from 'src/engine/dataloaders/dataloader.service'; +import { CacheStorageNamespace } from 'src/engine/integrations/cache-storage/types/cache-storage-namespace.enum'; +import { CacheStorageModule } from 'src/engine/integrations/cache-storage/cache-storage.module'; @Module({ imports: [ @@ -19,11 +21,17 @@ import { DataloaderService } from 'src/engine/dataloaders/dataloader.service'; driver: YogaDriver, useFactory: metadataModuleFactory, imports: [GraphQLConfigModule, DataloaderModule], - inject: [EnvironmentService, ExceptionHandlerService, DataloaderService], + inject: [ + EnvironmentService, + ExceptionHandlerService, + DataloaderService, + CacheStorageNamespace.WorkspaceSchema, + ], }), MetadataEngineModule, WorkspaceMigrationRunnerModule, WorkspaceMigrationModule, + CacheStorageModule, ], }) export class MetadataGraphQLApiModule {} diff --git a/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts b/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts index a124086a7..5c28d63f3 100644 --- a/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts @@ -9,11 +9,13 @@ import { MetadataGraphQLApiModule } from 'src/engine/api/graphql/metadata-graphq import { renderApolloPlayground } from 'src/engine/utils/render-apollo-playground.util'; import { DataloaderService } from 'src/engine/dataloaders/dataloader.service'; import { useCachedMetadata } from 'src/engine/api/graphql/graphql-config/hooks/use-cached-metadata'; +import { CacheStorageService } from 'src/engine/integrations/cache-storage/cache-storage.service'; export const metadataModuleFactory = async ( environmentService: EnvironmentService, exceptionHandlerService: ExceptionHandlerService, dataloaderService: DataloaderService, + workspaceSchemaCacheStorage: CacheStorageService, ): Promise => { const config: YogaDriverConfig = { autoSchemaFile: true, @@ -33,7 +35,15 @@ export const metadataModuleFactory = async ( useExceptionHandler({ exceptionHandlerService, }), - useCachedMetadata(), + useCachedMetadata({ + cacheGetter: workspaceSchemaCacheStorage.get.bind( + workspaceSchemaCacheStorage, + ), + cacheSetter: workspaceSchemaCacheStorage.set.bind( + workspaceSchemaCacheStorage, + ), + operationsToCache: ['ObjectMetadataItems'], + }), ], path: '/metadata', context: () => ({