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

@ -1,47 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
@Injectable()
export class WorkspaceFeatureFlagMapCacheService {
logger = new Logger(WorkspaceFeatureFlagMapCacheService.name);
constructor(
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly featureFlagService: FeatureFlagService,
) {}
async recomputeFeatureFlagMapCache({
workspaceId,
ignoreLock = false,
}: {
workspaceId: string;
ignoreLock?: boolean;
}): Promise<void> {
const isAlreadyCaching =
await this.workspaceCacheStorageService.getFeatureFlagMapOngoingCachingLock(
workspaceId,
);
if (!ignoreLock && isAlreadyCaching) {
return;
}
await this.workspaceCacheStorageService.addFeatureFlagMapOngoingCachingLock(
workspaceId,
);
const freshFeatureFlagMap =
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
await this.workspaceCacheStorageService.setFeatureFlagMap(
workspaceId,
freshFeatureFlagMap,
);
await this.workspaceCacheStorageService.removeFeatureFlagMapOngoingCachingLock(
workspaceId,
);
}
}

View File

@ -1,18 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceFeatureFlagMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flag-map-cache.service.ts/workspace-feature-flag-map-cache.service';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace], 'core'),
WorkspaceCacheStorageModule,
FeatureFlagModule,
],
providers: [WorkspaceFeatureFlagMapCacheService],
exports: [WorkspaceFeatureFlagMapCacheService],
})
export class WorkspaceFeatureFlagMapCacheModule {}

View File

@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceFeatureFlagsMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.service';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'),
WorkspaceCacheStorageModule,
],
providers: [WorkspaceFeatureFlagsMapCacheService],
exports: [WorkspaceFeatureFlagsMapCacheService],
})
export class WorkspaceFeatureFlagsMapCacheModule {}

View File

@ -0,0 +1,102 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { TwentyORMExceptionCode } from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
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';
@Injectable()
export class WorkspaceFeatureFlagsMapCacheService {
logger = new Logger(WorkspaceFeatureFlagsMapCacheService.name);
constructor(
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
@InjectRepository(FeatureFlag, 'core')
private readonly featureFlagRepository: Repository<FeatureFlag>,
) {}
async getWorkspaceFeatureFlagsMap({
workspaceId,
}: {
workspaceId: string;
}): Promise<FeatureFlagMap> {
const { data: workspaceFeatureFlagsMap } =
await this.getWorkspaceFeatureFlagsMapAndVersion({ workspaceId });
return workspaceFeatureFlagsMap;
}
async getWorkspaceFeatureFlagsMapAndVersion({
workspaceId,
}: {
workspaceId: string;
}) {
return getFromCacheWithRecompute<string, FeatureFlagMap>({
workspaceId,
getCacheData: () =>
this.workspaceCacheStorageService.getFeatureFlagsMap(workspaceId),
getCacheVersion: () =>
this.workspaceCacheStorageService.getFeatureFlagsMapVersionFromCache(
workspaceId,
),
recomputeCache: (params) => this.recomputeFeatureFlagsMapCache(params),
cachedEntityName: 'Feature flag map',
exceptionCode: TwentyORMExceptionCode.FEATURE_FLAG_MAP_VERSION_NOT_FOUND,
});
}
async recomputeFeatureFlagsMapCache({
workspaceId,
ignoreLock = false,
}: {
workspaceId: string;
ignoreLock?: boolean;
}): Promise<void> {
const isAlreadyCaching =
await this.workspaceCacheStorageService.getFeatureFlagsMapOngoingCachingLock(
workspaceId,
);
if (!ignoreLock && isAlreadyCaching) {
return;
}
await this.workspaceCacheStorageService.addFeatureFlagMapOngoingCachingLock(
workspaceId,
);
const freshFeatureFlagMap =
await this.getFeatureFlagsMapFromDatabase(workspaceId);
await this.workspaceCacheStorageService.setFeatureFlagsMap(
workspaceId,
freshFeatureFlagMap,
);
await this.workspaceCacheStorageService.removeFeatureFlagsMapOngoingCachingLock(
workspaceId,
);
}
private async getFeatureFlagsMapFromDatabase(workspaceId: string) {
const workspaceFeatureFlags = await this.featureFlagRepository.find({
where: { workspaceId },
});
const workspaceFeatureFlagsMap = workspaceFeatureFlags.reduce(
(result, currentFeatureFlag) => {
result[currentFeatureFlag.key] = currentFeatureFlag.value;
return result;
},
{} as FeatureFlagMap,
);
return workspaceFeatureFlagsMap;
}
}