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

@ -338,34 +338,6 @@ export type ClientAiModelConfig = {
provider: ModelProvider;
};
export type ClientConfig = {
__typename?: 'ClientConfig';
aiModels: Array<ClientAiModelConfig>;
analyticsEnabled: Scalars['Boolean'];
api: ApiConfig;
authProviders: AuthProviders;
billing: Billing;
calendarBookingPageId?: Maybe<Scalars['String']>;
canManageFeatureFlags: Scalars['Boolean'];
captcha: Captcha;
chromeExtensionId?: Maybe<Scalars['String']>;
debugMode: Scalars['Boolean'];
defaultSubdomain?: Maybe<Scalars['String']>;
frontDomain: Scalars['String'];
isAttachmentPreviewEnabled: Scalars['Boolean'];
isConfigVariablesInDbEnabled: Scalars['Boolean'];
isEmailVerificationRequired: Scalars['Boolean'];
isGoogleCalendarEnabled: Scalars['Boolean'];
isGoogleMessagingEnabled: Scalars['Boolean'];
isMicrosoftCalendarEnabled: Scalars['Boolean'];
isMicrosoftMessagingEnabled: Scalars['Boolean'];
isMultiWorkspaceEnabled: Scalars['Boolean'];
publicFeatureFlags: Array<PublicFeatureFlag>;
sentry: Sentry;
signInPrefilled: Scalars['Boolean'];
support: Support;
};
export type ComputeStepOutputSchemaInput = {
/** Step JSON format */
step: Scalars['JSON'];
@ -1410,6 +1382,7 @@ export type Object = {
icon?: Maybe<Scalars['String']>;
id: Scalars['UUID'];
imageIdentifierFieldMetadataId?: Maybe<Scalars['String']>;
indexMetadataList: Array<Index>;
indexMetadatas: ObjectIndexMetadatasConnection;
isActive: Scalars['Boolean'];
isCustom: Scalars['Boolean'];
@ -1609,7 +1582,6 @@ export type Query = {
billingPortalSession: BillingSessionOutput;
checkUserExists: CheckUserExistOutput;
checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValid;
clientConfig: ClientConfig;
currentUser: User;
currentWorkspace: Workspace;
field: Field;
@ -2821,11 +2793,6 @@ export type GetMeteredProductsUsageQueryVariables = Exact<{ [key: string]: never
export type GetMeteredProductsUsageQuery = { __typename?: 'Query', getMeteredProductsUsage: Array<{ __typename?: 'BillingMeteredProductUsageOutput', productKey: BillingProductKey, usageQuantity: number, freeTierQuantity: number, freeTrialQuantity: number, unitPriceCents: number, totalCostCents: number }> };
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isEmailVerificationRequired: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, isAttachmentPreviewEnabled: boolean, chromeExtensionId?: string | null, canManageFeatureFlags: boolean, isMicrosoftMessagingEnabled: boolean, isMicrosoftCalendarEnabled: boolean, isGoogleMessagingEnabled: boolean, isGoogleCalendarEnabled: boolean, isConfigVariablesInDbEnabled: boolean, calendarBookingPageId?: string | null, aiModels: Array<{ __typename?: 'ClientAIModelConfig', modelId: string, label: string, provider: ModelProvider, inputCostPer1kTokensInCredits: number, outputCostPer1kTokensInCredits: number }>, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, trialPeriods: Array<{ __typename?: 'BillingTrialPeriodDTO', duration: number, isCreditCardRequired: boolean }> }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: SupportDriver, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number }, publicFeatureFlags: Array<{ __typename?: 'PublicFeatureFlag', key: FeatureFlagKey, metadata: { __typename?: 'PublicFeatureFlagMetadata', label: string, description: string, imagePath: string } }> } };
export type SearchQueryVariables = Exact<{
searchInput: Scalars['String'];
limit: Scalars['Int'];
@ -4792,106 +4759,6 @@ export function useGetMeteredProductsUsageLazyQuery(baseOptions?: Apollo.LazyQue
export type GetMeteredProductsUsageQueryHookResult = ReturnType<typeof useGetMeteredProductsUsageQuery>;
export type GetMeteredProductsUsageLazyQueryHookResult = ReturnType<typeof useGetMeteredProductsUsageLazyQuery>;
export type GetMeteredProductsUsageQueryResult = Apollo.QueryResult<GetMeteredProductsUsageQuery, GetMeteredProductsUsageQueryVariables>;
export const GetClientConfigDocument = gql`
query GetClientConfig {
clientConfig {
aiModels {
modelId
label
provider
inputCostPer1kTokensInCredits
outputCostPer1kTokensInCredits
}
billing {
isBillingEnabled
billingUrl
trialPeriods {
duration
isCreditCardRequired
}
}
authProviders {
google
password
microsoft
sso {
id
name
type
status
issuer
}
}
signInPrefilled
isMultiWorkspaceEnabled
isEmailVerificationRequired
defaultSubdomain
frontDomain
debugMode
analyticsEnabled
isAttachmentPreviewEnabled
support {
supportDriver
supportFrontChatId
}
sentry {
dsn
environment
release
}
captcha {
provider
siteKey
}
api {
mutationMaximumAffectedRecords
}
chromeExtensionId
canManageFeatureFlags
publicFeatureFlags {
key
metadata {
label
description
imagePath
}
}
isMicrosoftMessagingEnabled
isMicrosoftCalendarEnabled
isGoogleMessagingEnabled
isGoogleCalendarEnabled
isConfigVariablesInDbEnabled
calendarBookingPageId
}
}
`;
/**
* __useGetClientConfigQuery__
*
* To run a query within a React component, call `useGetClientConfigQuery` and pass it any options that fit your needs.
* When your component renders, `useGetClientConfigQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetClientConfigQuery({
* variables: {
* },
* });
*/
export function useGetClientConfigQuery(baseOptions?: Apollo.QueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
}
export function useGetClientConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
}
export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>;
export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>;
export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>;
export const SearchDocument = gql`
query Search($searchInput: String!, $limit: Int!, $after: String, $excludedObjectNameSingulars: [String!], $includedObjectNameSingulars: [String!], $filter: ObjectRecordFilterInput) {
search(