Add fail on metadata cache miss (#7118)

- avoid failing when missing cache (used for command)
- remove unused load cache function. Cache will be always re-created
when trying to fetch if not existing
This commit is contained in:
Thomas Trompette
2024-09-18 15:40:24 +02:00
committed by GitHub
parent 44587b4908
commit 741a969cc1
5 changed files with 92 additions and 74 deletions

View File

@ -47,9 +47,9 @@ export class WorkspaceSchemaFactory {
); );
if (currentCacheVersion === undefined) { if (currentCacheVersion === undefined) {
await this.workspaceMetadataCacheService.recomputeMetadataCache( await this.workspaceMetadataCacheService.recomputeMetadataCache({
authContext.workspace.id, workspaceId: authContext.workspace.id,
); });
throw new GraphqlQueryRunnerException( throw new GraphqlQueryRunnerException(
'Metadata cache version not found', 'Metadata cache version not found',
GraphqlQueryRunnerExceptionCode.METADATA_CACHE_VERSION_NOT_FOUND, GraphqlQueryRunnerExceptionCode.METADATA_CACHE_VERSION_NOT_FOUND,
@ -63,9 +63,9 @@ export class WorkspaceSchemaFactory {
); );
if (!objectMetadataMap) { if (!objectMetadataMap) {
await this.workspaceMetadataCacheService.recomputeMetadataCache( await this.workspaceMetadataCacheService.recomputeMetadataCache({
authContext.workspace.id, workspaceId: authContext.workspace.id,
); });
throw new GraphqlQueryRunnerException( throw new GraphqlQueryRunnerException(
'Object metadata collection not found', 'Object metadata collection not found',
GraphqlQueryRunnerExceptionCode.METADATA_CACHE_VERSION_NOT_FOUND, GraphqlQueryRunnerExceptionCode.METADATA_CACHE_VERSION_NOT_FOUND,

View File

@ -26,10 +26,13 @@ export class WorkspaceMetadataCacheService {
) {} ) {}
@LogExecutionTime() @LogExecutionTime()
async recomputeMetadataCache( async recomputeMetadataCache({
workspaceId: string, workspaceId,
force = false, ignoreLock = false,
): Promise<void> { }: {
workspaceId: string;
ignoreLock?: boolean;
}): Promise<void> {
const currentCacheVersion = const currentCacheVersion =
await this.getMetadataVersionFromCache(workspaceId); await this.getMetadataVersionFromCache(workspaceId);
@ -43,17 +46,13 @@ export class WorkspaceMetadataCacheService {
); );
} }
if (!force && currentCacheVersion === currentDatabaseVersion) {
return;
}
const isAlreadyCaching = const isAlreadyCaching =
await this.workspaceCacheStorageService.getObjectMetadataOngoingCachingLock( await this.workspaceCacheStorageService.getObjectMetadataOngoingCachingLock(
workspaceId, workspaceId,
currentDatabaseVersion, currentDatabaseVersion,
); );
if (isAlreadyCaching) { if (!ignoreLock && isAlreadyCaching) {
return; return;
} }

View File

@ -45,8 +45,8 @@ export class WorkspaceMetadataVersionService {
{ metadataVersion: newMetadataVersion }, { metadataVersion: newMetadataVersion },
); );
await this.workspaceMetadataCacheService.recomputeMetadataCache( await this.workspaceMetadataCacheService.recomputeMetadataCache({
workspaceId, workspaceId,
); });
} }
} }

View File

@ -4,6 +4,7 @@ import { EntitySchema } from 'typeorm';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource'; import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { import {
@ -33,14 +34,27 @@ export class WorkspaceDatasourceFactory {
public async create( public async create(
workspaceId: string, workspaceId: string,
workspaceMetadataVersion: number | null, workspaceMetadataVersion: number | null,
failOnMetadataCacheMiss = true,
): Promise<WorkspaceDataSource> { ): Promise<WorkspaceDataSource> {
const desiredWorkspaceMetadataVersion = const {
await this.computeDesiredWorkspaceMetadataVersion( metadataVersion: cachedWorkspaceMetadataVersion,
workspaceId, objectMetadataMap: cachedObjectMetadataMap,
workspaceMetadataVersion, } = await this.getWorkspaceMetadataFromCache(
); workspaceId,
failOnMetadataCacheMiss,
);
const cacheKey = `${workspaceId}-${desiredWorkspaceMetadataVersion}`; if (
workspaceMetadataVersion !== null &&
cachedWorkspaceMetadataVersion !== workspaceMetadataVersion
) {
throw new TwentyORMException(
`Workspace metadata version mismatch detected for workspace ${workspaceId}. Current version: ${cachedWorkspaceMetadataVersion}. Desired version: ${workspaceMetadataVersion}`,
TwentyORMExceptionCode.METADATA_VERSION_MISMATCH,
);
}
const cacheKey = `${workspaceId}-${cachedWorkspaceMetadataVersion}`;
if (cacheKey in this.cachedDatasourcePromise) { if (cacheKey in this.cachedDatasourcePromise) {
return this.cachedDatasourcePromise[cacheKey]; return this.cachedDatasourcePromise[cacheKey];
@ -52,25 +66,8 @@ export class WorkspaceDatasourceFactory {
cacheKey as '`${string}-${string}`', cacheKey as '`${string}-${string}`',
async () => { async () => {
this.logger.log( this.logger.log(
`Creating workspace data source for workspace ${workspaceId} and metadata version ${desiredWorkspaceMetadataVersion}`, `Creating workspace data source for workspace ${workspaceId} and metadata version ${cachedWorkspaceMetadataVersion}`,
); );
const cachedObjectMetadataMap =
await this.workspaceCacheStorageService.getObjectMetadataMap(
workspaceId,
desiredWorkspaceMetadataVersion,
);
if (!cachedObjectMetadataMap) {
await this.workspaceMetadataCacheService.recomputeMetadataCache(
workspaceId,
true,
);
throw new TwentyORMException(
`Object metadata map not found for workspace ${workspaceId}`,
TwentyORMExceptionCode.METADATA_COLLECTION_NOT_FOUND,
);
}
const dataSourceMetadata = const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId( await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId(
@ -87,7 +84,7 @@ export class WorkspaceDatasourceFactory {
const cachedEntitySchemaOptions = const cachedEntitySchemaOptions =
await this.workspaceCacheStorageService.getORMEntitySchema( await this.workspaceCacheStorageService.getORMEntitySchema(
workspaceId, workspaceId,
desiredWorkspaceMetadataVersion, cachedWorkspaceMetadataVersion,
); );
let cachedEntitySchemas: EntitySchema[]; let cachedEntitySchemas: EntitySchema[];
@ -101,7 +98,7 @@ export class WorkspaceDatasourceFactory {
Object.values(cachedObjectMetadataMap).map((objectMetadata) => Object.values(cachedObjectMetadataMap).map((objectMetadata) =>
this.entitySchemaFactory.create( this.entitySchemaFactory.create(
workspaceId, workspaceId,
desiredWorkspaceMetadataVersion, cachedWorkspaceMetadataVersion,
objectMetadata, objectMetadata,
cachedObjectMetadataMap, cachedObjectMetadataMap,
), ),
@ -110,7 +107,7 @@ export class WorkspaceDatasourceFactory {
await this.workspaceCacheStorageService.setORMEntitySchema( await this.workspaceCacheStorageService.setORMEntitySchema(
workspaceId, workspaceId,
desiredWorkspaceMetadataVersion, cachedWorkspaceMetadataVersion,
entitySchemas.map((entitySchema) => entitySchema.options), entitySchemas.map((entitySchema) => entitySchema.options),
); );
@ -175,45 +172,67 @@ export class WorkspaceDatasourceFactory {
return creationPromise; return creationPromise;
} }
private async computeDesiredWorkspaceMetadataVersion( public async destroy(workspaceId: string): Promise<void> {
const cachedWorkspaceMetadataVersion =
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
await this.cacheManager.clearKey(
`${workspaceId}-${cachedWorkspaceMetadataVersion}`,
);
}
private async getWorkspaceMetadataFromCache(
workspaceId: string, workspaceId: string,
workspaceMetadataVersion: number | null, failOnMetadataCacheMiss = true,
): Promise<number> { ): Promise<{
const latestWorkspaceMetadataVersion = metadataVersion: number;
objectMetadataMap: ObjectMetadataMap;
}> {
let latestWorkspaceMetadataVersion =
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId); await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
if (latestWorkspaceMetadataVersion === undefined) { if (latestWorkspaceMetadataVersion === undefined) {
await this.workspaceMetadataCacheService.recomputeMetadataCache( await this.workspaceMetadataCacheService.recomputeMetadataCache({
workspaceId, workspaceId,
); ignoreLock: !failOnMetadataCacheMiss,
});
if (failOnMetadataCacheMiss) {
throw new TwentyORMException(
`Metadata version not found for workspace ${workspaceId}`,
TwentyORMExceptionCode.METADATA_VERSION_NOT_FOUND,
);
} else {
latestWorkspaceMetadataVersion =
await this.workspaceCacheStorageService.getMetadataVersion(
workspaceId,
);
}
}
if (!latestWorkspaceMetadataVersion) {
throw new TwentyORMException( throw new TwentyORMException(
`Metadata version not found for workspace ${workspaceId}`, `Metadata version not found after recompute for workspace ${workspaceId}`,
TwentyORMExceptionCode.METADATA_VERSION_NOT_FOUND, TwentyORMExceptionCode.METADATA_VERSION_NOT_FOUND,
); );
} }
const desiredWorkspaceMetadataVersion = const objectMetadataMap =
workspaceMetadataVersion ?? latestWorkspaceMetadataVersion; await this.workspaceCacheStorageService.getObjectMetadataMap(
workspaceId,
latestWorkspaceMetadataVersion,
);
if (latestWorkspaceMetadataVersion !== desiredWorkspaceMetadataVersion) { if (!objectMetadataMap) {
throw new TwentyORMException( throw new TwentyORMException(
`Workspace metadata version mismatch detected for workspace ${workspaceId}. Current version: ${latestWorkspaceMetadataVersion}. Desired version: ${desiredWorkspaceMetadataVersion}`, `Object metadata map not found for workspace ${workspaceId}`,
TwentyORMExceptionCode.METADATA_VERSION_MISMATCH, TwentyORMExceptionCode.METADATA_COLLECTION_NOT_FOUND,
); );
} }
return desiredWorkspaceMetadataVersion; return {
} metadataVersion: latestWorkspaceMetadataVersion,
objectMetadataMap,
public async destroy( };
workspaceId: string,
metadataVersion: number | null,
): Promise<void> {
const desiredWorkspaceMetadataVersion =
this.computeDesiredWorkspaceMetadataVersion(workspaceId, metadataVersion);
await this.cacheManager.clearKey(
`${workspaceId}-${desiredWorkspaceMetadataVersion}`,
);
} }
} }

View File

@ -15,16 +15,19 @@ export class TwentyORMGlobalManager {
async getRepositoryForWorkspace<T extends ObjectLiteral>( async getRepositoryForWorkspace<T extends ObjectLiteral>(
workspaceId: string, workspaceId: string,
workspaceEntity: Type<T>, workspaceEntity: Type<T>,
failOnMetadataCacheMiss?: boolean,
): Promise<WorkspaceRepository<T>>; ): Promise<WorkspaceRepository<T>>;
async getRepositoryForWorkspace<T extends ObjectLiteral>( async getRepositoryForWorkspace<T extends ObjectLiteral>(
workspaceId: string, workspaceId: string,
objectMetadataName: string, objectMetadataName: string,
failOnMetadataCacheMiss?: boolean,
): Promise<WorkspaceRepository<T>>; ): Promise<WorkspaceRepository<T>>;
async getRepositoryForWorkspace<T extends ObjectLiteral>( async getRepositoryForWorkspace<T extends ObjectLiteral>(
workspaceId: string, workspaceId: string,
workspaceEntityOrobjectMetadataName: Type<T> | string, workspaceEntityOrobjectMetadataName: Type<T> | string,
failOnMetadataCacheMiss = true,
): Promise<WorkspaceRepository<T>> { ): Promise<WorkspaceRepository<T>> {
let objectMetadataName: string; let objectMetadataName: string;
@ -39,6 +42,7 @@ export class TwentyORMGlobalManager {
const workspaceDataSource = await this.workspaceDataSourceFactory.create( const workspaceDataSource = await this.workspaceDataSourceFactory.create(
workspaceId, workspaceId,
null, null,
failOnMetadataCacheMiss,
); );
const repository = workspaceDataSource.getRepository<T>(objectMetadataName); const repository = workspaceDataSource.getRepository<T>(objectMetadataName);
@ -50,11 +54,7 @@ export class TwentyORMGlobalManager {
return await this.workspaceDataSourceFactory.create(workspaceId, null); return await this.workspaceDataSourceFactory.create(workspaceId, null);
} }
async loadDataSourceForWorkspace(workspaceId: string) {
await this.workspaceDataSourceFactory.create(workspaceId, null);
}
async destroyDataSourceForWorkspace(workspaceId: string) { async destroyDataSourceForWorkspace(workspaceId: string) {
await this.workspaceDataSourceFactory.destroy(workspaceId, null); await this.workspaceDataSourceFactory.destroy(workspaceId);
} }
} }