Skip to content

Commit 9a95cd0

Browse files
Fix applications query cartesian product causing read timeouts (#19892)
## Summary Same fix pattern as #19511 (`rolesPermissions` cartesian product). The `Settings > Applications` page was hitting query read timeouts in production. The offending SQL came from `ApplicationService.findManyApplications` / `findOneApplication`, which loaded **5 `OneToMany` children** in a single query via TypeORM `relations`: ``` logicFunctions × agents × frontComponents × objects × applicationVariables ``` Postgres returns the Cartesian product of all five — e.g. 20 logic functions × 5 agents × 30 front components × 100 objects × 10 variables = **3M rows for ~165 distinct records**, which trivially exceeds the read timeout. ## Changes - **`findManyApplications`** — dropped all `OneToMany` relations. The frontend `FIND_MANY_APPLICATIONS` query only selects scalar fields and the `applicationRegistration` ManyToOne, so joining the children was pure waste at the list level. - **`findOneApplication`** — kept the cheap `ManyToOne` / `OneToOne` joins (`packageJsonFile`, `yarnLockFile`, `applicationRegistration`) on the main query and fetched the 5 `OneToMany` children in parallel via `Promise.all`, reattaching them on the entity. Same shape as `WorkspaceRolesPermissionsCacheService.computeForCache` after #19511. - **`application.module.ts`** — registered the 5 child entity repositories via `TypeOrmModule.forFeature`. The other internal caller (`front-component.service.ts → findOneApplicationOrThrow`) only reads `application.universalIdentifier`, so the extra parallel single-key lookups remain far cheaper than the previous 8-way join with row explosion.
1 parent 27e1caf commit 9a95cd0

2 files changed

Lines changed: 68 additions & 22 deletions

File tree

packages/twenty-server/src/engine/core-modules/application/application.module.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,28 @@ import { TypeOrmModule } from '@nestjs/typeorm';
44
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
55
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
66
import { WorkspaceFlatApplicationMapCacheService } from 'src/engine/core-modules/application/workspace-flat-application-map-cache.service';
7+
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
78
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
89
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
910
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
11+
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
1012
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
13+
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
14+
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
15+
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
1116
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
1217

1318
@Module({
1419
imports: [
15-
TypeOrmModule.forFeature([ApplicationEntity, WorkspaceEntity]),
20+
TypeOrmModule.forFeature([
21+
ApplicationEntity,
22+
WorkspaceEntity,
23+
LogicFunctionEntity,
24+
AgentEntity,
25+
FrontComponentEntity,
26+
ObjectMetadataEntity,
27+
ApplicationVariableEntity,
28+
]),
1629
WorkspaceManyOrAllFlatEntityMapsCacheModule,
1730
WorkspaceCacheModule,
1831
TwentyConfigModule,

packages/twenty-server/src/engine/core-modules/application/application.service.ts

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@ import {
1212
} from 'src/engine/core-modules/application/application.exception';
1313
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/application-package/utils/get-default-application-package-fields.util';
1414
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/application-package/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
15+
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
1516
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
1617
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
18+
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
1719
import { ALL_FLAT_ENTITY_MAPS_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-flat-entity-maps-properties.constant';
20+
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
21+
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
1822
import { logicFunctionCreateHash } from 'src/engine/metadata-modules/logic-function/utils/logic-function-create-hash.utils';
23+
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
1924
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
2025
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
2126

@@ -28,6 +33,16 @@ export class ApplicationService {
2833
private readonly fileStorageService: FileStorageService,
2934
@InjectRepository(WorkspaceEntity)
3035
private readonly workspaceRepository: Repository<WorkspaceEntity>,
36+
@InjectRepository(LogicFunctionEntity)
37+
private readonly logicFunctionRepository: Repository<LogicFunctionEntity>,
38+
@InjectRepository(AgentEntity)
39+
private readonly agentRepository: Repository<AgentEntity>,
40+
@InjectRepository(FrontComponentEntity)
41+
private readonly frontComponentRepository: Repository<FrontComponentEntity>,
42+
@InjectRepository(ObjectMetadataEntity)
43+
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
44+
@InjectRepository(ApplicationVariableEntity)
45+
private readonly applicationVariableRepository: Repository<ApplicationVariableEntity>,
3146
) {}
3247

3348
async findApplicationRoleId(
@@ -119,16 +134,7 @@ export class ApplicationService {
119134
): Promise<ApplicationEntity[]> {
120135
return this.applicationRepository.find({
121136
where: { workspaceId },
122-
relations: [
123-
'logicFunctions',
124-
'agents',
125-
'frontComponents',
126-
'objects',
127-
'applicationVariables',
128-
'packageJsonFile',
129-
'yarnLockFile',
130-
'applicationRegistration',
131-
],
137+
relations: ['applicationRegistration'],
132138
});
133139
}
134140

@@ -153,19 +159,46 @@ export class ApplicationService {
153159
...(isDefined(id) ? { id } : { universalIdentifier }),
154160
};
155161

156-
return await this.applicationRepository.findOne({
162+
const application = await this.applicationRepository.findOne({
157163
where,
158-
relations: [
159-
'logicFunctions',
160-
'agents',
161-
'frontComponents',
162-
'objects',
163-
'applicationVariables',
164-
'packageJsonFile',
165-
'yarnLockFile',
166-
'applicationRegistration',
167-
],
164+
relations: ['packageJsonFile', 'yarnLockFile', 'applicationRegistration'],
168165
});
166+
167+
if (!isDefined(application)) {
168+
return null;
169+
}
170+
171+
const [
172+
logicFunctions,
173+
agents,
174+
frontComponents,
175+
objects,
176+
applicationVariables,
177+
] = await Promise.all([
178+
this.logicFunctionRepository.find({
179+
where: { applicationId: application.id, workspaceId },
180+
}),
181+
this.agentRepository.find({
182+
where: { applicationId: application.id, workspaceId },
183+
}),
184+
this.frontComponentRepository.find({
185+
where: { applicationId: application.id, workspaceId },
186+
}),
187+
this.objectMetadataRepository.find({
188+
where: { applicationId: application.id, workspaceId },
189+
}),
190+
this.applicationVariableRepository.find({
191+
where: { applicationId: application.id, workspaceId },
192+
}),
193+
]);
194+
195+
application.logicFunctions = logicFunctions;
196+
application.agents = agents;
197+
application.frontComponents = frontComponents;
198+
application.objects = objects;
199+
application.applicationVariables = applicationVariables;
200+
201+
return application;
169202
}
170203

171204
async findOneApplicationOrThrow({

0 commit comments

Comments
 (0)