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.
This commit is contained in:
@@ -4,15 +4,28 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { WorkspaceFlatApplicationMapCacheService } from 'src/engine/core-modules/application/workspace-flat-application-map-cache.service';
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([ApplicationEntity, WorkspaceEntity]),
|
||||
TypeOrmModule.forFeature([
|
||||
ApplicationEntity,
|
||||
WorkspaceEntity,
|
||||
LogicFunctionEntity,
|
||||
AgentEntity,
|
||||
FrontComponentEntity,
|
||||
ObjectMetadataEntity,
|
||||
ApplicationVariableEntity,
|
||||
]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
WorkspaceCacheModule,
|
||||
TwentyConfigModule,
|
||||
|
||||
@@ -12,10 +12,15 @@ import {
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/application-package/utils/get-default-application-package-fields.util';
|
||||
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/application-package/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { ALL_FLAT_ENTITY_MAPS_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-flat-entity-maps-properties.constant';
|
||||
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import { logicFunctionCreateHash } from 'src/engine/metadata-modules/logic-function/utils/logic-function-create-hash.utils';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||
|
||||
@@ -28,6 +33,16 @@ export class ApplicationService {
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
@InjectRepository(LogicFunctionEntity)
|
||||
private readonly logicFunctionRepository: Repository<LogicFunctionEntity>,
|
||||
@InjectRepository(AgentEntity)
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
@InjectRepository(FrontComponentEntity)
|
||||
private readonly frontComponentRepository: Repository<FrontComponentEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(ApplicationVariableEntity)
|
||||
private readonly applicationVariableRepository: Repository<ApplicationVariableEntity>,
|
||||
) {}
|
||||
|
||||
async findApplicationRoleId(
|
||||
@@ -119,16 +134,7 @@ export class ApplicationService {
|
||||
): Promise<ApplicationEntity[]> {
|
||||
return this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
relations: [
|
||||
'logicFunctions',
|
||||
'agents',
|
||||
'frontComponents',
|
||||
'objects',
|
||||
'applicationVariables',
|
||||
'packageJsonFile',
|
||||
'yarnLockFile',
|
||||
'applicationRegistration',
|
||||
],
|
||||
relations: ['applicationRegistration'],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,19 +159,46 @@ export class ApplicationService {
|
||||
...(isDefined(id) ? { id } : { universalIdentifier }),
|
||||
};
|
||||
|
||||
return await this.applicationRepository.findOne({
|
||||
const application = await this.applicationRepository.findOne({
|
||||
where,
|
||||
relations: [
|
||||
'logicFunctions',
|
||||
'agents',
|
||||
'frontComponents',
|
||||
'objects',
|
||||
'applicationVariables',
|
||||
'packageJsonFile',
|
||||
'yarnLockFile',
|
||||
'applicationRegistration',
|
||||
],
|
||||
relations: ['packageJsonFile', 'yarnLockFile', 'applicationRegistration'],
|
||||
});
|
||||
|
||||
if (!isDefined(application)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [
|
||||
logicFunctions,
|
||||
agents,
|
||||
frontComponents,
|
||||
objects,
|
||||
applicationVariables,
|
||||
] = await Promise.all([
|
||||
this.logicFunctionRepository.find({
|
||||
where: { applicationId: application.id, workspaceId },
|
||||
}),
|
||||
this.agentRepository.find({
|
||||
where: { applicationId: application.id, workspaceId },
|
||||
}),
|
||||
this.frontComponentRepository.find({
|
||||
where: { applicationId: application.id, workspaceId },
|
||||
}),
|
||||
this.objectMetadataRepository.find({
|
||||
where: { applicationId: application.id, workspaceId },
|
||||
}),
|
||||
this.applicationVariableRepository.find({
|
||||
where: { applicationId: application.id, workspaceId },
|
||||
}),
|
||||
]);
|
||||
|
||||
application.logicFunctions = logicFunctions;
|
||||
application.agents = agents;
|
||||
application.frontComponents = frontComponents;
|
||||
application.objects = objects;
|
||||
application.applicationVariables = applicationVariables;
|
||||
|
||||
return application;
|
||||
}
|
||||
|
||||
async findOneApplicationOrThrow({
|
||||
|
||||
Reference in New Issue
Block a user