Improve performance on metadata computation (#12785)

In this PR:

## Improve recompute metadata cache performance. We are aiming for
~100ms

Deleting relationMetadata table and FKs pointing on it
Fetching indexMetadata and indexFieldMetadata in a separate query as
typeorm is suboptimizing

## Remove caching lock

As recomputing the metadata cache is lighter, we try to stop preventing
multiple concurrent computations. This also simplifies interfaces

## Introduce self recovery mecanisms to recompute cache automatically if
corrupted

Aka getFreshObjectMetadataMaps

## custom object resolver performance improvement:  1sec to 200ms

Double check queries and indexes used while creating a custom object
Remove the queries to db to use the cached objectMetadataMap

## reduce objectMetadataMaps to 500kb
<img width="222" alt="image"
src="https://github.com/user-attachments/assets/2370dc80-49b6-4b63-8d5e-30c5ebdaa062"
/>

We used to stored 3 fieldMetadataMaps (byId, byName, byJoinColumnName).
While this is great for devXP, this is not great for performances.
Using the same mecanisme as for objectMetadataMap: we only keep byIdMap
and introduce two otherMaps to idByName, idByJoinColumnName to make the
bridge

## Add dataloader on IndexMetadata (aka indexMetadataList in the API)

## Improve field resolver performances too

## Deprecate ClientConfig
This commit is contained in:
Charles Bochet
2025-06-23 21:06:17 +02:00
committed by GitHub
parent 6aee42ab22
commit d5c974054d
145 changed files with 1485 additions and 2245 deletions

View File

@ -153,6 +153,7 @@ export class StandardFieldFactory {
isActive: workspaceFieldMetadataArgs.isActive ?? true,
asExpression: workspaceFieldMetadataArgs.asExpression,
generatedType: workspaceFieldMetadataArgs.generatedType,
isLabelSyncedWithName: true,
},
];
}
@ -191,6 +192,7 @@ export class StandardFieldFactory {
isNullable: true,
isUnique: false,
isActive: workspaceRelationMetadataArgs.isActive ?? true,
isLabelSyncedWithName: true,
});
return fieldMetadataCollection;

View File

@ -3,7 +3,10 @@ import { Injectable, Logger } from '@nestjs/common';
import { PartialIndexMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-index-metadata.interface';
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import {
IndexMetadataEntity,
IndexType,
} from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { generateDeterministicIndexName } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
@ -88,7 +91,7 @@ export class StandardIndexFactory {
isUnique: workspaceIndexMetadataArgs.isUnique,
isCustom: false,
indexWhereClause: workspaceIndexMetadataArgs.whereClause,
indexType: workspaceIndexMetadataArgs.type,
indexType: workspaceIndexMetadataArgs.type ?? IndexType.BTREE,
};
return indexMetadata;
@ -129,7 +132,7 @@ export class StandardIndexFactory {
columns: workspaceIndexMetadataArgs.columns,
isCustom: false,
isUnique: workspaceIndexMetadataArgs.isUnique,
indexType: workspaceIndexMetadataArgs.type,
indexType: workspaceIndexMetadataArgs.type ?? IndexType.BTREE,
indexWhereClause: workspaceIndexMetadataArgs.whereClause,
};

View File

@ -21,7 +21,9 @@ export class StandardObjectFactory {
private createObjectMetadata(
target: typeof BaseWorkspaceEntity,
context: WorkspaceSyncContext,
): Omit<PartialWorkspaceEntity, 'fields' | 'indexMetadatas'> | undefined {
):
| Omit<PartialWorkspaceEntity, 'fields' | 'indexMetadatas' | 'icon'>
| undefined {
const workspaceEntityMetadataArgs =
metadataArgsStorage.filterEntities(target);

View File

@ -9,7 +9,12 @@ export type PartialFieldMetadata<
T extends FieldMetadataType = FieldMetadataType,
> = Omit<
FieldMetadataInterface<T>,
'id' | 'label' | 'description' | 'objectMetadataId'
| 'id'
| 'label'
| 'description'
| 'objectMetadataId'
| 'createdAt'
| 'updatedAt'
> & {
standardId: string;
label: string | ((objectMetadata: ObjectMetadataEntity) => string);

View File

@ -16,8 +16,11 @@ export const computeStandardFields = (
)[],
originalObjectMetadata: ObjectMetadataEntity,
customObjectMetadataCollection: ObjectMetadataEntity[] = [],
): ComputedPartialFieldMetadata[] => {
const fields: ComputedPartialFieldMetadata[] = [];
): Omit<ComputedPartialFieldMetadata, 'createdAt' | 'updatedAt'>[] => {
const fields: Omit<
ComputedPartialFieldMetadata,
'createdAt' | 'updatedAt'
>[] = [];
for (const partialFieldMetadata of standardFieldMetadataCollection) {
// Relation from standard object to custom object
@ -43,6 +46,8 @@ export const computeStandardFields = (
...rest,
standardId: relationStandardId,
defaultValue: null,
isNullable: true,
isLabelSyncedWithName: true,
});
}
} else {