Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 9f92b84db9 View field update triggers excessive cache invalidation cascade (4.6s latency)
https://sonarly.com/issue/10039?type=bug

A simple `UpdateCoreViewField` mutation (toggling column visibility) takes ~4.6 seconds because the workspace migration runner over-invalidates caches, triggering full-table reloads for 5+ metadata types plus a cascading legacy invalidation of 6 more caches.

Fix: ## Root Cause

The `workspace-migration-runner.service.ts` computed a wide set of cache keys (`allFlatEntityMapsKeys`) that includes **all related/validation metadata types** for an action, then used that same wide set for **post-mutation cache invalidation**. For a `viewField` update this expanded to `['flatViewFieldMaps', 'flatViewMaps', 'flatFieldMetadataMaps', 'flatObjectMetadataMaps', 'flatViewFieldGroupMaps']`, causing 5 full-table reloads instead of 1. Worse, the presence of `flatFieldMetadataMaps` triggered the `shouldIncrementMetadataGraphqlSchemaVersion` branch, which further recomputed 6 additional caches and incremented the metadata version (causing all clients to refetch the entire schema — completely unnecessary for a view field property change).

## Fix

Compute a separate, **narrow** set of cache keys for post-mutation invalidation — only the directly-mutated entity types (`actionMetadataNames`), not all their related/validation metadata:

```typescript file=packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts lines=183-188
// Only invalidate caches for the directly mutated entity types, not all
// related/validation types. This prevents unnecessary full-table reloads
// and the legacy metadata version increment cascade for e.g. view field updates.
const mutatedFlatEntityMapsKeys = actionMetadataNames.map(
  getMetadataFlatEntityMapsKey,
);
```

Then pass `mutatedFlatEntityMapsKeys` to `invalidateCache` instead of `allFlatEntityMapsKeys`:

```typescript file=packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts lines=255-258
await this.invalidateCache({
  allFlatEntityMapsKeys: mutatedFlatEntityMapsKeys,
  workspaceId,
});
```

The `allFlatEntityMapsKeys` (wide set) is still used for the **initial cache retrieval** where all related types are needed for validation — only the post-mutation invalidation step is narrowed.

### Effect for a `viewField` update

| Before | After |
|--------|-------|
| 5 caches flushed + recomputed | 1 cache flushed + recomputed (`flatViewFieldMaps`) |
| `shouldIncrementMetadataGraphqlSchemaVersion = true` | `shouldIncrementMetadataGraphqlSchemaVersion = false` |
| 6 additional caches recomputed (roles, permissions, ORM) | No legacy cascade |
| Metadata version incremented (clients refetch schema) | No metadata version change |
| ~4,580ms total | ~few ms total |
2026-03-05 21:29:26 +00:00
@@ -180,6 +180,12 @@ export class WorkspaceMigrationRunnerService {
const allFlatEntityMapsKeys = actionsMetadataAndRelatedMetadataNames.map(
getMetadataFlatEntityMapsKey,
);
// Only invalidate caches for the directly mutated entity types, not all
// related/validation types. This prevents unnecessary full-table reloads
// and the legacy metadata version increment cascade for e.g. view field updates.
const mutatedFlatEntityMapsKeys = actionMetadataNames.map(
getMetadataFlatEntityMapsKey,
);
let allFlatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps<
@@ -247,7 +253,7 @@ export class WorkspaceMigrationRunnerService {
this.logger.timeEnd('Runner', 'Transaction execution');
await this.invalidateCache({
allFlatEntityMapsKeys,
allFlatEntityMapsKeys: mutatedFlatEntityMapsKeys,
workspaceId,
});