Add dataloader and read from cache for view entities (#18594)

## Context
Improve view resolution using cache and dataloader

## Performance Comparison

|Run|Main (no DataLoaders/cache)|Feature Branch (DataLoaders +
cache)|Speedup|
|---|---|---|---|
|1 (cold)|418ms|95ms|~4.4x faster|
|2|42ms|19ms|~2.2x faster|
|3|37ms|19ms|~1.9x faster|
|4|39ms|12ms|~3.2x faster|
|5|33ms|13ms|~2.5x faster|

The biggest improvement is to use dataloaders for the multiple relations
associated with views. Cache is a bit less significant since there are
other cache mechanism such as PostgreSQL buffer cache but it will
probably be more meaningful with bigger workspaces
This commit is contained in:
Weiko
2026-03-12 19:05:30 +01:00
committed by GitHub
parent a3c392ce8b
commit 1cb4c98cb3
12 changed files with 1121 additions and 737 deletions
@@ -11,6 +11,7 @@ import {
computeRecordGqlOperationFilter,
convertViewFilterValueToString,
getFilterTypeFromFieldType,
isDefined,
turnAnyFieldFilterIntoRecordGqlFilter,
} from 'twenty-shared/utils';
import { ObjectLiteral } from 'typeorm';
@@ -23,8 +24,11 @@ import {
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { GroupByDefinition } from 'src/engine/api/common/common-query-runners/types/group-by-definition.type';
import { GroupByField } from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
import { getGroupByDefinitions } from 'src/engine/api/common/common-query-runners/utils/get-group-by-definitions.util';
import { getObjectAlias } from 'src/engine/api/common/common-query-runners/utils/get-object-alias-for-group-by.util';
import { isGroupByRelationField } from 'src/engine/api/common/common-query-runners/utils/is-group-by-relation-field.util';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
import { CommonGroupByOutputItem } from 'src/engine/api/common/types/common-group-by-output-item.type';
@@ -36,10 +40,7 @@ import {
} from 'src/engine/api/common/types/common-query-args.type';
import { CommonSelectedFieldsResult } from 'src/engine/api/common/types/common-selected-fields-result.type';
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
import { GroupByDefinition } from 'src/engine/api/common/common-query-runners/types/group-by-definition.type';
import { GroupByField } from 'src/engine/api/common/common-query-runners/types/group-by-field.types';
import { formatResultWithGroupByDimensionValues } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/format-result-with-group-by-dimension-values.util';
import { isGroupByRelationField } from 'src/engine/api/common/common-query-runners/utils/is-group-by-relation-field.util';
import { parseGroupByArgs } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/parse-group-by-args.util';
import { GroupByWithRecordsService } from 'src/engine/api/graphql/graphql-query-runner/group-by/services/group-by-with-records.service';
import { getGroupLimit } from 'src/engine/api/graphql/graphql-query-runner/group-by/utils/get-group-limit.util';
@@ -53,7 +54,6 @@ import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { ViewFilterGroupService } from 'src/engine/metadata-modules/view-filter-group/services/view-filter-group.service';
import { ViewFilterService } from 'src/engine/metadata-modules/view-filter/services/view-filter.service';
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { ViewService } from 'src/engine/metadata-modules/view/services/view.service';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { formatColumnNameForRelationField } from 'src/engine/twenty-orm/utils/format-column-name-for-relation-field.util';
@@ -263,16 +263,19 @@ export class CommonGroupByQueryRunnerService extends CommonBaseQueryRunnerServic
},
});
let view: ViewEntity | null = viewFilters[0]?.view;
const viewFromFilter = viewFilters[0]?.view;
let viewAnyFieldFilterValue = viewFromFilter?.anyFieldFilterValue;
if (!view) {
view = await this.viewService.findById(args.viewId, workspaceId);
if (!isDefined(viewFromFilter)) {
const view = await this.viewService.findById(args.viewId, workspaceId);
viewAnyFieldFilterValue = view?.anyFieldFilterValue ?? null;
}
const { recordGqlOperationFilter: anyFieldFilter } =
turnAnyFieldFilterIntoRecordGqlFilter({
fields,
filterValue: view?.anyFieldFilterValue ?? '',
filterValue: viewAnyFieldFilterValue ?? '',
});
appliedFilters = combineFilters([