From 77eece77ea7b0c0a248b7f75555c261b6a232fa8 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Fri, 26 Apr 2024 17:31:40 +0200 Subject: [PATCH] Add a cache on /metadata (#5189) In this PR I'm introducing a simple custom graphql-yoga plugin to create a caching mechanism specific to our metadata. The cache key is made of : workspace id + workspace cache version, with this the cache is automatically invalidated each time a change is made on the workspace metadata. --- .../hooks/use-cached-metadata.ts | 38 +++++++++++++++++++ .../api/graphql/metadata.module-factory.ts | 2 + .../workspace-schema-storage.module.ts | 2 +- .../environment/environment-variables.ts | 3 +- 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-config/hooks/use-cached-metadata.ts 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 new file mode 100644 index 000000000..b89af80dc --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-config/hooks/use-cached-metadata.ts @@ -0,0 +1,38 @@ +import { Plugin } from 'graphql-yoga'; + +export function useCachedMetadata(): Plugin { + const cache = new Map(); + + const computeCacheKey = (serverContext: any) => { + const workspaceId = serverContext.req.workspace?.id ?? 'anonymous'; + const cacheVersion = serverContext.req.cacheVersion ?? '0'; + + return `${workspaceId}:${cacheVersion}`; + }; + + return { + onRequest: ({ endResponse, serverContext }) => { + const cacheKey = computeCacheKey(serverContext); + const foundInCache = cache.has(cacheKey); + + if (foundInCache) { + const cachedResponse = cache.get(cacheKey); + + const earlyResponse = Response.json(cachedResponse); + + return endResponse(earlyResponse); + } + }, + onResponse: async ({ response, serverContext }) => { + const cacheKey = computeCacheKey(serverContext); + + const foundInCache = cache.has(cacheKey); + + if (!foundInCache) { + const responseBody = await response.json(); + + cache.set(cacheKey, responseBody); + } + }, + }; +} 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 b58c3ac3b..a124086a7 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 @@ -8,6 +8,7 @@ import { useThrottler } from 'src/engine/api/graphql/graphql-config/hooks/use-th import { MetadataGraphQLApiModule } from 'src/engine/api/graphql/metadata-graphql-api.module'; 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'; export const metadataModuleFactory = async ( environmentService: EnvironmentService, @@ -32,6 +33,7 @@ export const metadataModuleFactory = async ( useExceptionHandler({ exceptionHandlerService, }), + useCachedMetadata(), ], path: '/metadata', context: () => ({ diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-storage/workspace-schema-storage.module.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-storage/workspace-schema-storage.module.ts index bd57d9db2..8d7d4d483 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-storage/workspace-schema-storage.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-storage/workspace-schema-storage.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; -import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module'; import { WorkspaceSchemaStorageService } from 'src/engine/api/graphql/workspace-schema-storage/workspace-schema-storage.service'; +import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; @Module({ imports: [ObjectMetadataModule, WorkspaceCacheVersionModule], diff --git a/packages/twenty-server/src/engine/integrations/environment/environment-variables.ts b/packages/twenty-server/src/engine/integrations/environment/environment-variables.ts index 36c13dfbc..aab54ab1a 100644 --- a/packages/twenty-server/src/engine/integrations/environment/environment-variables.ts +++ b/packages/twenty-server/src/engine/integrations/environment/environment-variables.ts @@ -26,6 +26,7 @@ import { LoggerDriverType } from 'src/engine/integrations/logger/interfaces'; import { IsStrictlyLowerThan } from 'src/engine/integrations/environment/decorators/is-strictly-lower-than.decorator'; import { CaptchaDriverType } from 'src/engine/integrations/captcha/interfaces'; import { MessageQueueDriverType } from 'src/engine/integrations/message-queue/interfaces'; +import { CacheStorageType } from 'src/engine/integrations/cache-storage/types/cache-storage-type.enum'; import { IsDuration } from './decorators/is-duration.decorator'; import { AwsRegion } from './interfaces/aws-region.interface'; @@ -369,7 +370,7 @@ export class EnvironmentVariables { @CastToPositiveNumber() API_RATE_LIMITING_LIMIT = 500; - CACHE_STORAGE_TYPE = 'memory'; + CACHE_STORAGE_TYPE: CacheStorageType = CacheStorageType.Memory; @CastToPositiveNumber() CACHE_STORAGE_TTL: number = 3600 * 24 * 7;