Read feature flags from cache (#11556)

We are now storing a workspace's feature flag map in our redis cache. 
The cache is invalidated upon feature flag update through the lab
resolver.
This commit is contained in:
Marie
2025-04-14 17:31:13 +02:00
committed by GitHub
parent 15eb96337f
commit d4deca45e8
22 changed files with 323 additions and 248 deletions

View File

@ -64,11 +64,11 @@ export class WorkspaceDataSource extends DataSource {
this.permissionsPerRoleId = permissionsPerRoleId;
}
setFeatureFlagMap(featureFlagMap: FeatureFlagMap) {
setFeatureFlagsMap(featureFlagMap: FeatureFlagMap) {
this.featureFlagMap = featureFlagMap;
}
setFeatureFlagMapVersion(featureFlagMapVersion: string) {
setFeatureFlagsMapVersion(featureFlagMapVersion: string) {
this.featureFlagMapVersion = featureFlagMapVersion;
}
}

View File

@ -10,7 +10,7 @@ import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interface
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { WorkspaceFeatureFlagMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flag-map-cache.service.ts/workspace-feature-flag-map-cache.service';
import { WorkspaceFeatureFlagsMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.service';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspaceRolesPermissionsCacheService } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.service';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
@ -21,6 +21,7 @@ import {
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
import { PromiseMemoizer } from 'src/engine/twenty-orm/storage/promise-memoizer.storage';
import { CacheKey } from 'src/engine/twenty-orm/storage/types/cache-key.type';
import { getFromCacheWithRecompute } from 'src/engine/utils/get-data-from-cache-with-recompute.util';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
type CacheResult<T, U> = {
@ -40,7 +41,7 @@ export class WorkspaceDatasourceFactory {
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
private readonly entitySchemaFactory: EntitySchemaFactory,
private readonly workspaceRolesPermissionsCacheService: WorkspaceRolesPermissionsCacheService,
private readonly workspaceFeatureFlagMapCacheService: WorkspaceFeatureFlagMapCacheService,
private readonly workspaceFeatureFlagsMapCacheService: WorkspaceFeatureFlagsMapCacheService,
) {}
public async create(
@ -55,7 +56,9 @@ export class WorkspaceDatasourceFactory {
);
const { data: cachedFeatureFlagMap, version: cachedFeatureFlagMapVersion } =
await this.getFeatureFlagMapFromCache({ workspaceId });
await this.workspaceFeatureFlagsMapCacheService.getWorkspaceFeatureFlagsMapAndVersion(
{ workspaceId },
);
const isPermissionsV2Enabled =
cachedFeatureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
@ -201,7 +204,7 @@ export class WorkspaceDatasourceFactory {
});
}
await this.updateWorkspaceDataSourceFeatureFlagMapIfNeeded({
await this.updateWorkspaceDataSourceFeatureFlagsMapIfNeeded({
workspaceDataSource,
cachedFeatureFlagMapVersion,
cachedFeatureFlagMap,
@ -210,47 +213,6 @@ export class WorkspaceDatasourceFactory {
return workspaceDataSource;
}
private async getFromCacheWithRecompute<T, U>({
workspaceId,
getCacheData,
getCacheVersion,
recomputeCache,
cachedEntityName,
exceptionCode,
}: {
workspaceId: string;
getCacheData: (workspaceId: string) => Promise<U | undefined>;
getCacheVersion: (workspaceId: string) => Promise<T | undefined>;
recomputeCache: (params: { workspaceId: string }) => Promise<void>;
cachedEntityName: string;
exceptionCode: TwentyORMExceptionCode;
}): Promise<CacheResult<T, U>> {
let cachedVersion: T | undefined;
let cachedData: U | undefined;
cachedVersion = await getCacheVersion(workspaceId);
cachedData = await getCacheData(workspaceId);
if (!isDefined(cachedData) || !isDefined(cachedVersion)) {
await recomputeCache({ workspaceId });
cachedData = await getCacheData(workspaceId);
cachedVersion = await getCacheVersion(workspaceId);
if (!isDefined(cachedData) || !isDefined(cachedVersion)) {
throw new TwentyORMException(
`${cachedEntityName} not found after recompute for workspace ${workspaceId}`,
exceptionCode,
);
}
}
return {
version: cachedVersion,
data: cachedData,
};
}
private async getRolesPermissionsFromCache({
workspaceId,
isPermissionsV2Enabled,
@ -267,7 +229,7 @@ export class WorkspaceDatasourceFactory {
return { version: undefined, data: undefined };
}
return this.getFromCacheWithRecompute<
return getFromCacheWithRecompute<
string | undefined,
ObjectRecordsPermissionsByRoleId | undefined
>({
@ -287,28 +249,6 @@ export class WorkspaceDatasourceFactory {
});
}
private async getFeatureFlagMapFromCache({
workspaceId,
}: {
workspaceId: string;
}): Promise<CacheResult<string, FeatureFlagMap>> {
return this.getFromCacheWithRecompute<string, FeatureFlagMap>({
workspaceId,
getCacheData: () =>
this.workspaceCacheStorageService.getFeatureFlagMap(workspaceId),
getCacheVersion: () =>
this.workspaceCacheStorageService.getFeatureFlagMapVersionFromCache(
workspaceId,
),
recomputeCache: (params) =>
this.workspaceFeatureFlagMapCacheService.recomputeFeatureFlagMapCache(
params,
),
cachedEntityName: 'Feature flag map',
exceptionCode: TwentyORMExceptionCode.FEATURE_FLAG_MAP_VERSION_NOT_FOUND,
});
}
private updateWorkspaceDataSourceIfNeeded<T>({
workspaceDataSource,
currentVersion,
@ -355,7 +295,7 @@ export class WorkspaceDatasourceFactory {
});
}
private async updateWorkspaceDataSourceFeatureFlagMapIfNeeded({
private async updateWorkspaceDataSourceFeatureFlagsMapIfNeeded({
workspaceDataSource,
cachedFeatureFlagMapVersion,
cachedFeatureFlagMap,
@ -369,9 +309,9 @@ export class WorkspaceDatasourceFactory {
currentVersion: workspaceDataSource.featureFlagMapVersion,
newVersion: cachedFeatureFlagMapVersion,
newData: cachedFeatureFlagMap,
setData: (data) => workspaceDataSource.setFeatureFlagMap(data),
setData: (data) => workspaceDataSource.setFeatureFlagsMap(data),
setVersion: (version) =>
workspaceDataSource.setFeatureFlagMapVersion(version),
workspaceDataSource.setFeatureFlagsMapVersion(version),
});
}

View File

@ -6,7 +6,7 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
import { WorkspaceFeatureFlagMapCacheModule } from 'src/engine/metadata-modules/workspace-feature-flag-map-cache.service.ts/workspace-roles-feature-flag-map-cache.module';
import { WorkspaceFeatureFlagsMapCacheModule } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.module';
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
import { WorkspaceRolesPermissionsCacheModule } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.module';
import { entitySchemaFactories } from 'src/engine/twenty-orm/factories';
@ -27,7 +27,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
WorkspaceMetadataCacheModule,
PermissionsModule,
WorkspaceRolesPermissionsCacheModule,
WorkspaceFeatureFlagMapCacheModule,
WorkspaceFeatureFlagsMapCacheModule,
FeatureFlagModule,
],
providers: [