Skip to content

Add local only cache to cache service and cache typeorm entity metadata#16287

Merged
charlesBochet merged 10 commits intomainfrom
c--add-local-only-cache-to-cache-service-and-cache-typeorm-entity-metadata
Dec 3, 2025
Merged

Add local only cache to cache service and cache typeorm entity metadata#16287
charlesBochet merged 10 commits intomainfrom
c--add-local-only-cache-to-cache-service-and-cache-typeorm-entity-metadata

Conversation

@Weiko
Copy link
Copy Markdown
Member

@Weiko Weiko commented Dec 3, 2025

Problem

buildEntityMetadatas in GlobalWorkspaceOrmManager is computationally expensive and was running on every executeInWorkspaceContext call. This method uses TypeORM's EntitySchemaTransformer and EntityMetadataBuilder to build metadata for all workspace entities (30-50+ objects with many fields each).

The resulting EntityMetadata[] is not serialisable which means it cannot be cached in Redis because they contain:

  • Circular references
  • Functions/methods
  • References to the DataSource instance

Solution

Extended the workspace cache system to support local-only caching, then created a cache provider for entityMetadatas.

Implementation details

Updated @WorkspaceCache decorator (workspace-cache.decorator.ts)

  • Added localOnly?: boolean option to skip Redis storage for non-serializable data
    Created WorkspaceEntityMetadatasCacheService
  • Computes entity metadatas from DB to avoid race condition, this is acceptable
    Simplified GlobalWorkspaceOrmManager
  • Now fetches entityMetadatas from cache instead of rebuilding on every call
    Updated Workspace migration runner - the only entry point where metadata can change
  • Now invalidate the new 'entityMetadata' local cache when shouldIncrementMetadataGraphqlSchemaVersion is true (== field/object mutations)

import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';

@Injectable()
@WorkspaceCache('entityMetadatas', { localOnly: true })
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ormEntityMetadata

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Dec 3, 2025

Greptile Overview

Greptile Summary

This PR introduces local-only caching for TypeORM EntityMetadata[] to avoid expensive rebuilding on every executeInWorkspaceContext call. The implementation extends the workspace cache system with a localOnly option that skips Redis storage for non-serializable data.

Key changes:

  • Added WorkspaceEntityMetadatasCacheService to compute and cache entity metadatas from database queries
  • Extended @WorkspaceCache decorator with localOnly?: boolean option
  • Modified WorkspaceCacheService to handle local-only keys separately (no Redis hash comparison)
  • Simplified GlobalWorkspaceOrmManager by removing inline metadata building logic
  • Removed feature flag check in CommonBaseQueryRunnerService, always using global datasource
  • Improved error handling in UserRoleService.getRoleIdForUserWorkspace to throw when no role is found

Critical Issue:
The PR description states that the migration runner should invalidate entityMetadatas when metadata changes (field/object mutations), but workspace-migration-runner-v2.service.ts was not modified. This will cause stale entity metadata to be served after schema changes until cache naturally expires or server restarts.

Confidence Score: 2/5

  • This PR has a critical cache invalidation bug that will cause stale metadata after schema changes.
  • The local-only caching implementation is well-designed, but the missing cache invalidation in the migration runner is a critical issue. When fields or objects are created/updated/deleted, the entityMetadatas cache won't be invalidated, leading to stale data being served until natural cache expiry or server restart.
  • workspace-entity-metadatas-cache.service.ts - missing integration with migration runner for cache invalidation

Important Files Changed

File Analysis

Filename Score Overview
packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/workspace-entity-metadatas-cache.service.ts 3/5 New service for caching entity metadatas with local-only caching. Missing cache invalidation integration with migration runner.
packages/twenty-server/src/engine/workspace-cache/services/workspace-cache.service.ts 4/5 Extended cache service to support local-only caching by skipping Redis storage for non-serializable data.
packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager.ts 4/5 Simplified to use cached entityMetadatas instead of rebuilding on every call. Removed buildEntitySchemas and buildEntityMetadatas methods.
packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts 4/5 Removed feature flag check and always uses global datasource. Simplified role retrieval logic by extracting to getRoleIdOrThrow.

Sequence Diagram

sequenceDiagram
    participant Client
    participant CommonBaseQueryRunner
    participant GlobalWorkspaceOrmManager
    participant WorkspaceCacheService
    participant WorkspaceEntityMetadatasCacheService
    participant LocalCache
    participant Database

    Client->>CommonBaseQueryRunner: execute()
    CommonBaseQueryRunner->>GlobalWorkspaceOrmManager: executeInWorkspaceContext()
    GlobalWorkspaceOrmManager->>WorkspaceCacheService: getOrRecompute(workspaceId, [..., 'entityMetadatas'])
    
    alt entityMetadatas in LocalCache
        WorkspaceCacheService->>LocalCache: get(entityMetadatas)
        LocalCache-->>WorkspaceCacheService: cached EntityMetadata[]
    else entityMetadatas not in LocalCache
        WorkspaceCacheService->>WorkspaceEntityMetadatasCacheService: computeForCache(workspaceId)
        WorkspaceEntityMetadatasCacheService->>Database: find ObjectMetadata, FieldMetadata
        Database-->>WorkspaceEntityMetadatasCacheService: raw entities
        WorkspaceEntityMetadatasCacheService->>WorkspaceEntityMetadatasCacheService: buildFlatMaps()
        WorkspaceEntityMetadatasCacheService->>WorkspaceEntityMetadatasCacheService: buildEntityMetadatas()
        WorkspaceEntityMetadatasCacheService-->>WorkspaceCacheService: EntityMetadata[]
        WorkspaceCacheService->>LocalCache: set(entityMetadatas) [localOnly - no Redis]
    end
    
    WorkspaceCacheService-->>GlobalWorkspaceOrmManager: cached data
    GlobalWorkspaceOrmManager-->>CommonBaseQueryRunner: ORMWorkspaceContext
    CommonBaseQueryRunner-->>Client: query results
Loading

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Dec 3, 2025

🚀 Preview Environment Ready!

Your preview environment is available at: http://bore.pub:3166

This environment will automatically shut down when the PR is closed or after 5 hours.

@charlesBochet charlesBochet merged commit 3d95d6c into main Dec 3, 2025
44 of 45 checks passed
@charlesBochet charlesBochet deleted the c--add-local-only-cache-to-cache-service-and-cache-typeorm-entity-metadata branch December 3, 2025 18:50
@twenty-eng-sync
Copy link
Copy Markdown

Hey @Weiko! After you've done the QA of your Pull Request, you can mark it as done here. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants