Add TTL eviction to local data cache (#16510)
We keep different versions of our cache to avoid race conditions but we never evict stale data. This PR should fix that
This commit is contained in:
-1
@@ -3,7 +3,6 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { type PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Omit } from 'zod/v4/core/util.cjs';
|
||||
|
||||
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
|
||||
import { QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
|
||||
|
||||
+10
-6
@@ -11,7 +11,7 @@ import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/wo
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
|
||||
import { GlobalWorkspaceDataSourceService } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildEntitySchemaMetadataMaps } from 'src/engine/twenty-orm/global-workspace-datasource/types/entity-schema-metadata.type';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
|
||||
@@ -26,7 +26,7 @@ export class WorkspaceORMEntityMetadatasCacheService extends WorkspaceCacheProvi
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
private readonly entitySchemaFactory: EntitySchemaFactory,
|
||||
private readonly globalWorkspaceDataSourceService: GlobalWorkspaceDataSourceService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -57,17 +57,21 @@ export class WorkspaceORMEntityMetadatasCacheService extends WorkspaceCacheProvi
|
||||
),
|
||||
);
|
||||
|
||||
return this.buildEntityMetadatas(entitySchemas);
|
||||
const entityMetadatas = await this.buildEntityMetadatas(entitySchemas);
|
||||
|
||||
return entityMetadatas;
|
||||
}
|
||||
|
||||
private buildEntityMetadatas(
|
||||
private async buildEntityMetadatas(
|
||||
entitySchemas: EntitySchema[],
|
||||
): EntityMetadata[] {
|
||||
): Promise<EntityMetadata[]> {
|
||||
const transformer = new EntitySchemaTransformer();
|
||||
const metadataArgsStorage = transformer.transform(entitySchemas);
|
||||
|
||||
const dataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
const entityMetadataBuilder = new EntityMetadataBuilder(
|
||||
this.globalWorkspaceDataSourceService.getGlobalWorkspaceDataSource(),
|
||||
dataSource,
|
||||
metadataArgsStorage,
|
||||
);
|
||||
|
||||
|
||||
+29
-4
@@ -28,10 +28,11 @@ import {
|
||||
} from 'src/engine/workspace-cache/types/workspace-cache-key.type';
|
||||
import { type WorkspaceLocalCacheEntry } from 'src/engine/workspace-cache/types/workspace-local-cache-entry.type';
|
||||
|
||||
const LOCAL_TTL_MS = 100;
|
||||
const MEMOIZER_TTL_MS = 10_000;
|
||||
const STALE_VERSION_TTL_MS = 5_000;
|
||||
const MAX_LOCAL_STALE_VERSIONS = 5;
|
||||
const LOCAL_TTL_MS = 100; // 100ms
|
||||
const LOCAL_ENTRY_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
||||
const MEMOIZER_TTL_MS = 10_000; // 10 seconds
|
||||
const STALE_VERSION_TTL_MS = 5_000; // 5 seconds
|
||||
const MAX_LOCAL_STALE_VERSIONS = 5; // 5 stale versions
|
||||
|
||||
type CacheDataType = WorkspaceCacheDataMap[WorkspaceCacheKeyName];
|
||||
|
||||
@@ -97,6 +98,8 @@ export class WorkspaceCacheService implements OnModuleInit {
|
||||
workspaceId: string,
|
||||
cacheKeyNames: K,
|
||||
): Promise<WorkspaceCacheResult<K>> {
|
||||
this.evictExpiredLocalEntries();
|
||||
|
||||
if (
|
||||
!isDefined(workspaceId) ||
|
||||
cacheKeyNames.length === 0 ||
|
||||
@@ -422,6 +425,28 @@ export class WorkspaceCacheService implements OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
private evictExpiredLocalEntries(): void {
|
||||
const now = Date.now();
|
||||
|
||||
for (const [localKey, entry] of this.localCache) {
|
||||
for (const [hash, version] of entry.versions) {
|
||||
if (now - version.lastReadAt > LOCAL_ENTRY_TTL_MS) {
|
||||
entry.versions.delete(hash);
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.versions.size === 0) {
|
||||
this.localCache.delete(localKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entry.versions.has(entry.latestHash)) {
|
||||
// Latest was evicted; drop the entire entry to avoid serving stale data.
|
||||
this.localCache.delete(localKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getProviderOrThrow(
|
||||
keyName: WorkspaceCacheKeyName,
|
||||
): WorkspaceCacheProvider<CacheDataType> {
|
||||
|
||||
Reference in New Issue
Block a user